commit f9eac109c255c28a0cc945d05d7dd437ef76a70d Author: Eriks Karls Date: Fri Jul 19 09:58:26 2019 +0300 Init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d4a2c44 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true +charset = utf-8 +end_of_line = lf + +[*.bat] +indent_style = tab +end_of_line = crlf + +[LICENSE] +insert_final_newline = false + +[Makefile] +indent_style = tab diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..181a31e --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,15 @@ +* eRepublik script version: +* Python version: +* Operating System: + +### Description + +Describe what you were trying to get done. +Tell us what happened, what went wrong, and what you expected to happen. + +### What I Did + +``` +Paste the command(s) you ran and the output. +If there was a crash, please include the traceback here. +``` diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84229f4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,102 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..3c59f7e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +# Config file for automatic testing at travis-ci.org + +language: python +python: + - 3.6 + - 3.5 + - 3.4 + - 2.7 + +# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors +install: pip install -U tox-travis + +# Command to run tests, e.g. python setup.py test +script: tox + + diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..2041989 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,13 @@ +======= +Credits +======= + +Development Lead +---------------- + +* Eriks Karls + +Contributors +------------ + +None yet. Why not be the first? diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..664a434 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,128 @@ +.. highlight:: shell + +============ +Contributing +============ + +Contributions are welcome, and they are greatly appreciated! Every little bit +helps, and credit will always be given. + +You can contribute in many ways: + +Types of Contributions +---------------------- + +Report Bugs +~~~~~~~~~~~ + +Report bugs at https://github.com/eeriks/erepublik_script/issues. + +If you are reporting a bug, please include: + +* Your operating system name and version. +* Any details about your local setup that might be helpful in troubleshooting. +* Detailed steps to reproduce the bug. + +Fix Bugs +~~~~~~~~ + +Look through the GitHub issues for bugs. Anything tagged with "bug" and "help +wanted" is open to whoever wants to implement it. + +Implement Features +~~~~~~~~~~~~~~~~~~ + +Look through the GitHub issues for features. Anything tagged with "enhancement" +and "help wanted" is open to whoever wants to implement it. + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +eRepublik script could always use more documentation, whether as part of the +official eRepublik script docs, in docstrings, or even on the web in blog posts, +articles, and such. + +Submit Feedback +~~~~~~~~~~~~~~~ + +The best way to send feedback is to file an issue at https://github.com/eeriks/erepublik_script/issues. + +If you are proposing a feature: + +* Explain in detail how it would work. +* Keep the scope as narrow as possible, to make it easier to implement. +* Remember that this is a volunteer-driven project, and that contributions + are welcome :) + +Get Started! +------------ + +Ready to contribute? Here's how to set up `erepublik_script` 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 + +3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: + + $ mkvirtualenv erepublik_script + $ cd erepublik_script/ + $ python setup.py develop + +4. Create a branch for local development:: + + $ git checkout -b name-of-your-bugfix-or-feature + + Now you can make your changes locally. + +5. When you're done making changes, check that your changes pass flake8 and the + tests, including testing other Python versions with tox:: + + $ flake8 erepublik_script tests + $ python setup.py test or py.test + $ tox + + To get flake8 and tox, just pip install them into your virtualenv. + +6. Commit your changes and push your branch to GitHub:: + + $ git add . + $ git commit -m "Your detailed description of your changes." + $ git push origin name-of-your-bugfix-or-feature + +7. Submit a pull request through the GitHub website. + +Pull Request Guidelines +----------------------- + +Before you submit a pull request, check that it meets these guidelines: + +1. The pull request should include tests. +2. If the pull request adds functionality, the docs should be updated. Put + your new functionality into a function with a docstring, and add the + feature to the list in README.rst. +3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check + https://travis-ci.org/eeriks/erepublik_script/pull_requests + and make sure that the tests pass for all supported Python versions. + +Tips +---- + +To run a subset of tests:: + + + $ python -m unittest tests.test_erepublik_script + +Deploying +--------- + +A reminder for the maintainers on how to deploy. +Make sure all your changes are committed (including an entry in HISTORY.rst). +Then run:: + +$ bumpversion patch # possible: major / minor / patch +$ git push +$ git push --tags + +Travis will then deploy to PyPI if tests pass. diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..8b692e8 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,8 @@ +======= +History +======= + +0.1.0 (2019-07-19) +------------------ + +* First release on PyPI. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3da5ee4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2019, Eriks Karls + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..965b2dd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,11 @@ +include AUTHORS.rst +include CONTRIBUTING.rst +include HISTORY.rst +include LICENSE +include README.rst + +recursive-include tests * +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] + +recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ae89419 --- /dev/null +++ b/Makefile @@ -0,0 +1,88 @@ +.PHONY: clean clean-test clean-pyc clean-build docs help +.DEFAULT_GOAL := help + +define BROWSER_PYSCRIPT +import os, webbrowser, sys + +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT + +define PRINT_HELP_PYSCRIPT +import re, sys + +for line in sys.stdin: + match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) + if match: + target, help = match.groups() + print("%-20s %s" % (target, help)) +endef +export PRINT_HELP_PYSCRIPT + +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +help: + @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) + +clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts + +clean-build: ## remove build artifacts + rm -fr build/ + rm -fr dist/ + rm -fr .eggs/ + find . -name '*.egg-info' -exec rm -fr {} + + find . -name '*.egg' -exec rm -f {} + + +clean-pyc: ## remove Python file artifacts + find . -name '*.pyc' -exec rm -f {} + + find . -name '*.pyo' -exec rm -f {} + + find . -name '*~' -exec rm -f {} + + find . -name '__pycache__' -exec rm -fr {} + + +clean-test: ## remove test and coverage artifacts + rm -fr .tox/ + rm -f .coverage + rm -fr htmlcov/ + rm -fr .pytest_cache + +lint: ## check style with flake8 + flake8 erepublik_script tests + +test: ## run tests quickly with the default Python + python setup.py test + +test-all: ## run tests on every Python version with tox + tox + +coverage: ## check code coverage quickly with the default Python + coverage run --source erepublik_script setup.py test + coverage report -m + coverage html + $(BROWSER) htmlcov/index.html + +docs: ## generate Sphinx HTML documentation, including API docs + rm -f docs/erepublik_script.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ erepublik_script + $(MAKE) -C docs clean + $(MAKE) -C docs html + $(BROWSER) docs/_build/html/index.html + +servedocs: docs ## compile the docs watching for changes + watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . + +release: dist ## package and upload a release + twine upload dist/* + +dist: clean ## builds source and wheel package + python setup.py sdist + python setup.py bdist_wheel + ls -l dist + +install: clean ## install the package to the active Python's site-packages + python setup.py install diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..585361b --- /dev/null +++ b/README.rst @@ -0,0 +1,41 @@ +================ +eRepublik script +================ + + +.. image:: https://img.shields.io/pypi/v/erepublik_script.svg + :target: https://pypi.python.org/pypi/erepublik_script + +.. image:: https://img.shields.io/travis/eeriks/erepublik_script.svg + :target: https://travis-ci.org/eeriks/erepublik_script + +.. image:: https://readthedocs.org/projects/erepublik-script/badge/?version=latest + :target: https://erepublik-script.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + + +.. image:: https://pyup.io/repos/github/eeriks/erepublik_script/shield.svg + :target: https://pyup.io/repos/github/eeriks/erepublik_script/ + :alt: Updates + + + +Python package for eRepublik automated playing + + +* Free software: MIT license +* Documentation: https://erepublik-script.readthedocs.io. + + +Features +-------- + +* TODO + +Credits +------- + +This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. + +.. _Cookiecutter: https://github.com/audreyr/cookiecutter +.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage diff --git a/debug/requests/2019-07-18_23-40-20_.html b/debug/requests/2019-07-18_23-40-20_.html new file mode 100644 index 0000000..d01e6c3 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-20_.html @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + Free Online Multiplayer Strategy Game | eRepublik + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + +

A new world is emerging. Your country needs YOU!

+ +
+
+

Features

+
    +
  • + + Conquer your country's neighbours and extend its territories +
  • +
  • + + Build a company and develop your economic empire +
  • +
  • + + Fight against real people on the battlefield +
  • +
+
+
+

Top countries

+
    +
  • + Indonesia + Indonesia + 4554 +
  • +
  • + Serbia + Serbia + 2973 +
  • +
  • + Brazil + Brazil + 2819 +
  • +
  • + Hungary + Hungary + 2450 +
  • +
  • + Argentina + Argentina + 2183 +
  • +
+
+
+

What others are saying

+
    +
  • + + “eRepublik creates multiplayer global strategy game” + + +
  • +
  • + + “eRepublik offers a real second life” + + +
  • +
  • + + “eRepublik takes strategy games to the Web” + + +
  • +
+ +
+
+ + + + + + + + + + + + +
+

Join the new world

+   + +
+ +
+
+ + + + + + + + + + +
+ + + diff --git a/debug/requests/2019-07-18_23-40-21_login.html b/debug/requests/2019-07-18_23-40-21_login.html new file mode 100644 index 0000000..45624c2 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-21_login.html @@ -0,0 +1,1655 @@ + + + + + + + + + + + + + + + +eRepublik + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + +
+ + + +
+ +
+
+
+ +

Are your sure you want to do this?

+
+ +
+
+ +
+ +
+ Close +
+

Report content

+
+
+
+
+
+
+
+ + + +
+
+ Close +
+
+
+
+

Error!

+

{{settings.msgs}}

+
+
+

Daily Order completed

+
+
+
+
+ + + +
+
+ +{{daily_order.bparts}} + Different Bazooka parts +
+
+ +{{daily_order.ebs}} + Energy Bar +
+ +
+
+
+
+
+
+
+ + Get Reward + + + Close + +
+
+
+
+
+ + + + + + + + + + + +
+ + + +
+
+

{{data.type.anniversary ? "Anniversary Challenge" : (data.type.summerChallenge ? "Summer Challenge" : (data.type.springChallenge ? "Spring Challenge" : (data.type.halloweenChallenge ? "Halloween Challenge" : "Weekly Challenge")))}}

+
+ +
+

+ + Weekly Challenge completed +

+
+ +
+

+ Next reward
+ {{data.nextReward.text}}

+
+
+ + + 00:00:00 + + +
+ {{data.player.name}} +
+ Get all rewards + +
+ + + {{data.player.name}} + + {{data.player.prestigePoints | number : fractionSize}} Prestige Points + + + + +
+ +
+ +
+ + +
+ + + Get reward +
+ +
+ + +
+ + +
+ + + Get reward +
+ +
+ +
+
+ + +
+
+ +
+
+

{{popup.message}}

+ + Close + +
+
+ + + +
+ + + + + +
+ +
+ + + +
+ +

+ + in + + {{settings.citizen.selectedCountry.name}} + +

+ +
+ + +
    +
  • + + + + +
    +

    Settings

    + +

    Read articles published in:

    + +
    +
    + + + +
    + +
      + +
    • + + + {{country.name}} + +
    • +
    +
    + +

    Show:

    + +
    +
    + + +
    + +
      +
    • + +
    • +
    +
    + + +
    +
  • +
+
+ +
+
+ +
+ + + +
+ +
+ more news + See all +
+
+ + +
+ Close + +
+

Manage objectives

+
+ +
+
+ +
+ +
+
+ +
+
+

+ + + +
+
+ +
+ +
+ +
+
+
+ + +
+
+ + + + +
+ + + + +
+ + +

My contributions

+

Campaign of the day

+

Latvia's Campaigns

+

Allies' campaigns

+

All campaigns

+ + + + + + +
Latvia is not involved in any active battles.
+ + + + + +
Your allies are currently not involved in any military campaigns.
+
    +
  • + + + + + + + + vs + + + + + + + {{campaign.region.name}} + + + Fight + Fight + +
  • +
+ + +
+ + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + + +
+ +
+
+
    +
  • + {{feedData.unreadCount}} + + {{feedData.name}} +
  • +
+
+
+
+

+ + + {{ _getTranslation('feed','battlefield')}} + + + + {{settings.wallData.dailyOrder.progress.current}}/{{settings.wallData.dailyOrder.progress.required}} + + +

+
+ {{_getTranslation('wallTexts','see_more')}} +
+
+
+ + +
+
+
+ {{settings.wallData.postCharactersLimit - settings.newPost.length}} {{_getTranslation('wallTexts','characters_left')}} + +
+
+ {{_getTranslation('feed','postAs')}} + +
+
+
+
+ +
+ +
+
+
+
+ + + + + + + + +
+ + +
+ +
+ +
+

List of eRepublik shortcuts

+
    +
  • AlertsShift + A
  • +
  • Military campaignsShift + C
  • +
  • Military unitShift + M
  • +
  • My placesShift + L
  • +
  • New MessageShift + N
  • +
  • StorageShift + S
  • +
  • Top newsShift + T
  • +
  • World mapShift + W
  • +
  • ResidenceShift + G
  • +
+ (press ESC to close) +
+
+ + diff --git a/debug/requests/2019-07-18_23-40-21_login_REDIRECT.html b/debug/requests/2019-07-18_23-40-21_login_REDIRECT.html new file mode 100644 index 0000000..376ea25 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-21_login_REDIRECT.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/debug/requests/2019-07-18_23-40-22_.html b/debug/requests/2019-07-18_23-40-22_.html new file mode 100644 index 0000000..fb31b56 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-22_.html @@ -0,0 +1,1655 @@ + + + + + + + + + + + + + + + +eRepublik + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + +
+ + + +
+ +
+
+
+ +

Are your sure you want to do this?

+
+ +
+
+ +
+ +
+ Close +
+

Report content

+
+
+
+
+
+
+
+ + + +
+
+ Close +
+
+
+
+

Error!

+

{{settings.msgs}}

+
+
+

Daily Order completed

+
+
+
+
+ + + +
+
+ +{{daily_order.bparts}} + Different Bazooka parts +
+
+ +{{daily_order.ebs}} + Energy Bar +
+ +
+
+
+
+
+
+
+ + Get Reward + + + Close + +
+
+
+
+
+ + + + + + + + + + + +
+ + + +
+
+

{{data.type.anniversary ? "Anniversary Challenge" : (data.type.summerChallenge ? "Summer Challenge" : (data.type.springChallenge ? "Spring Challenge" : (data.type.halloweenChallenge ? "Halloween Challenge" : "Weekly Challenge")))}}

+
+ +
+

+ + Weekly Challenge completed +

+
+ +
+

+ Next reward
+ {{data.nextReward.text}}

+
+
+ + + 00:00:00 + + +
+ {{data.player.name}} +
+ Get all rewards + +
+ + + {{data.player.name}} + + {{data.player.prestigePoints | number : fractionSize}} Prestige Points + + + + +
+ +
+ +
+ + +
+ + + Get reward +
+ +
+ + +
+ + +
+ + + Get reward +
+ +
+ +
+
+ + +
+
+ +
+
+

{{popup.message}}

+ + Close + +
+
+ + + +
+ + + + + +
+ +
+ + + +
+ +

+ + in + + {{settings.citizen.selectedCountry.name}} + +

+ +
+ + +
    +
  • + + + + +
    +

    Settings

    + +

    Read articles published in:

    + +
    +
    + + + +
    + +
      + +
    • + + + {{country.name}} + +
    • +
    +
    + +

    Show:

    + +
    +
    + + +
    + +
      +
    • + +
    • +
    +
    + + +
    +
  • +
+
+ +
+
+ +
+ + + +
+ +
+ more news + See all +
+
+ + +
+ Close + +
+

Manage objectives

+
+ +
+
+ +
+ +
+
+ +
+
+

+ + + +
+
+ +
+ +
+ +
+
+
+ + +
+
+ + + + +
+ + + + +
+ + +

My contributions

+

Campaign of the day

+

Latvia's Campaigns

+

Allies' campaigns

+

All campaigns

+ + + + + + +
Latvia is not involved in any active battles.
+ + + + + +
Your allies are currently not involved in any military campaigns.
+
    +
  • + + + + + + + + vs + + + + + + + {{campaign.region.name}} + + + Fight + Fight + +
  • +
+ + +
+ + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + + +
+ +
+
+
    +
  • + {{feedData.unreadCount}} + + {{feedData.name}} +
  • +
+
+
+
+

+ + + {{ _getTranslation('feed','battlefield')}} + + + + {{settings.wallData.dailyOrder.progress.current}}/{{settings.wallData.dailyOrder.progress.required}} + + +

+
+ {{_getTranslation('wallTexts','see_more')}} +
+
+
+ + +
+
+
+ {{settings.wallData.postCharactersLimit - settings.newPost.length}} {{_getTranslation('wallTexts','characters_left')}} + +
+
+ {{_getTranslation('feed','postAs')}} + +
+
+
+
+ +
+ +
+
+
+
+ + + + + + + + +
+ + +
+ +
+ +
+

List of eRepublik shortcuts

+
    +
  • AlertsShift + A
  • +
  • Military campaignsShift + C
  • +
  • Military unitShift + M
  • +
  • My placesShift + L
  • +
  • New MessageShift + N
  • +
  • StorageShift + S
  • +
  • Top newsShift + T
  • +
  • World mapShift + W
  • +
  • ResidenceShift + G
  • +
+ (press ESC to close) +
+
+ + diff --git a/debug/requests/2019-07-18_23-40-23_.html b/debug/requests/2019-07-18_23-40-23_.html new file mode 100644 index 0000000..8d28825 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-23_.html @@ -0,0 +1,1655 @@ + + + + + + + + + + + + + + + +eRepublik + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + +
+ + + +
+ +
+
+
+ +

Are your sure you want to do this?

+
+ +
+
+ +
+ +
+ Close +
+

Report content

+
+
+
+
+
+
+
+ + + +
+
+ Close +
+
+
+
+

Error!

+

{{settings.msgs}}

+
+
+

Daily Order completed

+
+
+
+
+ + + +
+
+ +{{daily_order.bparts}} + Different Bazooka parts +
+
+ +{{daily_order.ebs}} + Energy Bar +
+ +
+
+
+
+
+
+
+ + Get Reward + + + Close + +
+
+
+
+
+ + + + + + + + + + + +
+ + + +
+
+

{{data.type.anniversary ? "Anniversary Challenge" : (data.type.summerChallenge ? "Summer Challenge" : (data.type.springChallenge ? "Spring Challenge" : (data.type.halloweenChallenge ? "Halloween Challenge" : "Weekly Challenge")))}}

+
+ +
+

+ + Weekly Challenge completed +

+
+ +
+

+ Next reward
+ {{data.nextReward.text}}

+
+
+ + + 00:00:00 + + +
+ {{data.player.name}} +
+ Get all rewards + +
+ + + {{data.player.name}} + + {{data.player.prestigePoints | number : fractionSize}} Prestige Points + + + + +
+ +
+ +
+ + +
+ + + Get reward +
+ +
+ + +
+ + +
+ + + Get reward +
+ +
+ +
+
+ + +
+
+ +
+
+

{{popup.message}}

+ + Close + +
+
+ + + +
+ + + + + +
+ +
+ + + +
+ +

+ + in + + {{settings.citizen.selectedCountry.name}} + +

+ +
+ + +
    +
  • + + + + +
    +

    Settings

    + +

    Read articles published in:

    + +
    +
    + + + +
    + +
      + +
    • + + + {{country.name}} + +
    • +
    +
    + +

    Show:

    + +
    +
    + + +
    + +
      +
    • + +
    • +
    +
    + + +
    +
  • +
+
+ +
+
+ +
+ + + +
+ +
+ more news + See all +
+
+ + +
+ Close + +
+

Manage objectives

+
+ +
+
+ +
+ +
+
+ +
+
+

+ + + +
+
+ +
+ +
+ +
+
+
+ + +
+
+ + + + +
+ + + + +
+ + +

My contributions

+

Campaign of the day

+

Latvia's Campaigns

+

Allies' campaigns

+

All campaigns

+ + + + + + +
Latvia is not involved in any active battles.
+ + + + + +
Your allies are currently not involved in any military campaigns.
+
    +
  • + + + + + + + + vs + + + + + + + {{campaign.region.name}} + + + Fight + Fight + +
  • +
+ + +
+ + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + + +
+ +
+
+
    +
  • + {{feedData.unreadCount}} + + {{feedData.name}} +
  • +
+
+
+
+

+ + + {{ _getTranslation('feed','battlefield')}} + + + + {{settings.wallData.dailyOrder.progress.current}}/{{settings.wallData.dailyOrder.progress.required}} + + +

+
+ {{_getTranslation('wallTexts','see_more')}} +
+
+
+ + +
+
+
+ {{settings.wallData.postCharactersLimit - settings.newPost.length}} {{_getTranslation('wallTexts','characters_left')}} + +
+
+ {{_getTranslation('feed','postAs')}} + +
+
+
+
+ +
+ +
+
+
+
+ + + + + + + + +
+ + +
+ +
+ +
+

List of eRepublik shortcuts

+
    +
  • AlertsShift + A
  • +
  • Military campaignsShift + C
  • +
  • Military unitShift + M
  • +
  • My placesShift + L
  • +
  • New MessageShift + N
  • +
  • StorageShift + S
  • +
  • Top newsShift + T
  • +
  • World mapShift + W
  • +
  • ResidenceShift + G
  • +
+ (press ESC to close) +
+
+ + diff --git a/debug/requests/2019-07-18_23-40-31_.html b/debug/requests/2019-07-18_23-40-31_.html new file mode 100644 index 0000000..96e14d6 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-31_.html @@ -0,0 +1,1655 @@ + + + + + + + + + + + + + + + +eRepublik + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + +
+ + + +
+ +
+
+
+ +

Are your sure you want to do this?

+
+ +
+
+ +
+ +
+ Close +
+

Report content

+
+
+
+
+
+
+
+ + + +
+
+ Close +
+
+
+
+

Error!

+

{{settings.msgs}}

+
+
+

Daily Order completed

+
+
+
+
+ + + +
+
+ +{{daily_order.bparts}} + Different Bazooka parts +
+
+ +{{daily_order.ebs}} + Energy Bar +
+ +
+
+
+
+
+
+
+ + Get Reward + + + Close + +
+
+
+
+
+ + + + + + + + + + + +
+ + + +
+
+

{{data.type.anniversary ? "Anniversary Challenge" : (data.type.summerChallenge ? "Summer Challenge" : (data.type.springChallenge ? "Spring Challenge" : (data.type.halloweenChallenge ? "Halloween Challenge" : "Weekly Challenge")))}}

+
+ +
+

+ + Weekly Challenge completed +

+
+ +
+

+ Next reward
+ {{data.nextReward.text}}

+
+
+ + + 00:00:00 + + +
+ {{data.player.name}} +
+ Get all rewards + +
+ + + {{data.player.name}} + + {{data.player.prestigePoints | number : fractionSize}} Prestige Points + + + + +
+ +
+ +
+ + +
+ + + Get reward +
+ +
+ + +
+ + +
+ + + Get reward +
+ +
+ +
+
+ + +
+
+ +
+
+

{{popup.message}}

+ + Close + +
+
+ + + +
+ + + + + +
+ +
+ + + +
+ +

+ + in + + {{settings.citizen.selectedCountry.name}} + +

+ +
+ + +
    +
  • + + + + +
    +

    Settings

    + +

    Read articles published in:

    + +
    +
    + + + +
    + +
      + +
    • + + + {{country.name}} + +
    • +
    +
    + +

    Show:

    + +
    +
    + + +
    + +
      +
    • + +
    • +
    +
    + + +
    +
  • +
+
+ +
+
+ +
+ + + +
+ +
+ more news + See all +
+
+ + +
+ Close + +
+

Manage objectives

+
+ +
+
+ +
+ +
+
+ +
+
+

+ + + +
+
+ +
+ +
+ +
+
+
+ + +
+
+ + + + +
+ + + + +
+ + +

My contributions

+

Campaign of the day

+

Latvia's Campaigns

+

Allies' campaigns

+

All campaigns

+ + + + + + +
Latvia is not involved in any active battles.
+ + + + + +
Your allies are currently not involved in any military campaigns.
+
    +
  • + + + + + + + + vs + + + + + + + {{campaign.region.name}} + + + Fight + Fight + +
  • +
+ + +
+ + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + + +
+ +
+
+
    +
  • + {{feedData.unreadCount}} + + {{feedData.name}} +
  • +
+
+
+
+

+ + + {{ _getTranslation('feed','battlefield')}} + + + + {{settings.wallData.dailyOrder.progress.current}}/{{settings.wallData.dailyOrder.progress.required}} + + +

+
+ {{_getTranslation('wallTexts','see_more')}} +
+
+
+ + +
+
+
+ {{settings.wallData.postCharactersLimit - settings.newPost.length}} {{_getTranslation('wallTexts','characters_left')}} + +
+
+ {{_getTranslation('feed','postAs')}} + +
+
+
+
+ +
+ +
+
+
+
+ + + + + + + + +
+ + +
+ +
+ +
+

List of eRepublik shortcuts

+
    +
  • AlertsShift + A
  • +
  • Military campaignsShift + C
  • +
  • Military unitShift + M
  • +
  • My placesShift + L
  • +
  • New MessageShift + N
  • +
  • StorageShift + S
  • +
  • Top newsShift + T
  • +
  • World mapShift + W
  • +
  • ResidenceShift + G
  • +
+ (press ESC to close) +
+
+ + diff --git a/debug/requests/2019-07-18_23-40-32_military-campaigns-new.json b/debug/requests/2019-07-18_23-40-32_military-campaigns-new.json new file mode 100644 index 0000000..c5e4465 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-32_military-campaigns-new.json @@ -0,0 +1 @@ +{"request_time":1563518432,"battles":{"200307":{"id":200307,"war_id":114449,"zone_id":9,"is_rw":false,"is_as":false,"type":"tanks","start":1563512342,"det":1,"region":{"id":709,"name":"Hrodzienskaya"},"city":{"id":755,"name":"Hrodna"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":166,"allies":[58],"ally_list":[{"id":58,"deployed":true}],"points":30},"def":{"id":41,"allies":[42,78,171,31,81,38],"ally_list":[{"id":42,"deployed":true},{"id":78,"deployed":true},{"id":171,"deployed":true},{"id":31,"deployed":true},{"id":81,"deployed":true},{"id":38,"deployed":true}],"points":90},"div":{"1":{"id":5862663,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":890,"def":1570},"wall":{"for":41,"dom":67.55}},"2":{"id":5862664,"div":2,"end":1563517922,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":110,"def":1810},"wall":{"for":41,"dom":82.73}},"3":{"id":5862665,"div":3,"end":1563518342,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":530,"def":1810},"wall":{"for":41,"dom":76.02}},"4":{"id":5862666,"div":4,"end":1563518102,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":1840},"wall":{"for":41,"dom":60.76}}}},"200375":{"id":200375,"war_id":115537,"zone_id":2,"is_rw":true,"is_as":false,"type":"tanks","start":1563512342,"det":1.11,"region":{"id":117,"name":"Northwest of Mexico"},"city":{"id":278,"name":"Culiacan"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":26,"allies":[],"ally_list":[],"points":19},"def":{"id":43,"allies":[],"ally_list":[],"points":1},"div":{"1":{"id":5862667,"div":1,"end":1563517922,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1800,"def":120},"wall":{"for":26,"dom":57.93}},"2":{"id":5862668,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1660,"def":800},"wall":{"for":26,"dom":66.33}},"3":{"id":5862669,"div":3,"end":1563518282,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1820,"def":460},"wall":{"for":26,"dom":53.11}},"4":{"id":5862670,"div":4,"end":1563517981,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1820,"def":160},"wall":{"for":26,"dom":65.14}}}},"200260":{"id":200260,"war_id":114727,"zone_id":11,"is_rw":false,"is_as":false,"type":"tanks","start":1563512404,"det":1,"region":{"id":157,"name":"Lisboa"},"city":{"id":306,"name":"Lisbon"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":1,"allies":[81],"ally_list":[{"id":81,"deployed":true}],"points":61},"def":{"id":53,"allies":[78,31],"ally_list":[{"id":78,"deployed":true},{"id":31,"deployed":true}],"points":81},"div":{"1":{"id":5862671,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1000,"def":1400},"wall":{"for":53,"dom":54.62}},"2":{"id":5862672,"div":2,"end":1563518162,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":270,"def":1830},"wall":{"for":53,"dom":55.3}},"3":{"id":5862673,"div":3,"end":1563517922,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1820,"def":40},"wall":{"for":1,"dom":61.43}},"4":{"id":5862674,"div":4,"end":1563517862,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1800,"def":0},"wall":{"for":1,"dom":79.28}}}},"200299":{"id":200299,"war_id":114282,"zone_id":10,"is_rw":false,"is_as":false,"type":"tanks","start":1563512583,"det":1,"region":{"id":710,"name":"Minskaya"},"city":{"id":756,"name":"Minsk"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":83,"allies":[64],"ally_list":[{"id":64,"deployed":true}],"points":54},"def":{"id":166,"allies":[58],"ally_list":[{"id":58,"deployed":true}],"points":70},"div":{"1":{"id":5862687,"div":1,"end":1563518042,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":166,"dom":61.1}},"2":{"id":5862688,"div":2,"end":1563518042,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":166,"dom":58.47}},"3":{"id":5862689,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":450,"def":1770},"wall":{"for":83,"dom":52.71}},"4":{"id":5862690,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":840,"def":1380},"wall":{"for":83,"dom":51.25}}}},"200367":{"id":200367,"war_id":110845,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563512823,"det":1,"region":{"id":155,"name":"Cuyo"},"city":{"id":249,"name":"Mendoza"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":0},"def":{"id":27,"allies":[],"ally_list":[],"points":25},"div":{"1":{"id":5862695,"div":1,"end":1563518282,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":27,"dom":94.99}},"2":{"id":5862696,"div":2,"end":1563518282,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":27,"dom":54.76}},"3":{"id":5862697,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1390,"def":590},"wall":{"for":27,"dom":54.64}},"4":{"id":5862698,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":150,"def":1830},"wall":{"for":27,"dom":54.69}}}},"200340":{"id":200340,"war_id":105124,"zone_id":6,"is_rw":false,"is_as":false,"type":"tanks","start":1563512886,"det":1,"region":{"id":446,"name":"Rajasthan"},"city":{"id":492,"name":"Jaipur"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":44,"allies":[],"ally_list":[],"points":0},"def":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":67},"div":{"1":{"id":5862699,"div":1,"end":1563518403,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":1850},"wall":{"for":43,"dom":60.78}},"2":{"id":5862700,"div":2,"end":1563518403,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":1850},"wall":{"for":43,"dom":63.02}},"3":{"id":5862701,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":710,"def":1210},"wall":{"for":43,"dom":52.36}},"4":{"id":5862702,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":400,"def":1520},"wall":{"for":43,"dom":50.82}}}},"200368":{"id":200368,"war_id":110147,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563512943,"det":1.17,"region":{"id":334,"name":"Northern Territory"},"city":{"id":424,"name":"Darwin"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":24},"def":{"id":50,"allies":[54,80],"ally_list":[{"id":54,"deployed":true},{"id":80,"deployed":true}],"points":3},"div":{"1":{"id":5862703,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1790,"def":70},"wall":{"for":63,"dom":99.16}},"2":{"id":5862704,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":310,"def":1550},"wall":{"for":50,"dom":59.65}},"3":{"id":5862705,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1710,"def":150},"wall":{"for":63,"dom":58.11}},"4":{"id":5862706,"div":4,"end":1563518403,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1800,"def":0},"wall":{"for":63,"dom":79.92}}}},"200341":{"id":200341,"war_id":113618,"zone_id":6,"is_rw":false,"is_as":false,"type":"tanks","start":1563513003,"det":1,"region":{"id":504,"name":"Northern Cape"},"city":{"id":547,"name":"Kimberley"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":9,"allies":[171],"ally_list":[{"id":171,"deployed":true}],"points":5},"def":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":61},"div":{"1":{"id":5862711,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":43,"dom":63.48}},"2":{"id":5862712,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":43,"dom":57.49}},"3":{"id":5862713,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":1790},"wall":{"for":43,"dom":52.44}},"4":{"id":5862714,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":240,"def":1560},"wall":{"for":9,"dom":64.96}}}},"200348":{"id":200348,"war_id":115533,"zone_id":5,"is_rw":true,"is_as":false,"type":"tanks","start":1563513062,"det":1.06,"region":{"id":220,"name":"South East of England"},"city":{"id":340,"name":"Oxford"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":29,"allies":[],"ally_list":[],"points":9},"def":{"id":64,"allies":[],"ally_list":[],"points":46},"div":{"1":{"id":5862715,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1230,"def":540},"wall":{"for":29,"dom":99.27}},"2":{"id":5862716,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1770},"wall":{"for":64,"dom":52.97}},"3":{"id":5862717,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":60,"def":1710},"wall":{"for":29,"dom":63.93}},"4":{"id":5862718,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":1750},"wall":{"for":64,"dom":55.2}}}},"200378":{"id":200378,"war_id":111202,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563513062,"det":1,"region":{"id":331,"name":"Tasmania"},"city":{"id":421,"name":"Hobart"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":69,"allies":[43],"ally_list":[{"id":43,"deployed":true}],"points":0},"def":{"id":50,"allies":[54,80],"ally_list":[{"id":54,"deployed":true},{"id":80,"deployed":true}],"points":11},"div":{"1":{"id":5862719,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1770},"wall":{"for":50,"dom":59.22}},"2":{"id":5862720,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1770},"wall":{"for":50,"dom":50.68}},"3":{"id":5862721,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1770},"wall":{"for":50,"dom":52.78}},"4":{"id":5862722,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1770},"wall":{"for":50,"dom":55.12}}}},"200391":{"id":200391,"war_id":104337,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563512831,"det":1,"region":{"id":623,"name":"Central Croatia"},"city":{"id":663,"name":"Zagreb"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":1},"def":{"id":45,"allies":[65,40],"ally_list":[{"id":65,"deployed":true},{"id":40,"deployed":true}],"points":5},"div":{"1":{"id":5862723,"div":1,"end":1563518342,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1850,"def":10},"wall":{"for":63,"dom":81.03}},"2":{"id":5862724,"div":2,"end":1563518282,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":45,"dom":74.3}},"3":{"id":5862725,"div":3,"end":1563518282,"division_end":true,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1800},"wall":{"for":45,"dom":63.19}},"4":{"id":5862726,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1660,"def":260},"wall":{"for":63,"dom":61.79}}}},"200318":{"id":200318,"war_id":115518,"zone_id":8,"is_rw":true,"is_as":false,"type":"aircraft","start":1563513242,"det":1.35,"region":{"id":136,"name":"Podolia"},"city":{"id":294,"name":"Kamianets-Podilskyi"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":40,"allies":[],"ally_list":[],"points":61},"def":{"id":81,"allies":[],"ally_list":[],"points":27},"div":{"11":{"id":5862727,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1680,"def":0},"wall":{"for":40,"dom":86.22}}}},"200319":{"id":200319,"war_id":93137,"zone_id":8,"is_rw":false,"is_as":false,"type":"aircraft","start":1563513242,"det":1,"region":{"id":653,"name":"Rio Grande do Sul"},"city":{"id":693,"name":"Porto Alegre"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":74,"allies":[64,75],"ally_list":[{"id":64,"deployed":true},{"id":75,"deployed":true}],"points":3},"def":{"id":9,"allies":[171],"ally_list":[{"id":171,"deployed":true}],"points":85},"div":{"11":{"id":5862728,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":670,"def":1010},"wall":{"for":9,"dom":50.93}}}},"200330":{"id":200330,"war_id":114384,"zone_id":7,"is_rw":false,"is_as":false,"type":"tanks","start":1563513303,"det":1.11,"region":{"id":294,"name":"Vestlandet"},"city":{"id":388,"name":"Bergen"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":35,"allies":[70,12,57,15],"ally_list":[{"id":70,"deployed":true},{"id":12,"deployed":true},{"id":57,"deployed":true},{"id":15,"deployed":true}],"points":75},"def":{"id":37,"allies":[32],"ally_list":[{"id":32,"deployed":true}],"points":2},"div":{"1":{"id":5862729,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1470,"def":180},"wall":{"for":35,"dom":95.48}},"2":{"id":5862730,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1190,"def":460},"wall":{"for":35,"dom":62.78}},"3":{"id":5862731,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1150,"def":500},"wall":{"for":35,"dom":73.56}},"4":{"id":5862732,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1650,"def":0},"wall":{"for":35,"dom":95.54}}}},"200366":{"id":200366,"war_id":113861,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563513303,"det":1,"region":{"id":452,"name":"Tamil Nadu"},"city":{"id":498,"name":"Chennai"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":74,"allies":[64,75],"ally_list":[{"id":64,"deployed":true},{"id":75,"deployed":true}],"points":9},"def":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":13},"div":{"1":{"id":5862733,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1650},"wall":{"for":63,"dom":65.26}},"2":{"id":5862734,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":420,"def":1230},"wall":{"for":63,"dom":54.36}},"3":{"id":5862735,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1650},"wall":{"for":63,"dom":57.89}},"4":{"id":5862736,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1650},"wall":{"for":63,"dom":72.46}}}},"200310":{"id":200310,"war_id":104744,"zone_id":9,"is_rw":false,"is_as":false,"type":"tanks","start":1563513362,"det":1,"region":{"id":11,"name":"Banat"},"city":{"id":115,"name":"Timisoara"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":13,"allies":[],"ally_list":[],"points":25},"def":{"id":44,"allies":[],"ally_list":[],"points":85},"div":{"1":{"id":5862737,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1620},"wall":{"for":44,"dom":55.78}},"2":{"id":5862738,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1620},"wall":{"for":44,"dom":82.02}},"3":{"id":5862739,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1620},"wall":{"for":44,"dom":79.25}},"4":{"id":5862740,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":160,"def":1460},"wall":{"for":44,"dom":55.1}}}},"200324":{"id":200324,"war_id":105197,"zone_id":7,"is_rw":false,"is_as":false,"type":"tanks","start":1563513363,"det":1.14,"region":{"id":735,"name":"Red Sea Coast"},"city":{"id":773,"name":"Hurghada"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":70},"def":{"id":165,"allies":[],"ally_list":[],"points":7},"div":{"1":{"id":5862741,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":760,"def":860},"wall":{"for":165,"dom":88.23}},"2":{"id":5862742,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1550,"def":70},"wall":{"for":64,"dom":86.47}},"3":{"id":5862743,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1620,"def":0},"wall":{"for":64,"dom":60.43}},"4":{"id":5862744,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1610,"def":10},"wall":{"for":64,"dom":72.48}}}},"200359":{"id":200359,"war_id":97524,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563513363,"det":1,"region":{"id":625,"name":"Lika and Gorski Kotar"},"city":{"id":665,"name":"Karlovac"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":1},"def":{"id":41,"allies":[42,78,171,31,81,38],"ally_list":[{"id":42,"deployed":true},{"id":78,"deployed":true},{"id":171,"deployed":true},{"id":31,"deployed":true},{"id":81,"deployed":true},{"id":38,"deployed":true}],"points":32},"div":{"11":{"id":5862745,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":200,"def":1420},"wall":{"for":41,"dom":66.58}}}},"200361":{"id":200361,"war_id":105808,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563513544,"det":1,"region":{"id":355,"name":"Sofia"},"city":{"id":236,"name":"Sofia"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":0},"def":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":33},"div":{"11":{"id":5862746,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":410,"def":1120},"wall":{"for":43,"dom":53.03}}}},"200392":{"id":200392,"war_id":115359,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563513492,"det":1,"region":{"id":324,"name":"Scania"},"city":{"id":415,"name":"Malmo"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":72,"allies":[52,65],"ally_list":[{"id":52,"deployed":true},{"id":65,"deployed":true}],"points":0},"def":{"id":38,"allies":[70,41],"ally_list":[{"id":70,"deployed":true},{"id":41,"deployed":true}],"points":0},"div":{"1":{"id":5862747,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1530,"def":0},"wall":{"for":72,"dom":61.38}},"2":{"id":5862748,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1530,"def":0},"wall":{"for":72,"dom":58.24}},"3":{"id":5862749,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1530,"def":0},"wall":{"for":72,"dom":62.23}},"4":{"id":5862750,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":90,"def":1440},"wall":{"for":38,"dom":93.58}}}},"200393":{"id":200393,"war_id":114885,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563513494,"det":1,"region":{"id":230,"name":"Wallonia"},"city":{"id":148,"name":"Namur"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":72,"allies":[52,65],"ally_list":[{"id":52,"deployed":true},{"id":65,"deployed":true}],"points":0},"def":{"id":32,"allies":[55,37],"ally_list":[{"id":55,"deployed":true},{"id":37,"deployed":true}],"points":0},"div":{"1":{"id":5862751,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1520,"def":10},"wall":{"for":72,"dom":60.42}},"2":{"id":5862752,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1520,"def":10},"wall":{"for":72,"dom":60.24}},"3":{"id":5862753,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1520,"def":10},"wall":{"for":72,"dom":58.98}},"4":{"id":5862754,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1170,"def":360},"wall":{"for":72,"dom":66.85}}}},"200394":{"id":200394,"war_id":113185,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563513495,"det":1,"region":{"id":712,"name":"Vitsebskaya"},"city":{"id":758,"name":"Vitsebsk"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":72,"allies":[52,65],"ally_list":[{"id":52,"deployed":true},{"id":65,"deployed":true}],"points":0},"def":{"id":83,"allies":[64],"ally_list":[{"id":64,"deployed":true}],"points":0},"div":{"1":{"id":5862755,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1510,"def":20},"wall":{"for":72,"dom":59.26}},"2":{"id":5862756,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":1510},"wall":{"for":83,"dom":55.5}},"3":{"id":5862757,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1510,"def":20},"wall":{"for":72,"dom":53.94}},"4":{"id":5862758,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1530},"wall":{"for":83,"dom":54.36}}}},"200395":{"id":200395,"war_id":110659,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563513496,"det":1,"region":{"id":661,"name":"Zemgale"},"city":{"id":701,"name":"Jelgava"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":72,"allies":[52,65],"ally_list":[{"id":52,"deployed":true},{"id":65,"deployed":true}],"points":0},"def":{"id":71,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862759,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1500,"def":30},"wall":{"for":72,"dom":99.53}},"2":{"id":5862760,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":140,"def":1390},"wall":{"for":71,"dom":51.7}},"3":{"id":5862761,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1530},"wall":{"for":71,"dom":52.34}},"4":{"id":5862762,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1380,"def":150},"wall":{"for":71,"dom":64.02}}}},"200343":{"id":200343,"war_id":115531,"zone_id":6,"is_rw":true,"is_as":false,"type":"tanks","start":1563513843,"det":1.02,"region":{"id":63,"name":"Mississippi"},"city":{"id":187,"name":"Jackson"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":24,"allies":[],"ally_list":[],"points":52},"def":{"id":64,"allies":[],"ally_list":[],"points":14},"div":{"1":{"id":5862763,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1330,"def":50},"wall":{"for":24,"dom":64.64}},"2":{"id":5862764,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1330,"def":50},"wall":{"for":24,"dom":56.4}},"3":{"id":5862765,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1320,"def":60},"wall":{"for":24,"dom":60.04}},"4":{"id":5862766,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":940,"def":440},"wall":{"for":24,"dom":60.9}}}},"200349":{"id":200349,"war_id":114649,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563513843,"det":1,"region":{"id":186,"name":"Aquitaine"},"city":{"id":95,"name":"Bordeaux"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":47,"allies":[63],"ally_list":[{"id":63,"deployed":true}],"points":44},"def":{"id":11,"allies":[29],"ally_list":[{"id":29,"deployed":true}],"points":11},"div":{"1":{"id":5862767,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1320,"def":60},"wall":{"for":47,"dom":75.57}},"2":{"id":5862768,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1320,"def":60},"wall":{"for":47,"dom":58.85}},"3":{"id":5862769,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":940,"def":440},"wall":{"for":47,"dom":61.08}},"4":{"id":5862770,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1380,"def":0},"wall":{"for":47,"dom":64.41}}}},"200339":{"id":200339,"war_id":115528,"zone_id":6,"is_rw":true,"is_as":false,"type":"tanks","start":1563513902,"det":1.02,"region":{"id":62,"name":"Minnesota"},"city":{"id":182,"name":"Saint Paul"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":24,"allies":[],"ally_list":[],"points":57},"def":{"id":43,"allies":[],"ally_list":[],"points":9},"div":{"1":{"id":5862771,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1340,"def":10},"wall":{"for":24,"dom":54.75}},"2":{"id":5862772,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1320,"def":30},"wall":{"for":24,"dom":53.69}},"3":{"id":5862773,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1320,"def":30},"wall":{"for":24,"dom":53.2}},"4":{"id":5862774,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1340,"def":10},"wall":{"for":24,"dom":89.46}}}},"200329":{"id":200329,"war_id":113202,"zone_id":7,"is_rw":false,"is_as":false,"type":"tanks","start":1563514142,"det":1,"region":{"id":144,"name":"Taurida"},"city":{"id":302,"name":"Simferopol"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":27,"allies":[],"ally_list":[],"points":10},"def":{"id":40,"allies":[49,45],"ally_list":[{"id":49,"deployed":true},{"id":45,"deployed":true}],"points":67},"div":{"1":{"id":5862775,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1140,"def":90},"wall":{"for":27,"dom":100}},"2":{"id":5862776,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1130,"def":100},"wall":{"for":27,"dom":55.76}},"3":{"id":5862777,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":1200},"wall":{"for":40,"dom":51.6}},"4":{"id":5862778,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":750,"def":480},"wall":{"for":40,"dom":66.33}}}},"200298":{"id":200298,"war_id":84302,"zone_id":10,"is_rw":false,"is_as":false,"type":"tanks","start":1563514202,"det":1,"region":{"id":150,"name":"Parana and Santa Catarina"},"city":{"id":304,"name":"Curitiba"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":27,"allies":[],"ally_list":[],"points":72},"def":{"id":9,"allies":[171],"ally_list":[{"id":171,"deployed":true}],"points":49},"div":{"1":{"id":5862779,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":70,"def":1130},"wall":{"for":9,"dom":64.45}},"2":{"id":5862780,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":1180},"wall":{"for":9,"dom":62.36}},"3":{"id":5862781,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":70,"def":1130},"wall":{"for":9,"dom":57.14}},"4":{"id":5862782,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":50,"def":1150},"wall":{"for":9,"dom":55.81}}}},"200360":{"id":200360,"war_id":90970,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563514325,"det":1,"region":{"id":9,"name":"Oltenia"},"city":{"id":120,"name":"Craiova"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":23},"def":{"id":44,"allies":[],"ally_list":[],"points":10},"div":{"11":{"id":5862783,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":200,"def":940},"wall":{"for":44,"dom":63.88}}}},"200352":{"id":200352,"war_id":114337,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563514383,"det":1,"region":{"id":267,"name":"Lazio"},"city":{"id":13,"name":"Rome"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":59,"allies":[63],"ally_list":[{"id":63,"deployed":true}],"points":50},"def":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":5},"div":{"1":{"id":5862784,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":550,"def":560},"wall":{"for":59,"dom":52.47}},"2":{"id":5862785,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":550,"def":560},"wall":{"for":59,"dom":56.49}},"3":{"id":5862786,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":570,"def":540},"wall":{"for":59,"dom":61.89}},"4":{"id":5862787,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":960,"def":150},"wall":{"for":59,"dom":63}}}},"200380":{"id":200380,"war_id":95190,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563514383,"det":1,"region":{"id":521,"name":"Chungcheongbuk-do"},"city":{"id":562,"name":"Cheongju"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":11},"def":{"id":47,"allies":[63],"ally_list":[{"id":63,"deployed":true}],"points":0},"div":{"1":{"id":5862788,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1110,"def":0},"wall":{"for":43,"dom":59.18}},"2":{"id":5862789,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":360,"def":750},"wall":{"for":47,"dom":68.8}},"3":{"id":5862790,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":800,"def":310},"wall":{"for":47,"dom":63.59}},"4":{"id":5862791,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1000,"def":110},"wall":{"for":43,"dom":51.26}}}},"200396":{"id":200396,"war_id":108845,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563514092,"det":1,"region":{"id":489,"name":"Chugoku"},"city":{"id":533,"name":"Hiroshima"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":70,"allies":[42,23,171,12,35,15,38],"ally_list":[{"id":42,"deployed":true},{"id":23,"deployed":true},{"id":171,"deployed":true},{"id":12,"deployed":true},{"id":35,"deployed":true},{"id":15,"deployed":true},{"id":38,"deployed":true}],"points":0},"def":{"id":45,"allies":[65,40],"ally_list":[{"id":65,"deployed":true},{"id":40,"deployed":true}],"points":0},"div":{"1":{"id":5862792,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":920,"def":310},"wall":{"for":70,"dom":59.37}},"2":{"id":5862793,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":110,"def":1120},"wall":{"for":45,"dom":70.07}},"3":{"id":5862794,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":120,"def":1110},"wall":{"for":45,"dom":55.4}},"4":{"id":5862795,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":960,"def":270},"wall":{"for":70,"dom":57.43}}}},"200397":{"id":200397,"war_id":115544,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563514094,"det":1,"region":{"id":460,"name":"Sumatra"},"city":{"id":506,"name":"Medan"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":68,"allies":[],"ally_list":[],"points":0},"def":{"id":49,"allies":[30,40,29],"ally_list":[{"id":30,"deployed":true},{"id":40,"deployed":true},{"id":29,"deployed":true}],"points":0},"div":{"1":{"id":5862796,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":1210},"wall":{"for":49,"dom":56.33}},"2":{"id":5862797,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1090,"def":140},"wall":{"for":68,"dom":52.84}},"3":{"id":5862798,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":1200},"wall":{"for":49,"dom":52.07}},"4":{"id":5862799,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":1200},"wall":{"for":49,"dom":73.35}}}},"200358":{"id":200358,"war_id":94084,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563514442,"det":1.66,"region":{"id":526,"name":"Gyeongsangnam-do"},"city":{"id":567,"name":"Changwon"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":27,"allies":[],"ally_list":[],"points":25},"def":{"id":47,"allies":[63],"ally_list":[{"id":63,"deployed":true}],"points":8},"div":{"11":{"id":5862800,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1080},"wall":{"for":47,"dom":62.91}}}},"200398":{"id":200398,"war_id":115545,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563514151,"det":1.37,"region":{"id":232,"name":"Midtjylland"},"city":{"id":348,"name":"Viborg"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":55,"allies":[],"ally_list":[],"points":0},"def":{"id":12,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862801,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1070,"def":130},"wall":{"for":55,"dom":56.78}},"2":{"id":5862802,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1030,"def":170},"wall":{"for":55,"dom":62}},"3":{"id":5862803,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1030,"def":170},"wall":{"for":55,"dom":59.66}},"4":{"id":5862804,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":1060,"def":140},"wall":{"for":55,"dom":52.84}}}},"200321":{"id":200321,"war_id":115520,"zone_id":8,"is_rw":true,"is_as":false,"type":"aircraft","start":1563514562,"det":1.17,"region":{"id":649,"name":"West Srpska Republic"},"city":{"id":692,"name":"Banja Luka"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":69,"allies":[],"ally_list":[],"points":18},"def":{"id":167,"allies":[],"ally_list":[],"points":70},"div":{"11":{"id":5862805,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":110,"def":910},"wall":{"for":167,"dom":52.78}}}},"200350":{"id":200350,"war_id":111066,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563514622,"det":1,"region":{"id":270,"name":"Marche"},"city":{"id":374,"name":"Ancona"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":53},"def":{"id":14,"allies":[],"ally_list":[],"points":2},"div":{"1":{"id":5862806,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":990,"def":0},"wall":{"for":43,"dom":53.17}},"2":{"id":5862807,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":990,"def":0},"wall":{"for":43,"dom":56.93}},"3":{"id":5862808,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":40,"def":950},"wall":{"for":14,"dom":65.82}},"4":{"id":5862809,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":960},"wall":{"for":14,"dom":83.5}}}},"200305":{"id":200305,"war_id":111084,"zone_id":10,"is_rw":false,"is_as":false,"type":"tanks","start":1563514802,"det":1,"region":{"id":69,"name":"New Jersey"},"city":{"id":210,"name":"Trenton"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":13,"allies":[],"ally_list":[],"points":45},"def":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":76},"div":{"1":{"id":5862810,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":900},"wall":{"for":64,"dom":50.08}},"2":{"id":5862811,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":900},"wall":{"for":64,"dom":69.2}},"3":{"id":5862812,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":150,"def":750},"wall":{"for":64,"dom":77.88}},"4":{"id":5862813,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":880},"wall":{"for":64,"dom":77.22}}}},"200399":{"id":200399,"war_id":115546,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563514511,"det":1.2,"region":{"id":694,"name":"Cundiboyacense"},"city":{"id":741,"name":"Bogota"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":78,"allies":[],"ally_list":[],"points":0},"def":{"id":13,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862814,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1020},"wall":{"for":13,"dom":100}},"2":{"id":5862815,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":140,"def":880},"wall":{"for":13,"dom":58.69}},"3":{"id":5862816,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1020},"wall":{"for":13,"dom":52.59}},"4":{"id":5862817,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":1020},"wall":{"for":13,"dom":83.06}}}},"200381":{"id":200381,"war_id":115069,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563514983,"det":1,"region":{"id":672,"name":"Hamgyong"},"city":{"id":719,"name":"Hamhung"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":44,"allies":[],"ally_list":[],"points":5},"def":{"id":73,"allies":[],"ally_list":[],"points":6},"div":{"1":{"id":5862818,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":830},"wall":{"for":73,"dom":52.94}},"2":{"id":5862819,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":60,"def":780},"wall":{"for":73,"dom":56.86}},"3":{"id":5862820,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":110,"def":730},"wall":{"for":73,"dom":51.19}},"4":{"id":5862821,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":840,"def":0},"wall":{"for":44,"dom":70.13}}}},"200320":{"id":200320,"war_id":111615,"zone_id":8,"is_rw":false,"is_as":false,"type":"aircraft","start":1563515042,"det":1.02,"region":{"id":129,"name":"Llanos"},"city":{"id":286,"name":"San Juan de Los Morros"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":61,"allies":[],"ally_list":[],"points":76},"def":{"id":28,"allies":[],"ally_list":[],"points":12},"div":{"11":{"id":5862822,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":780,"def":40},"wall":{"for":61,"dom":57.91}}}},"200400":{"id":200400,"war_id":115547,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563514751,"det":1,"region":{"id":194,"name":"Languedoc Roussillon"},"city":{"id":96,"name":"Montpellier"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":11,"allies":[],"ally_list":[],"points":0},"def":{"id":170,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862823,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":890,"def":10},"wall":{"for":11,"dom":67.4}},"2":{"id":5862824,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":860,"def":40},"wall":{"for":11,"dom":60.13}},"3":{"id":5862825,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":870,"def":30},"wall":{"for":11,"dom":70.58}},"4":{"id":5862826,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":890,"def":10},"wall":{"for":11,"dom":79.59}}}},"200346":{"id":200346,"war_id":115529,"zone_id":6,"is_rw":false,"is_as":false,"type":"tanks","start":1563515103,"det":1,"region":{"id":634,"name":"Vojvodina"},"city":{"id":675,"name":"Novi Sad"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":66},"def":{"id":65,"allies":[45,72,77],"ally_list":[{"id":45,"deployed":true},{"id":72,"deployed":true},{"id":77,"deployed":true}],"points":0},"div":{"1":{"id":5862827,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":660,"def":140},"wall":{"for":63,"dom":74.27}},"2":{"id":5862828,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":630,"def":170},"wall":{"for":63,"dom":82.06}},"3":{"id":5862829,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":530,"def":270},"wall":{"for":63,"dom":60.82}},"4":{"id":5862830,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":710,"def":90},"wall":{"for":63,"dom":55.32}}}},"200371":{"id":200371,"war_id":115536,"zone_id":3,"is_rw":true,"is_as":false,"type":"tanks","start":1563515163,"det":1.01,"region":{"id":242,"name":"Aland"},"city":{"id":356,"name":"Mariehamn"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":39,"allies":[],"ally_list":[],"points":22},"def":{"id":35,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862831,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":730,"def":50},"wall":{"for":39,"dom":60.53}},"2":{"id":5862832,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":740,"def":40},"wall":{"for":39,"dom":58.82}},"3":{"id":5862833,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":780,"def":0},"wall":{"for":39,"dom":57.28}},"4":{"id":5862834,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":780,"def":0},"wall":{"for":39,"dom":90.99}}}},"200338":{"id":200338,"war_id":115527,"zone_id":6,"is_rw":true,"is_as":false,"type":"tanks","start":1563515222,"det":1.1,"region":{"id":363,"name":"Gansu"},"city":{"id":442,"name":"Lanzhou"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":14,"allies":[],"ally_list":[],"points":59},"def":{"id":24,"allies":[],"ally_list":[],"points":7},"div":{"1":{"id":5862835,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":710,"def":50},"wall":{"for":14,"dom":57.68}},"2":{"id":5862836,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":720,"def":40},"wall":{"for":14,"dom":56.57}},"3":{"id":5862837,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":730,"def":30},"wall":{"for":14,"dom":61.55}},"4":{"id":5862838,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":740,"def":20},"wall":{"for":14,"dom":72.19}}}},"200370":{"id":200370,"war_id":115535,"zone_id":3,"is_rw":true,"is_as":false,"type":"tanks","start":1563515342,"det":1.06,"region":{"id":749,"name":"Lower Kartli"},"city":{"id":796,"name":"Akhaltsikhe"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":168,"allies":[],"ally_list":[],"points":9},"def":{"id":64,"allies":[],"ally_list":[],"points":13},"div":{"1":{"id":5862839,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":680,"def":40},"wall":{"for":168,"dom":100}},"2":{"id":5862840,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":120,"def":600},"wall":{"for":64,"dom":51.46}},"3":{"id":5862841,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":490,"def":230},"wall":{"for":168,"dom":52.4}},"4":{"id":5862842,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":720,"def":0},"wall":{"for":168,"dom":60.27}}}},"200382":{"id":200382,"war_id":110990,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563515342,"det":1,"region":{"id":626,"name":"Istria and Kvarner"},"city":{"id":666,"name":"Rijeka"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":61,"allies":[],"ally_list":[],"points":7},"def":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":4},"div":{"1":{"id":5862843,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":720,"def":0},"wall":{"for":61,"dom":60.97}},"2":{"id":5862844,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":720,"def":0},"wall":{"for":61,"dom":57.82}},"3":{"id":5862845,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":720,"def":0},"wall":{"for":61,"dom":50.8}},"4":{"id":5862846,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":720,"def":0},"wall":{"for":61,"dom":73.18}}}},"200353":{"id":200353,"war_id":102341,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563515403,"det":1.09,"region":{"id":116,"name":"Baja"},"city":{"id":277,"name":"Mexicali"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":55},"def":{"id":26,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862847,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":700,"def":0},"wall":{"for":43,"dom":56.77}},"2":{"id":5862848,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":700,"def":0},"wall":{"for":43,"dom":55.63}},"3":{"id":5862849,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":700,"def":0},"wall":{"for":43,"dom":57.77}},"4":{"id":5862850,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":700,"def":0},"wall":{"for":43,"dom":56.16}}}},"200354":{"id":200354,"war_id":113549,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563515463,"det":1,"region":{"id":742,"name":"Fujairah"},"city":{"id":766,"name":"Fujairah"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":56,"allies":[30,29],"ally_list":[{"id":30,"deployed":true},{"id":29,"deployed":true}],"points":35},"def":{"id":166,"allies":[58],"ally_list":[{"id":58,"deployed":true}],"points":20},"div":{"1":{"id":5862851,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":280,"def":400},"wall":{"for":56,"dom":100}},"2":{"id":5862852,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":380,"def":300},"wall":{"for":166,"dom":53.02}},"3":{"id":5862853,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":170,"def":510},"wall":{"for":166,"dom":52.63}},"4":{"id":5862854,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":80,"def":600},"wall":{"for":166,"dom":59.06}}}},"200369":{"id":200369,"war_id":112002,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563515463,"det":1.02,"region":{"id":758,"name":"North Central States"},"city":{"id":804,"name":"Abuja"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":27,"allies":[],"ally_list":[],"points":22},"def":{"id":170,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862855,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":680},"wall":{"for":170,"dom":100}},"2":{"id":5862856,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":650,"def":30},"wall":{"for":27,"dom":61.37}},"3":{"id":5862857,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":400,"def":280},"wall":{"for":27,"dom":52.02}},"4":{"id":5862858,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":680,"def":0},"wall":{"for":27,"dom":100}}}},"200379":{"id":200379,"war_id":115539,"zone_id":2,"is_rw":true,"is_as":false,"type":"tanks","start":1563515463,"det":1.81,"region":{"id":728,"name":"Makkah"},"city":{"id":788,"name":"Mecca"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":164,"allies":[],"ally_list":[],"points":10},"def":{"id":43,"allies":[],"ally_list":[],"points":1},"div":{"1":{"id":5862859,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":680},"wall":{"for":43,"dom":100}},"2":{"id":5862860,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":570,"def":110},"wall":{"for":164,"dom":61.7}},"3":{"id":5862861,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":570,"def":110},"wall":{"for":164,"dom":55.98}},"4":{"id":5862862,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":340,"def":340},"wall":{"for":164,"dom":60.01}}}},"200331":{"id":200331,"war_id":110137,"zone_id":7,"is_rw":false,"is_as":false,"type":"tanks","start":1563515582,"det":1.06,"region":{"id":530,"name":"Eastern Netherlands"},"city":{"id":153,"name":"Arnhem"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":35,"allies":[70,12,57,15],"ally_list":[{"id":70,"deployed":true},{"id":12,"deployed":true},{"id":57,"deployed":true},{"id":15,"deployed":true}],"points":54},"def":{"id":31,"allies":[58,53,41],"ally_list":[{"id":58,"deployed":true},{"id":53,"deployed":true},{"id":41,"deployed":true}],"points":23},"div":{"1":{"id":5862863,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":630,"def":10},"wall":{"for":35,"dom":50.58}},"2":{"id":5862864,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":630,"def":10},"wall":{"for":35,"dom":50.35}},"3":{"id":5862865,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":620},"wall":{"for":31,"dom":51.2}},"4":{"id":5862866,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":540,"def":100},"wall":{"for":35,"dom":67.32}}}},"200373":{"id":200373,"war_id":115348,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563515642,"det":1.16,"region":{"id":134,"name":"Volhynia"},"city":{"id":296,"name":"Rivne"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":81,"allies":[78,39,77,52,1,41],"ally_list":[{"id":78,"deployed":true},{"id":39,"deployed":true},{"id":77,"deployed":true},{"id":52,"deployed":true},{"id":1,"deployed":true},{"id":41,"deployed":true}],"points":22},"def":{"id":40,"allies":[49,45],"ally_list":[{"id":49,"deployed":true},{"id":45,"deployed":true}],"points":0},"div":{"1":{"id":5862867,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":610,"def":10},"wall":{"for":81,"dom":57.9}},"2":{"id":5862868,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":580,"def":40},"wall":{"for":81,"dom":56.13}},"3":{"id":5862869,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":570,"def":50},"wall":{"for":81,"dom":53.63}},"4":{"id":5862870,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":620,"def":0},"wall":{"for":81,"dom":82.01}}}},"200315":{"id":200315,"war_id":112466,"zone_id":9,"is_rw":false,"is_as":false,"type":"tanks","start":1563515702,"det":1,"region":{"id":747,"name":"Abkhazia"},"city":{"id":798,"name":"Sokhumi"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":41,"allies":[42,78,171,31,81,38],"ally_list":[{"id":42,"deployed":true},{"id":78,"deployed":true},{"id":171,"deployed":true},{"id":31,"deployed":true},{"id":81,"deployed":true},{"id":38,"deployed":true}],"points":23},"def":{"id":27,"allies":[],"ally_list":[],"points":87},"div":{"1":{"id":5862871,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":100,"def":500},"wall":{"for":27,"dom":57.42}},"2":{"id":5862872,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":100,"def":500},"wall":{"for":27,"dom":56.84}},"3":{"id":5862873,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":50,"def":550},"wall":{"for":27,"dom":65.35}},"4":{"id":5862874,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":130,"def":470},"wall":{"for":27,"dom":53.01}}}},"200351":{"id":200351,"war_id":115492,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563515703,"det":1,"region":{"id":259,"name":"Abruzzo"},"city":{"id":367,"name":"L'Aquila"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":59,"allies":[63],"ally_list":[{"id":63,"deployed":true}],"points":0},"def":{"id":14,"allies":[],"ally_list":[],"points":55},"div":{"1":{"id":5862875,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":600},"wall":{"for":14,"dom":53.14}},"2":{"id":5862876,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":600},"wall":{"for":14,"dom":50.62}},"3":{"id":5862877,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":600},"wall":{"for":14,"dom":57.01}},"4":{"id":5862878,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":100,"def":500},"wall":{"for":14,"dom":51.04}}}},"200374":{"id":200374,"war_id":114375,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563515703,"det":1,"region":{"id":132,"name":"Subcarpathia"},"city":{"id":293,"name":"Uzhhorod"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":13,"allies":[],"ally_list":[],"points":11},"def":{"id":40,"allies":[49,45],"ally_list":[{"id":49,"deployed":true},{"id":45,"deployed":true}],"points":11},"div":{"1":{"id":5862879,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":580},"wall":{"for":40,"dom":55.67}},"2":{"id":5862880,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":600},"wall":{"for":40,"dom":56.72}},"3":{"id":5862881,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":600,"def":0},"wall":{"for":13,"dom":56.59}},"4":{"id":5862882,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":580,"def":20},"wall":{"for":13,"dom":61.58}}}},"200401":{"id":200401,"war_id":102441,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563515411,"det":1.17,"region":{"id":731,"name":"Lower Egypt"},"city":{"id":777,"name":"Cairo"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":0},"def":{"id":165,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862883,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":640,"def":40},"wall":{"for":43,"dom":57.96}},"2":{"id":5862884,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":620,"def":60},"wall":{"for":43,"dom":57.3}},"3":{"id":5862885,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":650,"def":30},"wall":{"for":43,"dom":59.7}},"4":{"id":5862886,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":670,"def":10},"wall":{"for":43,"dom":58.49}}}},"200362":{"id":200362,"war_id":113654,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563515822,"det":1,"region":{"id":503,"name":"North West Province"},"city":{"id":548,"name":"Mmabatho"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":164,"allies":[],"ally_list":[],"points":3},"def":{"id":54,"allies":[50,80],"ally_list":[{"id":50,"deployed":true},{"id":80,"deployed":true}],"points":30},"div":{"11":{"id":5862887,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":560,"def":0},"wall":{"for":164,"dom":72.09}}}},"200402":{"id":200402,"war_id":92643,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563515591,"det":1.25,"region":{"id":729,"name":"Jizan"},"city":{"id":789,"name":"Jizan"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":0},"def":{"id":164,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862888,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":610,"def":10},"wall":{"for":43,"dom":60.47}},"2":{"id":5862889,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":600,"def":20},"wall":{"for":43,"dom":56.97}},"3":{"id":5862890,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":590,"def":30},"wall":{"for":43,"dom":61.33}},"4":{"id":5862891,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":620,"def":0},"wall":{"for":43,"dom":100}}}},"200345":{"id":200345,"war_id":114283,"zone_id":6,"is_rw":false,"is_as":false,"type":"tanks","start":1563515945,"det":1,"region":{"id":744,"name":"Tirana"},"city":{"id":793,"name":"Tirana"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":1},"def":{"id":167,"allies":[],"ally_list":[],"points":65},"div":{"1":{"id":5862892,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":520},"wall":{"for":167,"dom":100}},"2":{"id":5862893,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":400,"def":120},"wall":{"for":64,"dom":100}},"3":{"id":5862894,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":360,"def":160},"wall":{"for":167,"dom":64.04}},"4":{"id":5862895,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":510,"def":10},"wall":{"for":64,"dom":53.71}}}},"200332":{"id":200332,"war_id":115523,"zone_id":7,"is_rw":true,"is_as":false,"type":"tanks","start":1563516002,"det":1.87,"region":{"id":736,"name":"Abu Dhabi"},"city":{"id":772,"name":"Abu Dhabi"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":166,"allies":[],"ally_list":[],"points":72},"def":{"id":15,"allies":[],"ally_list":[],"points":5},"div":{"1":{"id":5862896,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":500},"wall":{"for":15,"dom":100}},"2":{"id":5862897,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":480,"def":20},"wall":{"for":166,"dom":82.28}},"3":{"id":5862898,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":500},"wall":{"for":15,"dom":100}},"4":{"id":5862899,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":240},"wall":{"for":166,"dom":61.07}}}},"200347":{"id":200347,"war_id":115509,"zone_id":6,"is_rw":false,"is_as":false,"type":"tanks","start":1563516002,"det":1,"region":{"id":658,"name":"Louna-Eesti"},"city":{"id":698,"name":"Tartu"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":11,"allies":[29],"ally_list":[{"id":29,"deployed":true}],"points":66},"def":{"id":70,"allies":[42,23,171,12,35,15,38],"ally_list":[{"id":42,"deployed":true},{"id":23,"deployed":true},{"id":171,"deployed":true},{"id":12,"deployed":true},{"id":35,"deployed":true},{"id":15,"deployed":true},{"id":38,"deployed":true}],"points":0},"div":{"1":{"id":5862900,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":500},"wall":{"for":70,"dom":100}},"2":{"id":5862901,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":460,"def":40},"wall":{"for":11,"dom":54.47}},"3":{"id":5862902,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":440,"def":60},"wall":{"for":11,"dom":50.05}},"4":{"id":5862903,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":480,"def":20},"wall":{"for":11,"dom":57.88}}}},"200383":{"id":200383,"war_id":106976,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563516062,"det":1.04,"region":{"id":57,"name":"Louisiana"},"city":{"id":186,"name":"Baton Rouge"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":9},"def":{"id":24,"allies":[],"ally_list":[],"points":2},"div":{"1":{"id":5862904,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":480},"wall":{"for":24,"dom":100}},"2":{"id":5862905,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":480},"wall":{"for":24,"dom":100}},"3":{"id":5862906,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":220,"def":260},"wall":{"for":64,"dom":67.53}},"4":{"id":5862907,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":100,"def":380},"wall":{"for":64,"dom":52.91}}}},"200403":{"id":200403,"war_id":105870,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563515771,"det":1.21,"region":{"id":720,"name":"Al Jawf"},"city":{"id":780,"name":"Sakaka"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":82,"allies":[75],"ally_list":[{"id":75,"deployed":true}],"points":0},"def":{"id":164,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862908,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":460,"def":100},"wall":{"for":82,"dom":100}},"2":{"id":5862909,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":510,"def":50},"wall":{"for":82,"dom":53.13}},"3":{"id":5862910,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":470,"def":90},"wall":{"for":82,"dom":50.68}},"4":{"id":5862911,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":560,"def":0},"wall":{"for":82,"dom":80.89}}}},"200316":{"id":200316,"war_id":104986,"zone_id":9,"is_rw":false,"is_as":false,"type":"tanks","start":1563516124,"det":1,"region":{"id":535,"name":"Far Eastern Russia"},"city":{"id":574,"name":"Khabarovsk"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":45,"allies":[65,40],"ally_list":[{"id":65,"deployed":true},{"id":40,"deployed":true}],"points":44},"def":{"id":41,"allies":[42,78,171,31,81,38],"ally_list":[{"id":42,"deployed":true},{"id":78,"deployed":true},{"id":171,"deployed":true},{"id":31,"deployed":true},{"id":81,"deployed":true},{"id":38,"deployed":true}],"points":66},"div":{"1":{"id":5862912,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":460},"wall":{"for":41,"dom":100}},"2":{"id":5862913,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":460},"wall":{"for":41,"dom":100}},"3":{"id":5862914,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":300,"def":160},"wall":{"for":45,"dom":100}},"4":{"id":5862915,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":460},"wall":{"for":41,"dom":100}}}},"200342":{"id":200342,"war_id":115530,"zone_id":6,"is_rw":true,"is_as":false,"type":"tanks","start":1563516125,"det":1.12,"region":{"id":706,"name":"Northern Cyprus"},"city":{"id":752,"name":"Famagusta"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":82,"allies":[],"ally_list":[],"points":33},"def":{"id":80,"allies":[],"ally_list":[],"points":33},"div":{"1":{"id":5862916,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":460},"wall":{"for":80,"dom":100}},"2":{"id":5862917,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":60,"def":400},"wall":{"for":82,"dom":55.31}},"3":{"id":5862918,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":120,"def":340},"wall":{"for":80,"dom":52.91}},"4":{"id":5862919,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":270,"def":190},"wall":{"for":82,"dom":55.27}}}},"200372":{"id":200372,"war_id":115514,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563516125,"det":1,"region":{"id":639,"name":"Raska"},"city":{"id":684,"name":"Kraljevo"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":17},"def":{"id":80,"allies":[50,54],"ally_list":[{"id":50,"deployed":true},{"id":54,"deployed":true}],"points":5},"div":{"1":{"id":5862920,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":460},"wall":{"for":80,"dom":100}},"2":{"id":5862921,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":460},"wall":{"for":80,"dom":100}},"3":{"id":5862922,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":200,"def":260},"wall":{"for":64,"dom":50.43}},"4":{"id":5862923,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":450,"def":10},"wall":{"for":64,"dom":78.05}}}},"200404":{"id":200404,"war_id":112182,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563515831,"det":1,"region":{"id":507,"name":"Central Thailand"},"city":{"id":550,"name":"Bangkok"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":82,"allies":[75],"ally_list":[{"id":75,"deployed":true}],"points":0},"def":{"id":59,"allies":[63],"ally_list":[{"id":63,"deployed":true}],"points":0},"div":{"1":{"id":5862924,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":70,"def":470},"wall":{"for":59,"dom":51.64}},"2":{"id":5862925,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":130,"def":410},"wall":{"for":59,"dom":53.77}},"3":{"id":5862926,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":70,"def":470},"wall":{"for":59,"dom":57.69}},"4":{"id":5862927,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":110,"def":430},"wall":{"for":59,"dom":55.36}}}},"200405":{"id":200405,"war_id":115355,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563515833,"det":1,"region":{"id":468,"name":"Nazareth North District"},"city":{"id":513,"name":"Nazareth"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":82,"allies":[75],"ally_list":[{"id":75,"deployed":true}],"points":0},"def":{"id":58,"allies":[31,166],"ally_list":[{"id":31,"deployed":true},{"id":166,"deployed":true}],"points":0},"div":{"1":{"id":5862928,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":410,"def":130},"wall":{"for":82,"dom":100}},"2":{"id":5862929,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":510,"def":30},"wall":{"for":82,"dom":100}},"3":{"id":5862930,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":130,"def":410},"wall":{"for":58,"dom":56.31}},"4":{"id":5862931,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":540},"wall":{"for":58,"dom":68.2}}}},"200344":{"id":200344,"war_id":115532,"zone_id":6,"is_rw":true,"is_as":false,"type":"tanks","start":1563516242,"det":1.15,"region":{"id":292,"name":"Sorlandet"},"city":{"id":386,"name":"Kristiansand"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":37,"allies":[],"ally_list":[],"points":49},"def":{"id":35,"allies":[],"ally_list":[],"points":17},"div":{"1":{"id":5862932,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":410,"def":10},"wall":{"for":37,"dom":64.4}},"2":{"id":5862933,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":420,"def":0},"wall":{"for":37,"dom":58.49}},"3":{"id":5862934,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":420,"def":0},"wall":{"for":37,"dom":57.39}},"4":{"id":5862935,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":340,"def":80},"wall":{"for":37,"dom":55.8}}}},"200406":{"id":200406,"war_id":115548,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563516011,"det":1.11,"region":{"id":755,"name":"Gegharkunik"},"city":{"id":802,"name":"Gavar"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":169,"allies":[],"ally_list":[],"points":0},"def":{"id":63,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862936,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":350,"def":130},"wall":{"for":63,"dom":94.66}},"2":{"id":5862937,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":420,"def":60},"wall":{"for":169,"dom":68}},"3":{"id":5862938,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":160,"def":320},"wall":{"for":63,"dom":56.77}},"4":{"id":5862939,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":450,"def":30},"wall":{"for":169,"dom":59.77}}}},"200407":{"id":200407,"war_id":115549,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563516014,"det":1.17,"region":{"id":726,"name":"Tabuk"},"city":{"id":786,"name":"Tabuk"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":164,"allies":[],"ally_list":[],"points":0},"def":{"id":82,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862940,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":480},"wall":{"for":82,"dom":100}},"2":{"id":5862941,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":300,"def":180},"wall":{"for":164,"dom":55.27}},"3":{"id":5862942,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":310,"def":170},"wall":{"for":164,"dom":51.68}},"4":{"id":5862943,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":80,"def":400},"wall":{"for":82,"dom":80.69}}}},"200306":{"id":200306,"war_id":106101,"zone_id":10,"is_rw":false,"is_as":false,"type":"tanks","start":1563516542,"det":1,"region":{"id":487,"name":"Chubu"},"city":{"id":531,"name":"Nagoya"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":45,"allies":[65,40],"ally_list":[{"id":65,"deployed":true},{"id":40,"deployed":true}],"points":56},"def":{"id":166,"allies":[58],"ally_list":[{"id":58,"deployed":true}],"points":65},"div":{"1":{"id":5862944,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":320},"wall":{"for":166,"dom":50}},"2":{"id":5862945,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":70,"def":250},"wall":{"for":45,"dom":100}},"3":{"id":5862946,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":240,"def":80},"wall":{"for":45,"dom":100}},"4":{"id":5862947,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":240,"def":80},"wall":{"for":45,"dom":70.13}}}},"200408":{"id":200408,"war_id":115550,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563516251,"det":1.01,"region":{"id":77,"name":"Pennsylvania"},"city":{"id":202,"name":"Harrisburg"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":24,"allies":[],"ally_list":[],"points":0},"def":{"id":63,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862948,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":370,"def":30},"wall":{"for":24,"dom":74.87}},"2":{"id":5862949,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":350,"def":50},"wall":{"for":24,"dom":72.08}},"3":{"id":5862950,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":350,"def":50},"wall":{"for":24,"dom":52.82}},"4":{"id":5862951,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":280,"def":120},"wall":{"for":63,"dom":52.17}}}},"200364":{"id":200364,"war_id":115056,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563516662,"det":1.03,"region":{"id":336,"name":"Deutschschweiz"},"city":{"id":143,"name":"Bern"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":27,"allies":[],"ally_list":[],"points":28},"def":{"id":30,"allies":[42,49,56,29],"ally_list":[{"id":42,"deployed":true},{"id":49,"deployed":true},{"id":56,"deployed":true},{"id":29,"deployed":true}],"points":5},"div":{"11":{"id":5862952,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":290},"wall":{"for":30,"dom":55.92}}}},"200409":{"id":200409,"war_id":115551,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563516371,"det":1.02,"region":{"id":481,"name":"Southwestern Iran"},"city":{"id":528,"name":"Ahvaz"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":56,"allies":[],"ally_list":[],"points":0},"def":{"id":15,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862953,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":360},"wall":{"for":15,"dom":50}},"2":{"id":5862954,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":340,"def":20},"wall":{"for":56,"dom":70.53}},"3":{"id":5862955,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":300,"def":60},"wall":{"for":56,"dom":94.97}},"4":{"id":5862956,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":350,"def":10},"wall":{"for":56,"dom":58.68}}}},"200325":{"id":200325,"war_id":113416,"zone_id":8,"is_rw":false,"is_as":false,"type":"aircraft","start":1563516783,"det":1,"region":{"id":138,"name":"Dnipro"},"city":{"id":234,"name":"Kiev"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":9},"def":{"id":40,"allies":[49,45],"ally_list":[{"id":49,"deployed":true},{"id":45,"deployed":true}],"points":79},"div":{"11":{"id":5862957,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":270},"wall":{"for":40,"dom":75.3}}}},"200335":{"id":200335,"war_id":115525,"zone_id":7,"is_rw":true,"is_as":false,"type":"tanks","start":1563516783,"det":1,"region":{"id":325,"name":"Gotaland"},"city":{"id":416,"name":"Orebro"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":38,"allies":[],"ally_list":[],"points":60},"def":{"id":71,"allies":[],"ally_list":[],"points":17},"div":{"1":{"id":5862958,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":270},"wall":{"for":71,"dom":50}},"2":{"id":5862959,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":270},"wall":{"for":71,"dom":100}},"3":{"id":5862960,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":210,"def":60},"wall":{"for":38,"dom":55.85}},"4":{"id":5862961,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":10},"wall":{"for":38,"dom":84.38}}}},"200355":{"id":200355,"war_id":111467,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563516783,"det":1,"region":{"id":463,"name":"Lesser Sunda Islands"},"city":{"id":507,"name":"Mataram"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":53,"allies":[78,31],"ally_list":[{"id":78,"deployed":true},{"id":31,"deployed":true}],"points":15},"def":{"id":49,"allies":[30,40,29],"ally_list":[{"id":30,"deployed":true},{"id":40,"deployed":true},{"id":29,"deployed":true}],"points":40},"div":{"1":{"id":5862962,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":270,"def":0},"wall":{"for":53,"dom":99.72}},"2":{"id":5862963,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":270},"wall":{"for":49,"dom":100}},"3":{"id":5862964,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":270},"wall":{"for":49,"dom":50}},"4":{"id":5862965,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":60,"def":210},"wall":{"for":49,"dom":53.7}}}},"200297":{"id":200297,"war_id":105806,"zone_id":10,"is_rw":false,"is_as":false,"type":"tanks","start":1563516842,"det":1,"region":{"id":183,"name":"Canary Islands"},"city":{"id":324,"name":"Las Palmas de Gran Canaria"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":53,"allies":[78,31],"ally_list":[{"id":78,"deployed":true},{"id":31,"deployed":true}],"points":58},"def":{"id":15,"allies":[70,35,52],"ally_list":[{"id":70,"deployed":true},{"id":35,"deployed":true},{"id":52,"deployed":true}],"points":63},"div":{"1":{"id":5862966,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":0},"wall":{"for":53,"dom":100}},"2":{"id":5862967,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":260},"wall":{"for":15,"dom":50}},"3":{"id":5862968,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":260},"wall":{"for":15,"dom":50}},"4":{"id":5862969,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":230},"wall":{"for":15,"dom":96.94}}}},"200313":{"id":200313,"war_id":115227,"zone_id":9,"is_rw":false,"is_as":false,"type":"tanks","start":1563516842,"det":1,"region":{"id":275,"name":"Trentino-South Tyrol"},"city":{"id":376,"name":"Trento"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":61,"allies":[],"ally_list":[],"points":38},"def":{"id":167,"allies":[],"ally_list":[],"points":72},"div":{"1":{"id":5862970,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":0},"wall":{"for":61,"dom":100}},"2":{"id":5862971,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":210,"def":50},"wall":{"for":167,"dom":59.23}},"3":{"id":5862972,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":190,"def":70},"wall":{"for":167,"dom":54.11}},"4":{"id":5862973,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":0},"wall":{"for":61,"dom":100}}}},"200384":{"id":200384,"war_id":115540,"zone_id":2,"is_rw":true,"is_as":false,"type":"tanks","start":1563516842,"det":1.06,"region":{"id":249,"name":"Hesse"},"city":{"id":358,"name":"Wiesbaden"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":12,"allies":[],"ally_list":[],"points":11},"def":{"id":81,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862974,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":0},"wall":{"for":12,"dom":54.34}},"2":{"id":5862975,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":0},"wall":{"for":12,"dom":57.01}},"3":{"id":5862976,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":0},"wall":{"for":12,"dom":50.55}},"4":{"id":5862977,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":260,"def":0},"wall":{"for":12,"dom":69}}}},"200410":{"id":200410,"war_id":115552,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563516852,"det":1,"region":{"id":197,"name":"Lower Normandy"},"city":{"id":330,"name":"Caen"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":11,"allies":[],"ally_list":[],"points":0},"def":{"id":54,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862978,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":250},"wall":{"for":54,"dom":50}},"2":{"id":5862979,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":230,"def":20},"wall":{"for":11,"dom":100}},"3":{"id":5862980,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":230,"def":20},"wall":{"for":11,"dom":100}},"4":{"id":5862981,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":250,"def":0},"wall":{"for":11,"dom":100}}}},"200322":{"id":200322,"war_id":115521,"zone_id":8,"is_rw":true,"is_as":false,"type":"aircraft","start":1563517262,"det":1.12,"region":{"id":126,"name":"Central Western Venezuela"},"city":{"id":284,"name":"Barquisimeto"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":28,"allies":[],"ally_list":[],"points":81},"def":{"id":43,"allies":[],"ally_list":[],"points":7},"div":{"11":{"id":5862982,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":190,"def":0},"wall":{"for":28,"dom":85.73}}}},"200337":{"id":200337,"war_id":111134,"zone_id":7,"is_rw":false,"is_as":false,"type":"tanks","start":1563517322,"det":1.04,"region":{"id":95,"name":"Ontario"},"city":{"id":19,"name":"Ottawa"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":24,"allies":[],"ally_list":[],"points":72},"def":{"id":23,"allies":[70],"ally_list":[{"id":70,"deployed":true}],"points":5},"div":{"1":{"id":5862983,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":180,"def":0},"wall":{"for":24,"dom":100}},"2":{"id":5862984,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":180,"def":0},"wall":{"for":24,"dom":57.64}},"3":{"id":5862985,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":170,"def":10},"wall":{"for":24,"dom":60.38}},"4":{"id":5862986,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":50,"def":130},"wall":{"for":24,"dom":85.96}}}},"200323":{"id":200323,"war_id":106205,"zone_id":8,"is_rw":false,"is_as":false,"type":"aircraft","start":1563517442,"det":1.02,"region":{"id":88,"name":"Wisconsin"},"city":{"id":200,"name":"Madison"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":54},"def":{"id":24,"allies":[],"ally_list":[],"points":34},"div":{"11":{"id":5862987,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":60,"def":100},"wall":{"for":24,"dom":53.15}}}},"200411":{"id":200411,"war_id":114627,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563517151,"det":1,"region":{"id":499,"name":"Gauteng"},"city":{"id":541,"name":"Pretoria"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":80,"allies":[50,54],"ally_list":[{"id":50,"deployed":true},{"id":54,"deployed":true}],"points":0},"def":{"id":164,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862988,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":200},"wall":{"for":164,"dom":50}},"2":{"id":5862989,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":20,"def":180},"wall":{"for":164,"dom":51}},"3":{"id":5862990,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":160,"def":40},"wall":{"for":164,"dom":56.24}},"4":{"id":5862991,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":200,"def":0},"wall":{"for":80,"dom":100}}}},"200357":{"id":200357,"war_id":104181,"zone_id":5,"is_rw":false,"is_as":false,"type":"tanks","start":1563517503,"det":1.03,"region":{"id":65,"name":"Montana"},"city":{"id":170,"name":"Helena"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":167,"allies":[],"ally_list":[],"points":18},"def":{"id":24,"allies":[],"ally_list":[],"points":37},"div":{"1":{"id":5862992,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":150},"wall":{"for":24,"dom":55.7}},"2":{"id":5862993,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":150},"wall":{"for":24,"dom":56.56}},"3":{"id":5862994,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":110,"def":40},"wall":{"for":167,"dom":69.23}},"4":{"id":5862995,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":100,"def":50},"wall":{"for":167,"dom":100}}}},"200376":{"id":200376,"war_id":115538,"zone_id":3,"is_rw":true,"is_as":false,"type":"tanks","start":1563517503,"det":1.06,"region":{"id":120,"name":"Gulf of Mexico"},"city":{"id":280,"name":"Veracruz"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":26,"allies":[],"ally_list":[],"points":22},"def":{"id":64,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5862996,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":150},"wall":{"for":64,"dom":50}},"2":{"id":5862997,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":150,"def":0},"wall":{"for":26,"dom":100}},"3":{"id":5862998,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":120},"wall":{"for":64,"dom":51.98}},"4":{"id":5862999,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":150,"def":0},"wall":{"for":26,"dom":100}}}},"200377":{"id":200377,"war_id":114902,"zone_id":3,"is_rw":false,"is_as":false,"type":"tanks","start":1563517503,"det":1.04,"region":{"id":343,"name":"Upper Austria"},"city":{"id":428,"name":"Linz"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":43,"allies":[69,67],"ally_list":[{"id":69,"deployed":true},{"id":67,"deployed":true}],"points":17},"def":{"id":33,"allies":[],"ally_list":[],"points":5},"div":{"1":{"id":5863000,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":130,"def":20},"wall":{"for":43,"dom":61.27}},"2":{"id":5863001,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":150,"def":0},"wall":{"for":43,"dom":56.87}},"3":{"id":5863002,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":150,"def":0},"wall":{"for":43,"dom":60.48}},"4":{"id":5863003,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":150,"def":0},"wall":{"for":43,"dom":100}}}},"200356":{"id":200356,"war_id":115534,"zone_id":5,"is_rw":true,"is_as":false,"type":"tanks","start":1563517682,"det":1.03,"region":{"id":703,"name":"Eastern Taiwan"},"city":{"id":763,"name":"Taitung"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":81,"allies":[],"ally_list":[],"points":52},"def":{"id":35,"allies":[],"ally_list":[],"points":3},"div":{"1":{"id":5863004,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":100,"def":20},"wall":{"for":81,"dom":56.04}},"2":{"id":5863005,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":110,"def":10},"wall":{"for":81,"dom":53.96}},"3":{"id":5863006,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":120},"wall":{"for":35,"dom":53.27}},"4":{"id":5863007,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":120},"wall":{"for":35,"dom":50.73}}}},"200333":{"id":200333,"war_id":105734,"zone_id":7,"is_rw":false,"is_as":false,"type":"tanks","start":1563517742,"det":1,"region":{"id":697,"name":"Eastern Macedonia"},"city":{"id":744,"name":"Stip"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":64,"allies":[83,76,75,74],"ally_list":[{"id":83,"deployed":true},{"id":76,"deployed":true},{"id":75,"deployed":true},{"id":74,"deployed":true}],"points":50},"def":{"id":63,"allies":[47,59],"ally_list":[{"id":47,"deployed":true},{"id":59,"deployed":true}],"points":27},"div":{"1":{"id":5863008,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":110},"wall":{"for":63,"dom":99.84}},"2":{"id":5863009,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":110},"wall":{"for":63,"dom":100}},"3":{"id":5863010,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":110},"wall":{"for":63,"dom":100}},"4":{"id":5863011,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":110},"wall":{"for":63,"dom":100}}}},"200326":{"id":200326,"war_id":115522,"zone_id":8,"is_rw":true,"is_as":false,"type":"aircraft","start":1563517802,"det":1.2,"region":{"id":717,"name":"Al Riyadh"},"city":{"id":764,"name":"Riyadh"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":164,"allies":[],"ally_list":[],"points":79},"def":{"id":67,"allies":[],"ally_list":[],"points":9},"div":{"11":{"id":5863012,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":100,"def":0},"wall":{"for":164,"dom":73.44}}}},"200334":{"id":200334,"war_id":115524,"zone_id":7,"is_rw":true,"is_as":false,"type":"tanks","start":1563517802,"det":1.11,"region":{"id":730,"name":"Sinai"},"city":{"id":778,"name":"Dahab"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":165,"allies":[],"ally_list":[],"points":60},"def":{"id":43,"allies":[],"ally_list":[],"points":17},"div":{"1":{"id":5863013,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":100},"wall":{"for":43,"dom":100}},"2":{"id":5863014,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":100},"wall":{"for":43,"dom":76.6}},"3":{"id":5863015,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":100},"wall":{"for":43,"dom":100}},"4":{"id":5863016,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":100},"wall":{"for":43,"dom":62.65}}}},"200363":{"id":200363,"war_id":113133,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563517982,"det":1,"region":{"id":759,"name":"South West States"},"city":{"id":805,"name":"Lagos"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":9,"allies":[171],"ally_list":[{"id":171,"deployed":true}],"points":19},"def":{"id":170,"allies":[],"ally_list":[],"points":14},"div":{"11":{"id":5863017,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":70},"wall":{"for":170,"dom":60.85}}}},"200385":{"id":200385,"war_id":115541,"zone_id":2,"is_rw":true,"is_as":false,"type":"tanks","start":1563518043,"det":1.06,"region":{"id":101,"name":"Saskatchewan"},"city":{"id":220,"name":"Regina"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":23,"allies":[],"ally_list":[],"points":11},"def":{"id":24,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5863018,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":60},"wall":{"for":24,"dom":50}},"2":{"id":5863019,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":60},"wall":{"for":24,"dom":50}},"3":{"id":5863020,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":60},"wall":{"for":24,"dom":50}},"4":{"id":5863021,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":40,"def":20},"wall":{"for":23,"dom":63.87}}}},"200327":{"id":200327,"war_id":107096,"zone_id":8,"is_rw":false,"is_as":false,"type":"aircraft","start":1563518102,"det":1,"region":{"id":657,"name":"Laane-Eesti"},"city":{"id":697,"name":"Parnu"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":71,"allies":[],"ally_list":[],"points":19},"def":{"id":40,"allies":[49,45],"ally_list":[{"id":49,"deployed":true},{"id":45,"deployed":true}],"points":69},"div":{"11":{"id":5863022,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":40},"wall":{"for":40,"dom":52.78}}}},"200389":{"id":200389,"war_id":115542,"zone_id":2,"is_rw":true,"is_as":false,"type":"tanks","start":1563518102,"det":1.16,"region":{"id":84,"name":"Vermont"},"city":{"id":209,"name":"Montpelier"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":24,"allies":[],"ally_list":[],"points":11},"def":{"id":44,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5863023,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":50},"wall":{"for":44,"dom":100}},"2":{"id":5863024,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":50},"wall":{"for":44,"dom":93.61}},"3":{"id":5863025,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":50},"wall":{"for":44,"dom":96.28}},"4":{"id":5863026,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":50},"wall":{"for":44,"dom":57.03}}}},"200412":{"id":200412,"war_id":115553,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563517871,"det":1.27,"region":{"id":684,"name":"Low Andes"},"city":{"id":731,"name":"Arequipa"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":77,"allies":[],"ally_list":[],"points":0},"def":{"id":64,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5863027,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":70,"def":10},"wall":{"for":77,"dom":100}},"2":{"id":5863028,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":50},"wall":{"for":77,"dom":100}},"3":{"id":5863029,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":80,"def":0},"wall":{"for":77,"dom":100}},"4":{"id":5863030,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":70,"def":10},"wall":{"for":77,"dom":99.6}}}},"200328":{"id":200328,"war_id":114112,"zone_id":8,"is_rw":false,"is_as":false,"type":"aircraft","start":1563518282,"det":1,"region":{"id":453,"name":"Kerala"},"city":{"id":499,"name":"Thiruvananthapuram"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":167,"allies":[],"ally_list":[],"points":8},"def":{"id":74,"allies":[64,75],"ally_list":[{"id":64,"deployed":true},{"id":75,"deployed":true}],"points":80},"div":{"11":{"id":5863031,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":20},"wall":{"for":74,"dom":55.07}}}},"200387":{"id":200387,"war_id":113343,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563518282,"det":1,"region":{"id":70,"name":"New Mexico"},"city":{"id":177,"name":"Santa Fe"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":24,"allies":[],"ally_list":[],"points":0},"def":{"id":59,"allies":[63],"ally_list":[{"id":63,"deployed":true}],"points":11},"div":{"1":{"id":5863032,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":20},"wall":{"for":59,"dom":100}},"2":{"id":5863033,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":20},"wall":{"for":59,"dom":100}},"3":{"id":5863034,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":20},"wall":{"for":59,"dom":100}},"4":{"id":5863035,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":20},"wall":{"for":59,"dom":100}}}},"200388":{"id":200388,"war_id":111331,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563518342,"det":1.06,"region":{"id":391,"name":"Inner Mongolia"},"city":{"id":463,"name":"Hohhot"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":24,"allies":[],"ally_list":[],"points":11},"def":{"id":14,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5863036,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":10},"wall":{"for":14,"dom":50}},"2":{"id":5863037,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":10},"wall":{"for":14,"dom":50}},"3":{"id":5863038,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":0},"wall":{"for":24,"dom":100}},"4":{"id":5863039,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":0},"wall":{"for":24,"dom":52.47}}}},"200386":{"id":200386,"war_id":113537,"zone_id":2,"is_rw":false,"is_as":false,"type":"tanks","start":1563518402,"det":1,"region":{"id":103,"name":"British Columbia"},"city":{"id":219,"name":"Victoria"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":24,"allies":[],"ally_list":[],"points":0},"def":{"id":80,"allies":[50,54],"ally_list":[{"id":50,"deployed":true},{"id":54,"deployed":true}],"points":11},"div":{"1":{"id":5863040,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":80,"dom":50}},"2":{"id":5863041,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":80,"dom":50}},"3":{"id":5863042,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":80,"dom":50}},"4":{"id":5863043,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":80,"dom":50}}}},"200413":{"id":200413,"war_id":115554,"zone_id":1,"is_rw":true,"is_as":false,"type":"tanks","start":1563518111,"det":1.13,"region":{"id":734,"name":"Upper Egypt"},"city":{"id":774,"name":"Luxor"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":165,"allies":[],"ally_list":[],"points":0},"def":{"id":64,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5863044,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":40},"wall":{"for":64,"dom":50}},"2":{"id":5863045,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":40},"wall":{"for":64,"dom":50}},"3":{"id":5863046,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":40},"wall":{"for":64,"dom":50}},"4":{"id":5863047,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":30,"def":10},"wall":{"for":165,"dom":100}}}},"200414":{"id":200414,"war_id":114970,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563518231,"det":1,"region":{"id":38,"name":"Maramures"},"city":{"id":253,"name":"Baia Mare"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":44,"allies":[],"ally_list":[],"points":0},"def":{"id":1,"allies":[81],"ally_list":[{"id":81,"deployed":true}],"points":0},"div":{"1":{"id":5863048,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":10},"wall":{"for":44,"dom":100}},"2":{"id":5863049,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":10},"wall":{"for":44,"dom":100}},"3":{"id":5863050,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":20},"wall":{"for":1,"dom":65.16}},"4":{"id":5863051,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":10,"def":10},"wall":{"for":1,"dom":74.36}}}},"200390":{"id":200390,"war_id":115543,"zone_id":2,"is_rw":true,"is_as":false,"type":"tanks","start":1563518583,"det":1,"region":{"id":655,"name":"Kirde-Eesti"},"city":{"id":695,"name":"Narva"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":70,"allies":[],"ally_list":[],"points":0},"def":{"id":31,"allies":[],"ally_list":[],"points":11},"div":{"1":{"id":5863052,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":31,"dom":50}},"2":{"id":5863053,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":31,"dom":50}},"3":{"id":5863054,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":31,"dom":50}},"4":{"id":5863055,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":31,"dom":50}}}},"200336":{"id":200336,"war_id":115526,"zone_id":7,"is_rw":true,"is_as":false,"type":"tanks","start":1563518645,"det":1,"region":{"id":544,"name":"Volga"},"city":{"id":577,"name":"Volgograd"},"is_dict":false,"is_lib":false,"war_type":"resistance","inv":{"id":41,"allies":[],"ally_list":[],"points":63},"def":{"id":56,"allies":[],"ally_list":[],"points":14},"div":{"1":{"id":5863056,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":56,"dom":50}},"2":{"id":5863057,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":56,"dom":50}},"3":{"id":5863058,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":56,"dom":50}},"4":{"id":5863059,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":56,"dom":50}}}},"200415":{"id":200415,"war_id":112092,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563518352,"det":1,"region":{"id":762,"name":"Western Cuba"},"city":{"id":809,"name":"Havana"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":171,"allies":[9,42,70,41],"ally_list":[{"id":9,"deployed":true},{"id":42,"deployed":true},{"id":70,"deployed":true},{"id":41,"deployed":true}],"points":0},"def":{"id":52,"allies":[72,81,15],"ally_list":[{"id":72,"deployed":true},{"id":81,"deployed":true},{"id":15,"deployed":true}],"points":0},"div":{"1":{"id":5863060,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":52,"dom":50}},"2":{"id":5863061,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":52,"dom":50}},"3":{"id":5863062,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":52,"dom":50}},"4":{"id":5863063,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":52,"dom":50}}}},"200365":{"id":200365,"war_id":106650,"zone_id":4,"is_rw":false,"is_as":false,"type":"aircraft","start":1563518703,"det":1,"region":{"id":532,"name":"Moscow and Central Russia"},"city":{"id":235,"name":"Moscow"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":56,"allies":[30,29],"ally_list":[{"id":30,"deployed":true},{"id":29,"deployed":true}],"points":11},"def":{"id":41,"allies":[42,78,171,31,81,38],"ally_list":[{"id":42,"deployed":true},{"id":78,"deployed":true},{"id":171,"deployed":true},{"id":31,"deployed":true},{"id":81,"deployed":true},{"id":38,"deployed":true}],"points":22},"div":{"11":{"id":5863064,"div":11,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":41,"dom":50}}}},"200416":{"id":200416,"war_id":114296,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563518412,"det":1,"region":{"id":651,"name":"Brcko District"},"city":{"id":690,"name":"Brcko"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":167,"allies":[],"ally_list":[],"points":0},"def":{"id":69,"allies":[43],"ally_list":[{"id":43,"deployed":true}],"points":0},"div":{"1":{"id":5863065,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":69,"dom":50}},"2":{"id":5863066,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":69,"dom":50}},"3":{"id":5863067,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":69,"dom":50}},"4":{"id":5863068,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":69,"dom":50}}}},"200417":{"id":200417,"war_id":111344,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563518414,"det":1,"region":{"id":162,"name":"Azores"},"city":{"id":311,"name":"Ponta Delgada"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":171,"allies":[9,42,70,41],"ally_list":[{"id":9,"deployed":true},{"id":42,"deployed":true},{"id":70,"deployed":true},{"id":41,"deployed":true}],"points":0},"def":{"id":53,"allies":[78,31],"ally_list":[{"id":78,"deployed":true},{"id":31,"deployed":true}],"points":0},"div":{"1":{"id":5863069,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":53,"dom":50}},"2":{"id":5863070,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":53,"dom":50}},"3":{"id":5863071,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":53,"dom":50}},"4":{"id":5863072,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":53,"dom":50}}}},"200418":{"id":200418,"war_id":102029,"zone_id":1,"is_rw":false,"is_as":false,"type":"tanks","start":1563518415,"det":1.56,"region":{"id":757,"name":"North East States"},"city":{"id":808,"name":"Maiduguri"},"is_dict":false,"is_lib":false,"war_type":"direct","inv":{"id":171,"allies":[9,42,70,41],"ally_list":[{"id":9,"deployed":true},{"id":42,"deployed":true},{"id":70,"deployed":true},{"id":41,"deployed":true}],"points":0},"def":{"id":170,"allies":[],"ally_list":[],"points":0},"div":{"1":{"id":5863073,"div":1,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":170,"dom":50}},"2":{"id":5863074,"div":2,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":170,"dom":50}},"3":{"id":5863075,"div":3,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":170,"dom":50}},"4":{"id":5863076,"div":4,"end":null,"division_end":false,"epic":0,"epic_type":0,"intensity_scale":"cold_war","co":{"inv":[],"def":[]},"dom_pts":{"inv":0,"def":0},"wall":{"for":170,"dom":50}}}}},"countries":{"167":{"id":167,"name":"Albania","allies":[],"is_empire":false,"cotd":200313},"27":{"id":27,"name":"Argentina","allies":[],"is_empire":false,"cotd":0},"169":{"id":169,"name":"Armenia","allies":[],"is_empire":false,"cotd":0},"50":{"id":50,"name":"Australia","allies":[54,80],"is_empire":false,"cotd":0},"33":{"id":33,"name":"Austria","allies":[],"is_empire":false,"cotd":0},"83":{"id":83,"name":"Belarus","allies":[64],"is_empire":false,"cotd":0},"32":{"id":32,"name":"Belgium","allies":[37,55],"is_empire":false,"cotd":0},"76":{"id":76,"name":"Bolivia","allies":[64],"is_empire":false,"cotd":0},"69":{"id":69,"name":"Bosnia and Herzegovina","allies":[43],"is_empire":false,"cotd":0},"9":{"id":9,"name":"Brazil","allies":[171],"is_empire":false,"cotd":200298},"42":{"id":42,"name":"Bulgaria","allies":[30,77,70,171,41],"is_empire":false,"cotd":0},"23":{"id":23,"name":"Canada","allies":[70],"is_empire":false,"cotd":0},"64":{"id":64,"name":"Chile","allies":[76,83,75,74],"is_empire":false,"cotd":0},"14":{"id":14,"name":"China","allies":[],"is_empire":false,"cotd":0},"78":{"id":78,"name":"Colombia","allies":[81,41,53],"is_empire":false,"cotd":0},"63":{"id":63,"name":"Croatia","allies":[59,47],"is_empire":false,"cotd":200346},"171":{"id":171,"name":"Cuba","allies":[70,41,9,42],"is_empire":false,"cotd":0},"82":{"id":82,"name":"Cyprus","allies":[75],"is_empire":false,"cotd":0},"34":{"id":34,"name":"Czech Republic","allies":[],"is_empire":false,"cotd":0},"55":{"id":55,"name":"Denmark","allies":[32],"is_empire":false,"cotd":0},"165":{"id":165,"name":"Egypt","allies":[],"is_empire":false,"cotd":0},"70":{"id":70,"name":"Estonia","allies":[171,15,38,23,42,35,12],"is_empire":false,"cotd":0},"39":{"id":39,"name":"Finland","allies":[81],"is_empire":false,"cotd":0},"11":{"id":11,"name":"France","allies":[29],"is_empire":false,"cotd":200347},"168":{"id":168,"name":"Georgia","allies":[],"is_empire":false,"cotd":0},"12":{"id":12,"name":"Germany","allies":[35,70],"is_empire":false,"cotd":0},"44":{"id":44,"name":"Greece","allies":[],"is_empire":false,"cotd":200414},"13":{"id":13,"name":"Hungary","allies":[],"is_empire":false,"cotd":0},"48":{"id":48,"name":"India","allies":[],"is_empire":false,"cotd":0},"49":{"id":49,"name":"Indonesia","allies":[40,29,30],"is_empire":false,"cotd":0},"56":{"id":56,"name":"Iran","allies":[30,29],"is_empire":false,"cotd":0},"54":{"id":54,"name":"Ireland","allies":[80,50],"is_empire":false,"cotd":0},"58":{"id":58,"name":"Israel","allies":[166,31],"is_empire":false,"cotd":0},"10":{"id":10,"name":"Italy","allies":[],"is_empire":false,"cotd":0},"45":{"id":45,"name":"Japan","allies":[65],"is_empire":false,"cotd":0},"71":{"id":71,"name":"Latvia","allies":[],"is_empire":false,"cotd":0},"72":{"id":72,"name":"Lithuania","allies":[65,52],"is_empire":false,"cotd":0},"66":{"id":66,"name":"Malaysia","allies":[],"is_empire":false,"cotd":0},"26":{"id":26,"name":"Mexico","allies":[],"is_empire":false,"cotd":0},"80":{"id":80,"name":"Montenegro","allies":[54,50],"is_empire":false,"cotd":0},"31":{"id":31,"name":"Netherlands","allies":[41,53,58],"is_empire":false,"cotd":0},"84":{"id":84,"name":"New Zealand","allies":[],"is_empire":false,"cotd":0},"170":{"id":170,"name":"Nigeria","allies":[],"is_empire":false,"cotd":0},"73":{"id":73,"name":"North Korea","allies":[],"is_empire":false,"cotd":0},"37":{"id":37,"name":"Norway","allies":[32],"is_empire":false,"cotd":0},"57":{"id":57,"name":"Pakistan","allies":[35],"is_empire":false,"cotd":0},"75":{"id":75,"name":"Paraguay","allies":[74,82,64],"is_empire":false,"cotd":0},"77":{"id":77,"name":"Peru","allies":[81,65,42],"is_empire":false,"cotd":0},"67":{"id":67,"name":"Philippines","allies":[43],"is_empire":false,"cotd":0},"35":{"id":35,"name":"Poland","allies":[57,15,70,12],"is_empire":false,"cotd":0},"53":{"id":53,"name":"Portugal","allies":[31,78],"is_empire":false,"cotd":0},"81":{"id":81,"name":"Republic of China (Taiwan)","allies":[78,1,77,52,39,41],"is_empire":false,"cotd":0},"79":{"id":79,"name":"Republic of Macedonia (FYROM)","allies":[],"is_empire":false,"cotd":0},"52":{"id":52,"name":"Republic of Moldova","allies":[81,15,72],"is_empire":false,"cotd":0},"1":{"id":1,"name":"Romania","allies":[81],"is_empire":false,"cotd":0},"41":{"id":41,"name":"Russia","allies":[38,31,171,78,42,81],"is_empire":false,"cotd":200316},"164":{"id":164,"name":"Saudi Arabia","allies":[],"is_empire":false,"cotd":0},"65":{"id":65,"name":"Serbia","allies":[45,72,77],"is_empire":false,"cotd":200346},"68":{"id":68,"name":"Singapore","allies":[],"is_empire":false,"cotd":0},"36":{"id":36,"name":"Slovakia","allies":[],"is_empire":false,"cotd":0},"61":{"id":61,"name":"Slovenia","allies":[],"is_empire":false,"cotd":0},"51":{"id":51,"name":"South Africa","allies":[],"is_empire":false,"cotd":0},"47":{"id":47,"name":"South Korea","allies":[63],"is_empire":false,"cotd":0},"15":{"id":15,"name":"Spain","allies":[35,70,52],"is_empire":false,"cotd":0},"38":{"id":38,"name":"Sweden","allies":[41,70],"is_empire":false,"cotd":0},"30":{"id":30,"name":"Switzerland","allies":[56,29,49,42],"is_empire":false,"cotd":0},"59":{"id":59,"name":"Thailand","allies":[63],"is_empire":false,"cotd":0},"43":{"id":43,"name":"Turkey","allies":[69,67],"is_empire":false,"cotd":200340},"40":{"id":40,"name":"Ukraine","allies":[49],"is_empire":false,"cotd":0},"166":{"id":166,"name":"United Arab Emirates","allies":[58],"is_empire":false,"cotd":0},"29":{"id":29,"name":"United Kingdom","allies":[56,49,11,30],"is_empire":false,"cotd":0},"74":{"id":74,"name":"Uruguay","allies":[75,64],"is_empire":false,"cotd":0},"24":{"id":24,"name":"USA","allies":[],"is_empire":false,"cotd":200388},"28":{"id":28,"name":"Venezuela","allies":[],"is_empire":false,"cotd":0}},"last_updated":1563518421,"citizen_contribution":[{"battle_id":200335,"zone_id":7,"division":4,"side_country_id":71,"damage":74067606,"kills":50},{"battle_id":200395,"zone_id":1,"division":4,"side_country_id":71,"damage":62188839,"kills":40}]} \ No newline at end of file diff --git a/debug/requests/2019-07-18_23-40-33_economy-inventory-items.json b/debug/requests/2019-07-18_23-40-33_economy-inventory-items.json new file mode 100644 index 0000000..c5bec4a --- /dev/null +++ b/debug/requests/2019-07-18_23-40-33_economy-inventory-items.json @@ -0,0 +1 @@ +{"inventoryItems":{"activeEnhancements":{"title":"Active Enhancements","id":"activeEnhancements","items":{"4_1_active":{"name":"House Q1","id":"4_1_active","industryId":4,"quality":1,"amount":1,"activable":0,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":[],"active":{"uses":168,"time_left":582294},"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/4/q1_55x55_stars.png","tooltip":"Houses increase your maximum Energy and your Energy recovery rate while you are located in your residence City.","token":"house_q1","attributes":{"durability":{"id":"durability","name":"Durability","type":"hours","value":168},"energyPool":{"id":"energyPool","name":"Energy","type":"energy","value":50},"overtimePoints":{"id":"overtimePoints","name":"Overtime Points","type":"hour","value":1},"recoveryRate":{"id":"recoveryRate","name":"Energy recovery","type":"6 minutes","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"activationCost":0,"activationMessage":"You must establish residence before activationg this house","maxQuality":5},"4_2_active":{"name":"House Q2","id":"4_2_active","industryId":4,"quality":2,"amount":1,"activable":0,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":[],"active":{"uses":168,"time_left":582295},"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/4/q2_55x55_stars.png","tooltip":"Houses increase your maximum Energy and your Energy recovery rate while you are located in your residence City.","token":"house_q2","attributes":{"durability":{"id":"durability","name":"Durability","type":"hours","value":168},"energyPool":{"id":"energyPool","name":"Energy","type":"energy","value":80},"overtimePoints":{"id":"overtimePoints","name":"Overtime Points","type":"hour","value":1},"recoveryRate":{"id":"recoveryRate","name":"Energy recovery","type":"6 minutes","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"activationCost":0,"activationMessage":"You must establish residence before activationg this house","maxQuality":5},"4_3_active":{"name":"House Q3","id":"4_3_active","industryId":4,"quality":3,"amount":1,"activable":0,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":[],"active":{"uses":168,"time_left":582296},"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/4/q3_55x55_stars.png","tooltip":"Houses increase your maximum Energy and your Energy recovery rate while you are located in your residence City.","token":"house_q3","attributes":{"durability":{"id":"durability","name":"Durability","type":"hours","value":168},"energyPool":{"id":"energyPool","name":"Energy","type":"energy","value":100},"overtimePoints":{"id":"overtimePoints","name":"Overtime Points","type":"hour","value":1},"recoveryRate":{"id":"recoveryRate","name":"Energy recovery","type":"6 minutes","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"activationCost":0,"activationMessage":"You must establish residence before activationg this house","maxQuality":5},"100_damageBoosters_5_225107043_active":{"name":"+50% Damage","id":"100_damageBoosters_5_225107043_active","industryId":100,"quality":5,"amount":"-","activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+50% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":5,"duration":28370,"fromInventory":true}},"active":{"time_left":28370},"icon":0,"tooltip":"50% Damage Booster for 7 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+50% Damage","type":"use","value":"50%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":28370}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":28370,"canActivateBooster":0,"remaining":28370},"100_powerPackBoosters_20_90889528_active":{"name":"Power Pack Booster","id":"100_powerPackBoosters_20_90889528_active","industryId":100,"quality":20,"amount":"-","activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":{"tooltip":"+20 Energy / 6 minutes","url":"/en/economy/activateBooster","params":{"type":"power_pack","quality":20,"duration":1727428,"fromInventory":true}},"active":{"time_left":1727428},"icon":0,"tooltip":"+20 Energy / 6 minutes for 19 days","token":"","attributes":{"energyRecovery":{"id":"energyRecovery","name":"+20 Energy / 6 minutes","type":" / 6 minutes","value":20},"duration":{"id":"duration","name":"Duration","type":"days","value":1727428}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":1,"type":"powerPackBoosters","duration":1727428,"canActivateBooster":0,"remaining":1727428},"100_blitzkriegPackBoosters_2000_84949724_active":{"name":"Blitzkrieg Pack Booster","id":"100_blitzkriegPackBoosters_2000_84949724_active","industryId":100,"quality":2000,"amount":"-","activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":{"tooltip":"+2000 Energy Building","url":"/en/economy/activateBooster","params":{"type":"blitzkrieg_pack","quality":2000,"duration":540793,"fromInventory":true}},"active":{"time_left":540793},"icon":0,"tooltip":"+2000 Energy Building for 6 days","token":"","attributes":{"energyPool":{"id":"energyPool","name":"Energy","type":"days","value":2000},"duration":{"id":"duration","name":"Duration","type":"days","value":6}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":1,"type":"blitzkriegPackBoosters","duration":540793,"canActivateBooster":0,"remaining":540793}}},"finalProducts":{"title":"Final products","id":"finalProducts","items":{"1_1":{"name":"Food Q1","id":"1_1","industryId":1,"quality":1,"amount":22413,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q1_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q1","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_2":{"name":"Food Q2","id":"1_2","industryId":1,"quality":2,"amount":41217,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q2_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q2","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":4}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_4":{"name":"Food Q4","id":"1_4","industryId":1,"quality":4,"amount":793,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q4_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q4","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":8}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_5":{"name":"Food Q5","id":"1_5","industryId":1,"quality":5,"amount":308,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q5_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q5","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":10}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_10":{"name":"Energy Bar","id":"1_10","industryId":1,"quality":10,"amount":130,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q10.png","tooltip":"Consuming Energy Bars recovers your Energy","token":"energy_bar","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":100}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0},"2_7":{"name":"Weapon Q7","id":"2_7","industryId":2,"quality":7,"amount":38,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/2/q7_55x55_stars.png","tooltip":"Using weapons improves your damage in battles","token":"weapon_q7","attributes":{"firePower":{"id":"firePower","name":"Fire power","type":"use","value":200},"durability":{"id":"durability","name":"Durability","type":"uses","value":10}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"used":{"durability":{"id":"durability","name":"Durability","type":"uses","value":3}},"maxQuality":7},"4_100":{"name":"Overtime Points","id":"4_100","industryId":4,"quality":100,"amount":4,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"/images/modules/misc/overtime_points_55x55.png","tooltip":"Used for working overtime","token":"house_q100","attributes":{"info":{"id":"info","name":"Receive one Overtime Point every hour for each active house you own","type":"hours","value":0}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0},"100_damageBoosters_5_86400":{"name":"+50% Damage","id":"100_damageBoosters_5_86400","industryId":100,"quality":5,"amount":53,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+50% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":5,"duration":86400,"fromInventory":true}},"active":0,"icon":0,"tooltip":"50% Damage Booster for 24 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+50% Damage","type":"use","value":"50%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":86400}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":86400,"canActivateBooster":1},"100_damageBoosters_5_28800":{"name":"+50% Damage","id":"100_damageBoosters_5_28800","industryId":100,"quality":5,"amount":62,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+50% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":5,"duration":28800,"fromInventory":true}},"active":0,"icon":0,"tooltip":"50% Damage Booster for 8 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+50% Damage","type":"use","value":"50%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":28800}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":28800,"canActivateBooster":1},"100_damageBoosters_10_86400":{"name":"+100% Damage","id":"100_damageBoosters_10_86400","industryId":100,"quality":10,"amount":5,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+100% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":10,"duration":86400,"fromInventory":true}},"active":0,"icon":0,"tooltip":"100% Damage Booster for 24 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+100% Damage","type":"use","value":"100%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":86400}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":86400,"canActivateBooster":1},"100_damageBoosters_10_28800":{"name":"+100% Damage","id":"100_damageBoosters_10_28800","industryId":100,"quality":10,"amount":14,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+100% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":10,"duration":28800,"fromInventory":true}},"active":0,"icon":0,"tooltip":"100% Damage Booster for 8 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+100% Damage","type":"use","value":"100%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":28800}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":28800,"canActivateBooster":1},"100_damageBoosters_10_7200":{"name":"+100% Damage","id":"100_damageBoosters_10_7200","industryId":100,"quality":10,"amount":5,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+100% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":10,"duration":7200,"fromInventory":true}},"active":0,"icon":0,"tooltip":"100% Damage Booster for 2 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+100% Damage","type":"use","value":"100%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":7200}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":7200,"canActivateBooster":1},"100_speedBoosters_2_600":{"name":"x5 Damage Accelerator","id":"100_speedBoosters_2_600","industryId":100,"quality":2,"amount":70,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"x5 Damage Accelerator","url":"/en/economy/activateBooster","params":{"type":"speed","quality":2,"duration":600,"fromInventory":true}},"active":0,"icon":0,"tooltip":"x5 Damage Accelerator for 10 minutes","token":"","attributes":{"damageAcceleration":{"id":"damageAcceleration","name":"Hit 5 times faster","type":"use","value":"x5"},"duration":{"id":"duration","name":"Duration","type":"minutes","value":600}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"speedBoosters","duration":600,"canActivateBooster":1},"100_catchupBoosters_30_60":{"name":"Ghost Booster","id":"100_catchupBoosters_30_60","industryId":100,"quality":30,"amount":150,"activable":1,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":1,"activationData":{"tooltip":"Ghost Booster availability","url":"/en/military/fight-activateBooster","params":{"type":"catchup","quality":30,"duration":60,"fromInventory":true}},"active":0,"icon":0,"tooltip":"30% Ghost Booster for 1 minute","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+30% Damage","type":"use","value":"30%"},"duration":{"id":"duration","name":"Duration","type":"minute","value":1}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"catchupBoosters","duration":60,"canActivateBooster":1}}},"rawMaterials":{"title":"Raw materials","id":"rawMaterials","items":{"7_1":{"name":"Food Raw Materials","id":"7_1","industryId":7,"quality":1,"amount":588,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/7/default.png","tooltip":"Raw material needed to produce food
One raw material occupies 100 storage spaces","token":"raw_food","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":13.71},"7_1_partial":{"name":"Food Raw Materials (Under Construction)","id":"7_1_partial","industryId":7,"quality":"1_partial","amount":"13.71%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/7/default.png","tooltip":"Raw material needed to produce food
One raw material occupies 100 storage spaces","token":"raw_food","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0},"12_1":{"name":"Weapon Raw Materials","id":"12_1","industryId":12,"quality":1,"amount":421,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/12/default.png","tooltip":"Raw material needed to produce weapons
One raw material occupies 100 storage spaces","token":"raw_weapon","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":77.86},"12_1_partial":{"name":"Weapon Raw Materials (Under Construction)","id":"12_1_partial","industryId":12,"quality":"1_partial","amount":"77.86%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/12/default.png","tooltip":"Raw material needed to produce weapons
One raw material occupies 100 storage spaces","token":"raw_weapon","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0},"17_1":{"name":"House Raw Materials","id":"17_1","industryId":17,"quality":1,"amount":2,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/17/default.png","tooltip":"Raw material needed to produce houses
One raw material occupies 100 storage spaces","token":"sand_q1","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":79.45},"17_1_partial":{"name":"House Raw Materials (Under Construction)","id":"17_1_partial","industryId":17,"quality":"1_partial","amount":"79.45%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/17/default.png","tooltip":"Raw material needed to produce houses
One raw material occupies 100 storage spaces","token":"sand_q1","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0},"24_1":{"name":"Aircraft Weapons Raw Materials","id":"24_1","industryId":24,"quality":1,"amount":0,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/24/default.png","tooltip":"Raw material needed to produce Air-to-Air Missiles
One raw material occupies 100 storage spaces","token":"magnesium_q1","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":29},"24_1_partial":{"name":"Aircraft Weapons Raw Materials (Under Construction)","id":"24_1_partial","industryId":24,"quality":"1_partial","amount":"29%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/24/default.png","tooltip":"Raw material needed to produce Air-to-Air Missiles
One raw material occupies 100 storage spaces","token":"magnesium_q1","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0}}}},"inventoryStatus":{"totalStorage":1796000,"usedStorage":165873,"color":"green"}} \ No newline at end of file diff --git a/debug/requests/2019-07-18_23-40-33_economy-mycompanies.html b/debug/requests/2019-07-18_23-40-33_economy-mycompanies.html new file mode 100644 index 0000000..d2b1225 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-33_economy-mycompanies.html @@ -0,0 +1,8403 @@ + + + + + + + + + + + + + + + +eRepublik + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + +
+ + + +
+ +
+
+
+ +

Are your sure you want to do this?

+
+ +
+
+ +
+ +
+ Close +
+

Report content

+
+
+
+
+
+
+
+ + + +
+
+ Close +
+
+
+
+

Error!

+

{{settings.msgs}}

+
+
+

Daily Order completed

+
+
+
+
+ + + +
+
+ +{{daily_order.bparts}} + Different Bazooka parts +
+
+ +{{daily_order.ebs}} + Energy Bar +
+ +
+
+
+
+
+
+
+ + Get Reward + + + Close + +
+
+
+
+
+ + + + + + + +
+
+ + + +
+

My job

+ + +
+
+ + +
+ Energy + -10 +
+ +
+ Gross Salary + +{{data.salary || 'N/A'}} {{data.currency || 'N/A'}} +
+ +
+ Work Tax + -{{data.tax || 'N/A'}} {{data.currency || 'N/A'}} +
+ + + Work + + +
+
+
+ +
+ You are unemployed +
+
+ + + Get a job + +
+
+
+ +
+ + x {{data.overTime.points}} +
+ Overtime Points needed + -24 +
+
+ +
+ Energy + -10 + -100 +
+ +
+ Salary earned today + {{data.overTime.salary}} {{data.currency}} +
+ + + Nightshift Work + Work overtime + + Receive 1 Overtime Point every hour from each active House. + Work for 10 Energy in 55:30 +
+
+ + +
+ +
+

+ My companies How to manage your companies +

+
+ +
+ +
+ Work as Manager + +
+
+ Employees + + + +
+
+ Raw materials +
+
+ Final products +
+
+
+ Select a company to sell or dissolve + Close +
+
+ Select a company to upgrade or downgrade + Close +
+
+
+
+ + From AF with Love W + (88) + + +  + + + Zahedan, Sistan and Baluchistan, Iran +
+ +
+
+
+ + From AF with Love F + (34) + + +  + + + Semnan, Semnan, Iran +
+ +
+
+
+ + From AF With Love H + (31) + + +  + + + Tallahassee, Florida, USA +
+ +
+
+
+ + Minimums 2 reizes diena + (0) + + +  + + + Riga, Vidzeme, Latvia +
+ +
+
+
+ + From AF with Love H + (0) + + +  + + + Olympia, Washington, USA +
+ +
+
+
+ + Unassigned companies + (0) + + + + +     Create new company + + + +
+ +
+
+
+
+ Work Tax + 0 + LVL +
+
+ Energy + 0 +
+
+ Employees assigned + 0/75 +
+
+ Raw materials +
+ + + 0 +
+
+ + + 0 +
+
+ + + 0 +
+
+ + + 0 +
+
+ + Start production + +
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+

List of eRepublik shortcuts

+
    +
  • AlertsShift + A
  • +
  • Military campaignsShift + C
  • +
  • Military unitShift + M
  • +
  • My placesShift + L
  • +
  • New MessageShift + N
  • +
  • StorageShift + S
  • +
  • Top newsShift + T
  • +
  • World mapShift + W
  • +
  • ResidenceShift + G
  • +
+ (press ESC to close) +
+
+ + diff --git a/debug/requests/2019-07-18_23-40-34_economy-exchange-retrieve.json b/debug/requests/2019-07-18_23-40-34_economy-exchange-retrieve.json new file mode 100644 index 0000000..60e606b --- /dev/null +++ b/debug/requests/2019-07-18_23-40-34_economy-exchange-retrieve.json @@ -0,0 +1 @@ +{"error":false,"message":"","ecash":{"id":951206,"value":551051.725703,"citizen_id":1620414,"currencyId":1,"currency":"LVL","currency_icon":"//www.erepublik.net/images/flags_png/S/Latvia.png"},"gold":{"id":951207,"value":3745.685,"citizen_id":1620414,"currencyId":62,"currency":"GOLD","currency_icon":"//www.erepublik.net/images/modules/_icons/gold_icon.png"},"page":0,"currencyId":62,"essentials":"\n\n\n\n\n\n\n","create_form":"\t\n\t\t\t\n\t\t\tAmount:\n\t\t\t\n\t\t\t\t\n\t\t\t\tLVL\n\t\t\t\tLVL\n\t\t\t\n\t\t\tExchange rate:\n\t\t\t\n\t\t\t\t1\n\t\t\t\tLVL\n\t\t\t\tLVL\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t\n\t\t\t\tGOLD\n\t\t\t\tGOLD\n\t\t\t\n\t\t\tSave\n\t\t","buy_mode":"\n\n\n \n\t\t\t\n \n \n \n \n \n \n\t\t\n\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\n\t\t\n\t\t\n\t
All offers
\n Citizen\n \n Amount\n \n Rate\n \n Buy:\n
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tp.e.d.r.a.m\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t410.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t610.650\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tktiniatros21\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t5.50\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t610.665\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tartemis matsas\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t6.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t610.665\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tEnrico Sodomico\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t1.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t611.999\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tRems.\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t75.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t612.000\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tArmat0re\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t20.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t613.000\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tMast3R.B0Y\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t72.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t613.000\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tgustavo35\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t15.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t613.990\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tatalanta BG\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t10.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t613.990\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tAminta Makedonecot\n\t\t\t\t\n\t\t\t\n \n\t\t\t\n\t\t\t\t6.00\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\n\t\t\t\n\t\t\t\t1\n\t\t\t\tGOLD\n\t\t\t\t\n\t\t\t=\n\t\t\t\n\t\t\t\t614.000\n\t\t\t\tLVL\n\t\t\n\t\t\t\n\t\t\t\n\t\t
"} \ No newline at end of file diff --git a/debug/requests/2019-07-18_23-40-35_main-weekly-challenge-data.json b/debug/requests/2019-07-18_23-40-35_main-weekly-challenge-data.json new file mode 100644 index 0000000..a3f5161 --- /dev/null +++ b/debug/requests/2019-07-18_23-40-35_main-weekly-challenge-data.json @@ -0,0 +1 @@ +{"error":false,"enabled":true,"type":{"anniversary":false,"flavorPacks":false,"springChallenge":false,"summerChallenge":false,"halloweenChallenge":false},"timeLeft":346765,"nextReward":{"maxReward":false,"type":"icon_energy_booster","text":"+1 Energy recovery until the end of Day 4,262"},"maxRewardId":0,"player":{"avatar":"//cdnt.erepublik.net/7efiav4XZ4SMvXAtgEk1NciUmAg=/55x55/smart/avatars/Citizens/2009/07/08/4b57b9ebb0232f0d6c3f6f2c21b8ab95.jpg?c022b6df6f643263dba839cb35b7a9ab","name":"inpoc1","prestigePoints":14170},"progress":0.9141935483871,"rewards":{"normal":[{"id":59,"collectedBefore":58,"percent":0.74193548387097,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":60,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":61,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":62,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":63,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":64,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":65,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":66,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":67,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":68,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_booster"},{"id":69,"collectedBefore":0,"percent":0.016129032258065,"label":"You have already collected this reward","tooltip":"","status":"rewarded","icon":"energy_bars"},{"id":70,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 14,250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","tooltip":"Reach 14250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","status":"","icon":"energy_booster"},{"id":71,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 14,500 Prestige Points to unlock the following reward: 10 Energy Bars","tooltip":"Reach 14500 Prestige Points to unlock the following reward: 10 Energy Bars","status":"","icon":"energy_bars"},{"id":72,"collectedBefore":0,"percent":0.032258064516129,"label":"Reach 15,000 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","tooltip":"Reach 15000 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","status":"","icon":"energy_booster"},{"id":73,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 15,250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","tooltip":"Reach 15250 Prestige Points to unlock the following reward: +1 Energy recovery until the end of Day 4,262","status":"","icon":"energy_booster"},{"id":74,"collectedBefore":0,"percent":0.016129032258065,"label":"Reach 15,500 Prestige Points to unlock the following reward: 15 Energy Bars","tooltip":"Reach 15500 Prestige Points to unlock the following reward: 15 Energy Bars","status":"","icon":"energy_bars"}],"extra":[]}} \ No newline at end of file diff --git a/debug/requests/2019-07-18_23-40-56_economy-inventory-items.json b/debug/requests/2019-07-18_23-40-56_economy-inventory-items.json new file mode 100644 index 0000000..a4887ed --- /dev/null +++ b/debug/requests/2019-07-18_23-40-56_economy-inventory-items.json @@ -0,0 +1 @@ +{"inventoryItems":{"activeEnhancements":{"title":"Active Enhancements","id":"activeEnhancements","items":{"4_1_active":{"name":"House Q1","id":"4_1_active","industryId":4,"quality":1,"amount":1,"activable":0,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":[],"active":{"uses":168,"time_left":582271},"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/4/q1_55x55_stars.png","tooltip":"Houses increase your maximum Energy and your Energy recovery rate while you are located in your residence City.","token":"house_q1","attributes":{"durability":{"id":"durability","name":"Durability","type":"hours","value":168},"energyPool":{"id":"energyPool","name":"Energy","type":"energy","value":50},"overtimePoints":{"id":"overtimePoints","name":"Overtime Points","type":"hour","value":1},"recoveryRate":{"id":"recoveryRate","name":"Energy recovery","type":"6 minutes","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"activationCost":0,"activationMessage":"You must establish residence before activationg this house","maxQuality":5},"4_2_active":{"name":"House Q2","id":"4_2_active","industryId":4,"quality":2,"amount":1,"activable":0,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":[],"active":{"uses":168,"time_left":582272},"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/4/q2_55x55_stars.png","tooltip":"Houses increase your maximum Energy and your Energy recovery rate while you are located in your residence City.","token":"house_q2","attributes":{"durability":{"id":"durability","name":"Durability","type":"hours","value":168},"energyPool":{"id":"energyPool","name":"Energy","type":"energy","value":80},"overtimePoints":{"id":"overtimePoints","name":"Overtime Points","type":"hour","value":1},"recoveryRate":{"id":"recoveryRate","name":"Energy recovery","type":"6 minutes","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"activationCost":0,"activationMessage":"You must establish residence before activationg this house","maxQuality":5},"4_3_active":{"name":"House Q3","id":"4_3_active","industryId":4,"quality":3,"amount":1,"activable":0,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":[],"active":{"uses":168,"time_left":582273},"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/4/q3_55x55_stars.png","tooltip":"Houses increase your maximum Energy and your Energy recovery rate while you are located in your residence City.","token":"house_q3","attributes":{"durability":{"id":"durability","name":"Durability","type":"hours","value":168},"energyPool":{"id":"energyPool","name":"Energy","type":"energy","value":100},"overtimePoints":{"id":"overtimePoints","name":"Overtime Points","type":"hour","value":1},"recoveryRate":{"id":"recoveryRate","name":"Energy recovery","type":"6 minutes","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"activationCost":0,"activationMessage":"You must establish residence before activationg this house","maxQuality":5},"100_damageBoosters_5_225107043_active":{"name":"+50% Damage","id":"100_damageBoosters_5_225107043_active","industryId":100,"quality":5,"amount":"-","activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+50% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":5,"duration":28347,"fromInventory":true}},"active":{"time_left":28347},"icon":0,"tooltip":"50% Damage Booster for 7 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+50% Damage","type":"use","value":"50%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":28347}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":28347,"canActivateBooster":0,"remaining":28347},"100_powerPackBoosters_20_90889528_active":{"name":"Power Pack Booster","id":"100_powerPackBoosters_20_90889528_active","industryId":100,"quality":20,"amount":"-","activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":{"tooltip":"+20 Energy / 6 minutes","url":"/en/economy/activateBooster","params":{"type":"power_pack","quality":20,"duration":1727405,"fromInventory":true}},"active":{"time_left":1727405},"icon":0,"tooltip":"+20 Energy / 6 minutes for 19 days","token":"","attributes":{"energyRecovery":{"id":"energyRecovery","name":"+20 Energy / 6 minutes","type":" / 6 minutes","value":20},"duration":{"id":"duration","name":"Duration","type":"days","value":1727405}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":1,"type":"powerPackBoosters","duration":1727405,"canActivateBooster":0,"remaining":1727405},"100_blitzkriegPackBoosters_2000_84949724_active":{"name":"Blitzkrieg Pack Booster","id":"100_blitzkriegPackBoosters_2000_84949724_active","industryId":100,"quality":2000,"amount":"-","activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":0,"activationData":{"tooltip":"+2000 Energy Building","url":"/en/economy/activateBooster","params":{"type":"blitzkrieg_pack","quality":2000,"duration":540770,"fromInventory":true}},"active":{"time_left":540770},"icon":0,"tooltip":"+2000 Energy Building for 6 days","token":"","attributes":{"energyPool":{"id":"energyPool","name":"Energy","type":"days","value":2000},"duration":{"id":"duration","name":"Duration","type":"days","value":6}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":1,"type":"blitzkriegPackBoosters","duration":540770,"canActivateBooster":0,"remaining":540770}}},"finalProducts":{"title":"Final products","id":"finalProducts","items":{"1_1":{"name":"Food Q1","id":"1_1","industryId":1,"quality":1,"amount":22413,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q1_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q1","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":2}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_2":{"name":"Food Q2","id":"1_2","industryId":1,"quality":2,"amount":41217,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q2_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q2","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":4}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_4":{"name":"Food Q4","id":"1_4","industryId":1,"quality":4,"amount":793,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q4_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q4","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":8}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_5":{"name":"Food Q5","id":"1_5","industryId":1,"quality":5,"amount":308,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q5_55x55_stars.png","tooltip":"Consuming food recovers your Energy","token":"food_q5","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":10}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"maxQuality":7},"1_10":{"name":"Energy Bar","id":"1_10","industryId":1,"quality":10,"amount":130,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/1/q10.png","tooltip":"Consuming Energy Bars recovers your Energy","token":"energy_bar","attributes":{"energyRestore":{"id":"energyRestore","name":"Energy restore","type":"use","value":100}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0},"2_7":{"name":"Weapon Q7","id":"2_7","industryId":2,"quality":7,"amount":38,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/2/q7_55x55_stars.png","tooltip":"Using weapons improves your damage in battles","token":"weapon_q7","attributes":{"firePower":{"id":"firePower","name":"Fire power","type":"use","value":200},"durability":{"id":"durability","name":"Durability","type":"uses","value":10}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"used":{"durability":{"id":"durability","name":"Durability","type":"uses","value":3}},"maxQuality":7},"4_100":{"name":"Overtime Points","id":"4_100","industryId":4,"quality":100,"amount":4,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"/images/modules/misc/overtime_points_55x55.png","tooltip":"Used for working overtime","token":"house_q100","attributes":{"info":{"id":"info","name":"Receive one Overtime Point every hour for each active house you own","type":"hours","value":0}},"isRaw":0,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0},"100_damageBoosters_5_86400":{"name":"+50% Damage","id":"100_damageBoosters_5_86400","industryId":100,"quality":5,"amount":53,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+50% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":5,"duration":86400,"fromInventory":true}},"active":0,"icon":0,"tooltip":"50% Damage Booster for 24 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+50% Damage","type":"use","value":"50%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":86400}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":86400,"canActivateBooster":1},"100_damageBoosters_5_28800":{"name":"+50% Damage","id":"100_damageBoosters_5_28800","industryId":100,"quality":5,"amount":62,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+50% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":5,"duration":28800,"fromInventory":true}},"active":0,"icon":0,"tooltip":"50% Damage Booster for 8 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+50% Damage","type":"use","value":"50%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":28800}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":28800,"canActivateBooster":1},"100_damageBoosters_10_86400":{"name":"+100% Damage","id":"100_damageBoosters_10_86400","industryId":100,"quality":10,"amount":5,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+100% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":10,"duration":86400,"fromInventory":true}},"active":0,"icon":0,"tooltip":"100% Damage Booster for 24 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+100% Damage","type":"use","value":"100%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":86400}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":86400,"canActivateBooster":1},"100_damageBoosters_10_28800":{"name":"+100% Damage","id":"100_damageBoosters_10_28800","industryId":100,"quality":10,"amount":14,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+100% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":10,"duration":28800,"fromInventory":true}},"active":0,"icon":0,"tooltip":"100% Damage Booster for 8 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+100% Damage","type":"use","value":"100%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":28800}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":28800,"canActivateBooster":1},"100_damageBoosters_10_7200":{"name":"+100% Damage","id":"100_damageBoosters_10_7200","industryId":100,"quality":10,"amount":5,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"+100% Damage","url":"/en/economy/activateBooster","params":{"type":"damage","quality":10,"duration":7200,"fromInventory":true}},"active":0,"icon":0,"tooltip":"100% Damage Booster for 2 hours","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+100% Damage","type":"use","value":"100%"},"duration":{"id":"duration","name":"Duration","type":"hours","value":7200}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"damageBoosters","duration":7200,"canActivateBooster":1},"100_speedBoosters_2_600":{"name":"x5 Damage Accelerator","id":"100_speedBoosters_2_600","industryId":100,"quality":2,"amount":70,"activable":1,"deactivable":0,"activableFromInventory":1,"activableFromBattlefield":1,"activationData":{"tooltip":"x5 Damage Accelerator","url":"/en/economy/activateBooster","params":{"type":"speed","quality":2,"duration":600,"fromInventory":true}},"active":0,"icon":0,"tooltip":"x5 Damage Accelerator for 10 minutes","token":"","attributes":{"damageAcceleration":{"id":"damageAcceleration","name":"Hit 5 times faster","type":"use","value":"x5"},"duration":{"id":"duration","name":"Duration","type":"minutes","value":600}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"speedBoosters","duration":600,"canActivateBooster":1},"100_catchupBoosters_30_60":{"name":"Ghost Booster","id":"100_catchupBoosters_30_60","industryId":100,"quality":30,"amount":150,"activable":1,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":1,"activationData":{"tooltip":"Ghost Booster availability","url":"/en/military/fight-activateBooster","params":{"type":"catchup","quality":30,"duration":60,"fromInventory":true}},"active":0,"icon":0,"tooltip":"30% Ghost Booster for 1 minute","token":"","attributes":{"damageBoost":{"id":"damageBoost","name":"+30% Damage","type":"use","value":"30%"},"duration":{"id":"duration","name":"Duration","type":"minute","value":1}},"isRaw":0,"isPartial":0,"isBooster":1,"isBomb":0,"isPackBooster":0,"type":"catchupBoosters","duration":60,"canActivateBooster":1}}},"rawMaterials":{"title":"Raw materials","id":"rawMaterials","items":{"7_1":{"name":"Food Raw Materials","id":"7_1","industryId":7,"quality":1,"amount":588,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/7/default.png","tooltip":"Raw material needed to produce food
One raw material occupies 100 storage spaces","token":"raw_food","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":13.71},"7_1_partial":{"name":"Food Raw Materials (Under Construction)","id":"7_1_partial","industryId":7,"quality":"1_partial","amount":"13.71%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/7/default.png","tooltip":"Raw material needed to produce food
One raw material occupies 100 storage spaces","token":"raw_food","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0},"12_1":{"name":"Weapon Raw Materials","id":"12_1","industryId":12,"quality":1,"amount":421,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/12/default.png","tooltip":"Raw material needed to produce weapons
One raw material occupies 100 storage spaces","token":"raw_weapon","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":77.86},"12_1_partial":{"name":"Weapon Raw Materials (Under Construction)","id":"12_1_partial","industryId":12,"quality":"1_partial","amount":"77.86%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/12/default.png","tooltip":"Raw material needed to produce weapons
One raw material occupies 100 storage spaces","token":"raw_weapon","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0},"17_1":{"name":"House Raw Materials","id":"17_1","industryId":17,"quality":1,"amount":2,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/17/default.png","tooltip":"Raw material needed to produce houses
One raw material occupies 100 storage spaces","token":"sand_q1","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":79.45},"17_1_partial":{"name":"House Raw Materials (Under Construction)","id":"17_1_partial","industryId":17,"quality":"1_partial","amount":"79.45%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/17/default.png","tooltip":"Raw material needed to produce houses
One raw material occupies 100 storage spaces","token":"sand_q1","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0},"24_1":{"name":"Aircraft Weapons Raw Materials","id":"24_1","industryId":24,"quality":1,"amount":0,"activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/24/default.png","tooltip":"Raw material needed to produce Air-to-Air Missiles
One raw material occupies 100 storage spaces","token":"magnesium_q1","attributes":[],"isRaw":1,"isPartial":0,"isBooster":0,"isBomb":0,"isPackBooster":0,"underCostruction":29},"24_1_partial":{"name":"Aircraft Weapons Raw Materials (Under Construction)","id":"24_1_partial","industryId":24,"quality":"1_partial","amount":"29%","activable":0,"deactivable":0,"activableFromInventory":0,"activableFromBattlefield":0,"activationData":0,"active":0,"activationTooltip":"","icon":"//www.erepublik.net/images/icons/industry/24/default.png","tooltip":"Raw material needed to produce Air-to-Air Missiles
One raw material occupies 100 storage spaces","token":"magnesium_q1","attributes":[],"isRaw":1,"isPartial":1,"isBooster":0,"isBomb":0,"isPackBooster":0}}}},"inventoryStatus":{"totalStorage":1796000,"usedStorage":165873,"color":"green"}} \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..6376cdf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = erepublik_script +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/authors.rst b/docs/authors.rst new file mode 100644 index 0000000..e122f91 --- /dev/null +++ b/docs/authors.rst @@ -0,0 +1 @@ +.. include:: ../AUTHORS.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100755 index 0000000..dbd31f2 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# erepublik_script documentation build configuration file, created by +# sphinx-quickstart on Fri Jun 9 13:47:02 2017. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another +# directory, add these directories to sys.path here. If the directory is +# relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('..')) + +import erepublik_script + +# -- General configuration --------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# 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'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'eRepublik script' +copyright = u"2019, Eriks Karls" +author = u"Eriks Karls" + +# The version info for the project you're documenting, acts as replacement +# for |version| and |release|, also used in various other places throughout +# the built documents. +# +# The short X.Y version. +version = erepublik_script.__version__ +# The full version, including alpha/beta/rc tags. +release = erepublik_script.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' + +# 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 +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Options for HTMLHelp output --------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'erepublik_scriptdoc' + + +# -- Options for LaTeX output ------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'erepublik_script.tex', + u'eRepublik script Documentation', + u'Eriks Karls', 'manual'), +] + + +# -- Options for manual page output ------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'erepublik_script', + u'eRepublik script Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'erepublik_script', + u'eRepublik script Documentation', + author, + 'erepublik_script', + 'One line description of project.', + 'Miscellaneous'), +] + + + diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..e582053 --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..2506499 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1 @@ +.. include:: ../HISTORY.rst diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..244cf0a --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,20 @@ +Welcome to eRepublik script's documentation! +====================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + readme + installation + usage + modules + contributing + authors + history + +Indices and tables +================== +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..af81116 --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,51 @@ +.. highlight:: shell + +============ +Installation +============ + + +Stable release +-------------- + +To install eRepublik script, run this command in your terminal: + +.. code-block:: console + + $ pip install erepublik_script + +This is the preferred method to install eRepublik script, as it will always install the most recent stable release. + +If you don't have `pip`_ installed, this `Python installation guide`_ can guide +you through the process. + +.. _pip: https://pip.pypa.io +.. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ + + +From sources +------------ + +The sources for eRepublik script can be downloaded from the `Github repo`_. + +You can either clone the public repository: + +.. code-block:: console + + $ git clone git://github.com/eeriks/erepublik_script + +Or download the `tarball`_: + +.. code-block:: console + + $ curl -OL https://github.com/eeriks/erepublik_script/tarball/master + +Once you have a copy of the source, you can install it with: + +.. code-block:: console + + $ python setup.py install + + +.. _Github repo: https://github.com/eeriks/erepublik_script +.. _tarball: https://github.com/eeriks/erepublik_script/tarball/master diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..a11302f --- /dev/null +++ b/docs/make.bat @@ -0,0 +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 diff --git a/docs/readme.rst b/docs/readme.rst new file mode 100644 index 0000000..72a3355 --- /dev/null +++ b/docs/readme.rst @@ -0,0 +1 @@ +.. include:: ../README.rst diff --git a/docs/usage.rst b/docs/usage.rst new file mode 100644 index 0000000..0765a49 --- /dev/null +++ b/docs/usage.rst @@ -0,0 +1,7 @@ +===== +Usage +===== + +To use eRepublik script in a project:: + + import erepublik_script diff --git a/erepublik_script/__init__.py b/erepublik_script/__init__.py new file mode 100644 index 0000000..fd19371 --- /dev/null +++ b/erepublik_script/__init__.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 -*- + +"""Top-level package for eRepublik script.""" + +__author__ = """Eriks Karls""" +__email__ = 'eriks@72.lv' +__version__ = '0.1.0' + +import json +import os +import random +import sys +import threading +from collections import defaultdict +from datetime import timedelta +from typing import List, Tuple + +from erepublik_script import classes, utils +from erepublik_script.citizen import Citizen + +__all__ = ["Citizen"] + +INTERACTIVE = True +CONFIG = defaultdict(bool) + + +def main(): + player = None + try: # If errors before player is initialized + while True: + player = Citizen(email=CONFIG['email'], password=CONFIG['password']) + if player.logged_in: + break + utils.silent_sleep(2) + player.config.work = CONFIG['work'] + player.config.train = CONFIG['train'] + player.config.ot = CONFIG['ot'] + player.config.wam = bool(CONFIG['wam']) + player.config.employees = bool(CONFIG['employ']) + player.config.auto_sell = CONFIG.get('auto_sell', []) + player.config.auto_sell_all = CONFIG.get('auto_sell_all', False) + player.config.auto_buy_raw = CONFIG.get('auto_buy_raw', False) + player.config.force_wam = CONFIG.get('force_wam', False) + player.config.fight = CONFIG['fight'] + player.config.air = CONFIG['air'] + player.config.ground = CONFIG['ground'] + player.config.all_in = CONFIG['all_in'] + player.config.next_energy = CONFIG['next_energy'] + player.config.boosters = CONFIG['boosters'] + player.config.travel_to_fight = CONFIG['travel_to_fight'] + player.config.always_travel = CONFIG.get('always_travel', False) + player.config.epic_hunt = CONFIG['epic_hunt'] + player.config.epic_hunt_ebs = CONFIG['epic_hunt_ebs'] + player.config.rw_def_side = CONFIG['rw_def_side'] + player.config.random_sleep = CONFIG['random_sleep'] + player.config.continuous_fighting = CONFIG['continuous_fighting'] + player.config.interactive = CONFIG['interactive'] + player.reporter.allowed = not CONFIG.get('reporting_is_not_allowed') + + player.set_debug(CONFIG.get('debug', False)) + while True: + try: + player.update_all() + break + except: + utils.silent_sleep(2) + + now = utils.now() + dt_max = now.replace(year=9999) + tasks = { + 'eat': now, + } + wam_hour = employ_hour = 14 + if player.config.work: + tasks.update({'work': now}) + if player.config.train: + tasks.update({'train': now}) + if player.config.ot: + tasks.update({'ot': now}) + if player.config.fight: + tasks.update({'fight': now}) + if player.config.wam: + wam_hour = 14 + if not isinstance(CONFIG['wam'], bool): + try: + wam_hour = abs(int(CONFIG['wam'])) % 24 + except ValueError: + pass + tasks.update({'wam': now.replace(hour=wam_hour, minute=0, second=0, microsecond=0)}) + if player.config.employees: + employ_hour = 8 + if not isinstance(CONFIG['employ'], bool): + try: + employ_hour = abs(int(CONFIG['employ'])) % 24 + except ValueError: + pass + tasks.update({'employ': now.replace(hour=employ_hour, minute=0, second=0, microsecond=0)}) + + if player.config.epic_hunt: + tasks['epic_hunt'] = now + + if CONFIG.get("renew_houses", True): + tasks['renew_houses'] = now + + if CONFIG.get('start_battles'): + """ {'start_battle': {war_id: {'regions': [region_id, ], + 'timing': ['at', 'hh:mm' | 'before', 'hh:mm' (before autoattack) | + 'auto' (after round for citizenship country's oldest battle or at 00:00) + 'rw', (after first round of RW if you are occupying)]}} """ + player.allowed_battles = CONFIG.get('start_battles', dict()) + raise classes.ErepublikException("Battle starting is not implemented") + + if player.reporter.allowed: + report = dict(CONFIG) + report.pop("email", None) + report.pop("password", None) + report.update( + VERSION=utils.VERSION, + COMMIT_ID=utils.COMMIT_ID + ) + player.reporter.report_action("ACTIVE_CONFIG", json_val=report) + # -1 because main thread is counted in + name = "{}-state_updater-{}".format(player.name, threading.active_count() - 1) + state_thread = threading.Thread(target=player.state_update_repeater, name=name) + state_thread.start() + + if CONFIG.get("congress", True): + tasks['congress'] = now.replace(hour=1, minute=30, second=0) + + if CONFIG.get("party_president", False): + tasks['party_president'] = now.replace(hour=1, minute=30, second=0) + + contribute_cc = int(CONFIG.get("contribute_cc", 0)) + if contribute_cc: + tasks['contribute_cc'] = now.replace(hour=2, minute=0, second=0) + + if CONFIG.get("gold_buy"): + tasks['gold_buy'] = now.replace(hour=23, minute=57, second=0, microsecond=0) + + error_count = 0 + while error_count < 3: + try: + now = utils.now() + player.update_all() + if tasks.get('work', dt_max) <= now: + player.write_log("Doing task: work") + player.update_citizen_info() + player.work() + if player.config.ot: + tasks['ot'] = now + player.collect_daily_task() + next_time = now.replace(hour=0, minute=0, second=0) + timedelta(days=1) + tasks.update({'work': next_time}) + + if tasks.get('train', dt_max) <= now: + player.write_log("Doing task: train") + player.update_citizen_info() + player.train() + player.collect_daily_task() + next_time = now.replace(hour=0, minute=0, second=0) + timedelta(days=1) + tasks.update({'train': next_time}) + + if tasks.get('wam', dt_max) <= now: + player.write_log("Doing task: Work as manager") + success = player.work_wam() + player.eat() + if success: + next_time = now.replace(hour=wam_hour, minute=0, second=0, microsecond=0) + timedelta(days=1) + else: + next_time = now.replace(second=0, microsecond=0) + timedelta(minutes=30) + + tasks.update({'wam': next_time}) + + if tasks.get('eat', dt_max) <= now: + player.write_log("Doing task: eat") + player.eat() + + if player.energy.food_fights > player.energy.limit // 10: + next_minutes = 12 + else: + next_minutes = (player.energy.limit - 5 * player.energy.interval) // player.energy.interval * 6 + + next_time = player.energy.reference_time + timedelta(minutes=next_minutes) + tasks.update({'eat': next_time}) + + if tasks.get('fight', dt_max) <= now or player.energy.is_energy_full: + fight_energy_debug_log: List[Tuple[int, str]] = [] + player.write_log("Doing task: fight") + player.write_log(player.health_info) + + if player.should_fight(): + player.find_battle_and_fight() + else: + player.collect_weekly_reward() + energy = classes.EnergyToFight(player.details.xp_till_level_up * 10 - player.energy.limit + 50) + fight_energy_debug_log.append(( + energy.i, + f"Levelup reachable {player.details.xp_till_level_up} * 10 - {player.energy.limit} + 50" + )) + + # Do levelup + energy.check(player.details.xp_till_level_up * 10 + 50) + fight_energy_debug_log.append(( + energy.i, f"Levelup {player.details.xp_till_level_up} * 10 + 50" + )) + + # if levelup is close stop queueing other fighting + if not player.is_levelup_close: + + # Obligatory need 75pp + if player.details.pp < 75: + energy.check(75 - player.details.pp) + fight_energy_debug_log.append((energy.i, f"Obligatory need 75pp: 75 - {player.details.pp}")) + + if player.config.continuous_fighting and player.has_battle_contribution: + energy.check(player.energy.interval) + fight_energy_debug_log.append((energy.i, f"continuous_fighting: {player.energy.interval}")) + + # All-in + if player.config.all_in: + energy.check(player.energy.limit * 2 - 3 * player.energy.interval) + fight_energy_debug_log.append(( + energy.i, f"All-in: {player.energy.limit} * 2 - 3 * {player.energy.interval}" + )) + elif player.energy.limit * 2 - 3 * player.energy.interval >= player.energy.recovered: + # 1h worth of energy + energy.check(player.energy.limit * 2 - 3 * player.energy.interval) + fight_energy_debug_log.append( + (energy.i, f"1h worth of energy: {player.energy.interval} * 10" + )) + + # All-in for AIR battles + if all([player.config.air, player.config.all_in, + player.energy.available >= player.energy.limit]): + energy.check(player.energy.limit) + fight_energy_debug_log.append(( + energy.i, f"All-in for AIR battles: {player.energy.limit}" + )) + + # Get to next Energy +1 + if player.next_reachable_energy and player.config.next_energy: + energy.check(player.next_reachable_energy * 10) + fight_energy_debug_log.append(( + energy.i, f"Get to next Energy +1: {player.next_reachable_energy} * 10" + )) + + energy = energy.i - player.energy.available + next_minutes = max([6, abs(energy) // player.energy.interval * 6]) + # utils.write_silent_log("\n".join([f"{energy} {info}" for energy, info in fight_energy_debug_log])) + next_time = player.energy.reference_time + timedelta(minutes=next_minutes) + tasks.update({'fight': next_time}) + + if tasks.get('ot', dt_max) <= now: + player.write_log("Doing task: ot") + if now > player.my_companies.next_ot_time: + player.work_ot() + next_time = now + timedelta(minutes=60) + else: + next_time = player.my_companies.next_ot_time + tasks.update({'ot': next_time}) + + if tasks.get('employ', dt_max) <= now: + player.write_log("Doing task: Employee work") + next_time = utils.now().replace(hour=employ_hour, minute=0, second=0) + timedelta(days=1) + next_time = next_time if player.work_employees() else tasks.get('employ') + timedelta(minutes=30) + tasks.update({'employ': next_time}) + + if tasks.get('epic_hunt', dt_max) <= now: + player.write_log("Doing task: EPIC check") + player.check_epic_battles() + if player.active_fs: + next_time = now + timedelta(minutes=1) + else: + next_time = tasks.get('eat') + tasks.update({'epic_hunt': next_time}) + + if tasks.get('gold_buy', dt_max) <= now: + player.write_log("Doing task: auto buy 10g") + for offer in player.get_monetary_offers(): + if offer['amount'] >= 10 and player.details.cc >= 20 * offer["price"]: + # TODO: check allowed amount to buy + if player.buy_monetary_market_offer(offer=offer['offer_id'], amount=10, currency=62): + break + + next_time = tasks.get('gold_buy') + timedelta(days=1) + tasks.update({'gold_buy': next_time}) + + if tasks.get('congress', dt_max) <= now: + if 1 <= now.day < 16: + next_time = now.replace(day=16) + elif 16 <= now.day < 24: + player.write_log("Doing task: candidate for congress") + player.candidate_for_congress() + if not now.month == 12: + next_time = now.replace(month=now.month + 1, day=16) + else: + next_time = now.replace(year=now.year + 1, month=1, day=16) + else: + if not now.month == 12: + next_time = now.replace(month=now.month + 1, day=16) + else: + next_time = now.replace(year=now.year + 1, month=1, day=16) + tasks.update({'congress': next_time.replace(hour=1, minute=30, second=0, microsecond=0)}) + + if tasks.get('party_president', dt_max) <= now: + if not now.day == 15: + player.write_log("Doing task: candidate for party president") + player.candidate_for_party_presidency() + if not now.month == 12: + next_time = now.replace(month=now.month + 1) + else: + next_time = now.replace(year=now.year + 1, month=1) + else: + if not now.month == 12: + next_time = now.replace(month=now.month + 1) + else: + next_time = now.replace(year=now.year + 1, month=1) + tasks.update(party_president=next_time.replace(day=16, hour=0, minute=0, second=0, microsecond=0)) + + if tasks.get('contribute_cc', dt_max) <= now: + if not now.weekday(): + player.update_money() + cc = (player.details.cc // contribute_cc) * contribute_cc + player.write_log("Doing task: Contribute {}cc to Latvia".format(cc)) + player.contribute_cc_to_country(cc) + next_time = now + timedelta(days=7 - now.weekday()) + next_time = next_time.replace(hour=2, minute=0, second=0) + tasks.update({'contribute_cc': next_time}) + + if tasks.get('renew_houses', dt_max) <= now: + player.write_log("Doing task: Renew houses") + end_times = player.renew_houses() + if end_times: + tasks.update(renew_houses=min(end_times.values()) - timedelta(hours=24)) + else: + player.write_log("No houses found! Forcing q1 usage...") + end_times = player.buy_and_activate_house(1) + if not end_times: + tasks.update(renew_houses=now + timedelta(hours=6)) + else: + tasks.update(renew_houses=min(end_times.values()) - timedelta(hours=24)) + + closest_next_time = dt_max + next_tasks = [] + for task, next_time in sorted(tasks.items(), key=lambda s: s[1]): + next_tasks.append("{}: {}".format(next_time.strftime('%F %T'), task)) + if next_time < closest_next_time: + closest_next_time = next_time + random_seconds = random.randint(0, 121) if player.config.random_sleep else 0 + sleep_seconds = int(utils.get_sleep_seconds(closest_next_time)) + if sleep_seconds <= 0: + raise classes.ErepublikException(f"Loop detected! Offending task: '{next_tasks[0]}'") + closest_next_time += timedelta(seconds=random_seconds) + player.write_log("My next Tasks and there time:\n" + "\n".join(sorted(next_tasks))) + player.write_log("Sleeping until (eRep): {} (sleeping for {}s + random {}s)".format( + closest_next_time.strftime("%F %T"), sleep_seconds, random_seconds)) + seconds_to_sleep = sleep_seconds + random_seconds if sleep_seconds > 0 else 0 + player.sleep(seconds_to_sleep) + + except classes.ErepublikNetworkException: + player.write_log('Network ERROR detected. Sleeping for 1min...') + player.sleep(60) + except (KeyboardInterrupt, SystemExit): + sys.exit(1) + except classes.ErepublikException as e: + utils.process_error(f"Known error detected! {e}", player.name, sys.exc_info(), player, utils.COMMIT_ID) + except: + utils.process_error("Unknown error!", player.name, sys.exc_info(), player, utils.COMMIT_ID) + error_count += 1 + if error_count < 3: + player.sleep(60) + player.stop_threads.set() + player.write_log('Too many errors.') + except (KeyboardInterrupt, SystemExit): + sys.exit(1) + except classes.ErepublikException: + utils.process_error("[{}] Fatal error.".format(utils.COMMIT_ID), player.name, sys.exc_info(), player, + utils.COMMIT_ID) + except: + if isinstance(player, Citizen): + name = player.name + elif CONFIG.get('email', None): + name = CONFIG['email'] + else: + name = "Uninitialized" + utils.process_error("[{}] Fatal error.".format(utils.COMMIT_ID), name, sys.exc_info(), player, utils.COMMIT_ID) + sys.exit(1) + + +if __name__ == "__main__": + assert sys.version_info >= (3, 7, 1) + + write_log = utils.write_silent_log + + try: + with open('config.json', 'r') as f: + CONFIG = json.load(f) + + write_log('Config file found. Checking...') + CONFIG = utils.parse_config(CONFIG) + except: + CONFIG = utils.parse_config() + + with open('config.json', 'w') as f: + json.dump(CONFIG, f, indent=True, sort_keys=True) + if CONFIG['interactive']: + write_log = utils.write_interactive_log + else: + write_log = utils.write_silent_log + write_log('\nTo quit press [ctrl] + [c]', False) + os.chdir(os.path.dirname(os.path.realpath(__file__))) + write_log('Version: ' + utils.VERSION) + while True: + main() + write_log("Restarting after 1h") + utils.interactive_sleep(60 * 60) diff --git a/erepublik_script/citizen.py b/erepublik_script/citizen.py new file mode 100644 index 0000000..d21ae4d --- /dev/null +++ b/erepublik_script/citizen.py @@ -0,0 +1,1802 @@ +import datetime +import re +import sys +import threading +import time +from json import loads, dumps +from typing import Dict, List, Tuple, Any, Union + +import requests +from requests import Response, RequestException + +from erepublik_script import classes, utils + + +class Citizen(classes.CitizenAPI): + url: str = "https://www.erepublik.com/en" + + division = 0 + + all_battles: Dict[int, classes.Battle] = dict() + countries: Dict[int, Dict[str, Union[str, List[int]]]] = dict() + __last_war_update_data = {} + __last_full_update: datetime.datetime + + active_fs = False + + food = {"q1": 0, "q2": 0, "q3": 0, "q4": 0, "q5": 0, "q6": 0, "q7": 0, "total": 0} + inventory = {"used": 0, "total": 0} + boosters = { + "100_damageBoosters_5_7200": 0, # 2h × 60min × 60sec, +50% + "100_damageBoosters_5_28800": 0, # 8h × 60min × 60sec, +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 + candies_double = 0 + candies_small = 0 + tickets = 0 + + work_units = 0 + ot_points = 0 + + tg_contract = {} + promos = {} + + eday = 0 + + r: requests.Response + reporter: classes.Reporter + token = "" + name = "Not logged in!" + debug = False + __registered = False + logged_in = False + + def __init__(self, email: str = "", password: str = ""): + super().__init__() + self.commit_id = utils.COMMIT_ID + self.config = classes.Config() + self.config.email = email + self.config.password = password + self.energy = classes.Energy() + self.details = classes.Details() + self.politics = classes.Politics() + 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.stop_threads = threading.Event() + self.__last_full_update = utils.good_timedelta(self.now, - datetime.timedelta(minutes=5)) + + def write_log(self, *args, **kwargs): + if self.config.interactive: + utils.write_interactive_log(*args, **kwargs) + else: + utils.write_silent_log(*args, **kwargs) + + def sleep(self, seconds: int): + if self.config.interactive: + utils.interactive_sleep(seconds) + else: + time.sleep(seconds) + + def __str__(self) -> str: + return "Citizen {}".format(self.name) + + @property + def __dict__(self): + ret = super().__dict__.copy() + ret.pop('reporter', None) + ret.pop('stop_threads', None) + + return ret + + def set_debug(self, debug: bool): + self.debug = debug + self._req.debug = debug + + def set_pin(self, pin: int): + self.details.pin = pin + + def get_csrf_token(self): + """ + get_csrf_token is the function which logs you in, and updates csrf tokens + (after 15min time of inactivity opening page in eRepublik.com redirects to home page), + by explicitly requesting homepage. + """ + resp = self._req.get(self.url) + self.r = resp + try: + self.update_citizen_info(resp.text) + except: + pass + if self._errors_in_response(resp): + self.get_csrf_token() + return + + html = resp.text + self.check_for_new_medals(html) + re_token = re.search(r'var csrfToken = \'(\w{32})\'', html) + re_login_token = re.search(r'', html) + if re_token: + self.token = re_token.group(1) + elif re_login_token: + self.token = re_login_token.group(1) + self._login() + else: + raise classes.ErepublikException("Something went wrong! Can't find token in page! Exiting!") + + def _login(self): + # MUST BE CALLED TROUGH self.get_csrf_token() + r = self.post_login(self.token, self.config.email, self.config.password) + self.r = r + + if r.url == "{}/login".format(self.url): + self.write_log("Citizen email and/or password is incorrect!") + raise KeyboardInterrupt + else: + re_name_id = re.search(r'', r.text) + self.name = re_name_id.group(2) + self.details.citizen_id = re_name_id.group(1) + + self.write_log("Logged in as: {}".format(self.name)) + self.get_csrf_token() + self.logged_in = True + + def _errors_in_response(self, response: requests.Response): + if response.status_code >= 400: + self.r = response + if response.status_code >= 500: + self.write_log("eRepublik servers are having internal troubles. Sleeping for 5 minutes") + self.sleep(5 * 60) + else: + raise classes.ErepublikException("HTTP {} error!".format(response.status_code)) + return bool(re.search(r'body id="error"|Internal Server Error|' + r'CSRF attack detected|meta http-equiv="refresh"|not_authenticated', response.text)) + + def get(self, url: str, *args, **kwargs) -> Response: + if (self.now - self._req.last_time).seconds >= 15 * 60: + self.get_csrf_token() + if "params" in kwargs: + if "_token" in kwargs["params"]: + kwargs["params"]["_token"] = self.token + if url == self.r.url and not url == self.url: # Don't duplicate requests, except for homepage + response = self.r + else: + try: + response = super().get(url, **kwargs) + except RequestException as e: + self.write_log("Network error while issuing GET request", e) + self.sleep(60) + return self.get(url, *args, **kwargs) + + try: + self.update_citizen_info(html=response.text) + except: + pass + + if self._errors_in_response(response): + self.get_csrf_token() + self.get(url, **kwargs) + else: + self.check_for_new_medals(response.text) + + self.r = response + return response + + def post(self, url: str, data: dict = None, json: dict = None, **kwargs) -> Response: + if (self.now - self._req.last_time).seconds >= 14 * 60: + self.get_csrf_token() + if "_token" in data: + data["_token"] = self.token + if "_token" in json: + json["_token"] = self.token + + try: + response = super().post(url, data=data, json=json, **kwargs) + except RequestException as e: + self.write_log("Network error while issuing POST request", e) + self.sleep(60) + return self.post(url, data, json, **kwargs) + + # response = super().post(url, data=data, json=json, **kwargs) + try: + resp_data = response.json() + if (resp_data.get("error") or not resp_data.get("status")) and resp_data.get("message", "") == "captcha": + utils.send_email(self.name, [response.text, ], player=self, captcha=True) + except: + pass + + if self._errors_in_response(response): + self.get_csrf_token() + if data: + data.update({"_token": self.token}) + elif json: + json.update({"_token": self.token}) + response = self.post(url, data=data, json=json, **kwargs) + else: + self.check_for_new_medals(response.text) + + self.r = response + return response + + def check_for_new_medals(self, html: str): + new_medals = re.findall(r'(
.*?
\s*
)', + html, re.M | re.S | re.I) + data = {} + for medal in new_medals: + try: + info = re.search(r"

New Achievement

.*?(.*?)

.*?" + r"achievement_recieved.*?(.*?).*?" + r"
", medal, re.M | re.S) + about = info.group(1).strip() + title = info.group(2).strip() + reward, currency = info.group(3).strip().split(" ") + while not isinstance(reward, float): + try: + reward = float(reward) + except ValueError: + reward = reward[:-1] + + if title not in data: + data[title] = {'about': about, 'kind': title, 'reward': reward, "count": 1, "currency": currency} + else: + data[title]['count'] += 1 + except AttributeError: + continue + if data: + + msgs = ["{count} ✕ {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))) + for info in data.values(): + self.reporter.report_action("NEW_MEDAL", info) + + levelup = re.search(r"

Congratulations, you have reached experience level (\d+)

", html) + if levelup: + level = levelup.group(1) + msg = "Level up! Current level {}".format(level) + self.write_log(msg) + self.reporter.report_action("LEVEL_UP", value=level) + + def update_all(self, force_update=False): + # Do full update max every 5 min + if utils.good_timedelta(self.__last_full_update, datetime.timedelta(minutes=5)) > self.now and not force_update: + return + else: + self.__last_full_update = self.now + self.update_citizen_info() + self.update_war_info() + self.update_inventory() + self.update_companies() + self.update_money() + self.update_weekly_challenge() + self.send_state_update() + + def update_citizen_info(self, html: str = None): + """ + Gets main page and updates most information about player + """ + if html is None: + self.get_main() + html = self.r.text + 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 v > self.now} + send_mail = False + for promo in promos: + promo_name = promo.get("id") + expire = utils.localize_timestamp(int(promo.get("expiresAt"))) + if promo_name not in self.promos: + send_mail = True + self.promos.update({promo_name: expire}) + if promo_name == "trainingContract": + if not self.tg_contract: + self.train() + if not self.tg_contract["free_train"] and self.tg_contract.get("active", False): + if self.details.gold >= 54: + self.buy_tg_contract() + else: + self.write_log("Training ground contract active but don't have enough gold ({}g {}cc)".format( + self.details.gold, self.details.cc + )) + if send_mail: + active_promos = ["{} active until {}".format(k, v.strftime("%F %T")) for k, v in self.promos.items()] + utils.send_email(self.name, active_promos, player=self, promo=True) + + new_date = re.search(r"var new_date = '(\d*)';", html) + if new_date: + self.energy.set_reference_time( + utils.good_timedelta(self.now, datetime.timedelta(seconds=int(new_date.group(1)))) + ) + + ugly_js = re.search(r"var erepublik = ({.*}),\s+", html).group(1) + citizen_js = loads(ugly_js) + citizen = citizen_js.get("citizen", {}) + + self.eday = citizen_js.get("settings").get("eDay") + self.division = int(citizen.get("division", 0)) + + self.energy.interval = citizen.get("energyPerInterval", 0) + self.energy.limit = citizen.get("energyToRecover", 0) + self.energy.recovered = citizen.get("energy", 0) + self.energy.recoverable = citizen.get("energyFromFoodRemaining", 0) + + self.details.current_region = citizen.get("regionLocationId", 0) + self.details.current_country = citizen.get("countryLocationId", 0) # country where citizen is located + self.details.residence_region = citizen.get("residence", {}).get("regionId", 0) + self.details.residence_country = citizen.get("residence", {}).get("countryId", 0) + self.details.citizen_id = citizen.get("citizenId", 0) + self.details.citizenship = int(citizen.get("country", 0)) + self.details.xp = citizen.get("currentExperiencePoints", 0) + self.details.daily_task_done = citizen.get("dailyTasksDone", False) + self.details.daily_task_reward = citizen.get("hasReward", False) + # if citizen.get("dailyOrderDone", False) and not citizen.get("hasDailyOrderReward", False): + # self.post_military_group_missions(self.token) + # self.get_citizen_daily_assistant() + + self.details.next_pp.sort() + for id_, skill in citizen.get("mySkills", {}).items(): + self.details.mayhem_skills.update({int(skill["terrain_id"]): int(skill["skill_points"])}) + + if citizen.get('party', []): + party = citizen.get('party') + self.politics.is_party_member = True + self.politics.party_id = party.get('party_id') + self.politics.is_party_president = bool(party.get('is_party_president')) + self.politics.party_slug = "{}-{}".format(party.get("stripped_title"), party.get('party_id')) + + def update_money(self, page: int = 0, currency: int = 62) -> Response: + """ + Gets monetary market offers to get exact amount of CC and Gold available + """ + if currency not in [1, 62]: + currency = 62 + resp = self.post_economy_exchange_retrieve(self.token, False, page, currency) + self.details.cc = float(resp.json().get("ecash").get("value")) + self.details.gold = float(resp.json().get("gold").get("value")) + return resp + + def update_job_info(self): + ot = self.get_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) + + def update_companies(self): + html = self.get_economy_my_companies().text + page_details = loads(re.search(r"var pageDetails\s+= ({.*});", html).group(1)) + self.my_companies.work_units = int(page_details.get("total_works", 0)) + + have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html) + have_companies = re.search(r"var companies\s+= ({.*}});", html) + if have_holdings and have_companies: + self.my_companies.prepare_companies(loads(have_companies.group(1))) + self.my_companies.prepare_holdings(loads(have_holdings.group(1))) + self.my_companies.update_holding_companies() + + def update_inventory(self) -> dict: + j = self.get_economy_inventory_items().json() + + self.inventory.update({"used": j.get("inventoryStatus").get("usedStorage"), + "total": j.get("inventoryStatus").get("totalStorage")}) + final = j.get("inventoryItems").get("finalProducts").get("items") + 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]) + return j + + def update_weekly_challenge(self): + data = self.get_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", {}): + status = reward.get("status", "") + if status == "rewarded": + continue + elif status == "completed": + data = { + "_token": self.token, + "rewardId": reward.get("id", 0) + } + self.post_weekly_challenge_reward(self.token, 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", "")) + if pps: + self.details.next_pp.append(int(pps.group(1))) + + def update_war_info(self) -> Dict[Any, Any]: + if not self.details.current_country: + self.update_citizen_info() + + resp_json = self.get_military_campaigns().json() + if resp_json.get("countries"): + for c_id, c_data in resp_json.get("countries").items(): + if int(c_id) not in self.countries: + self.countries.update({ + int(c_id): {"name": c_data.get("name"), "allies": c_data.get("allies")} + }) + else: + self.countries[int(c_id)].update(allies=c_data.get("allies")) + self.__last_war_update_data = resp_json + if resp_json.get("battles"): + for battle_id, battle_data in resp_json.get("battles", {}).items(): + self.all_battles.update({int(battle_id): classes.Battle(battle_data)}) + return self.__last_war_update_data + + def eat(self): + """ + Try to eat food + """ + self.update_citizen_info() + self.update_inventory() + if self.details.xp_till_level_up > (self.energy.recovered - 50) // 10: + 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: + 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: + self.write_log("I failed to buy food") + else: + self.write_log("I'm not allowed to eat because I have levelup coming up!") + self.write_log(self.health_info) + + def eat_ebs(self): + self.write_log("Eating energy bar") + self.update_citizen_info() + if self.energy.recoverable: + self._eat("blue") + self._eat("orange") + self.write_log(self.health_info) + + def _eat(self, colour: str = "blue") -> Response: + response = self.post_eat(self.token, colour) + r_json = response.json() + next_recovery = r_json.get("food_remaining_reset").split(":") + self.energy.set_reference_time( + utils.good_timedelta(self.now, + datetime.timedelta(seconds=int(next_recovery[1]) * 60 + int(next_recovery[2]))) + ) + self.energy.recovered = r_json.get("health") + self.energy.recoverable = r_json.get("food_remaining") + for q, amount in r_json.get("units_consumed").items(): + if "q{}".format(q) in self.food: + self.food["q{}".format(q)] -= amount + elif q == "10": + self.candies_normal -= amount + elif q == "11": + self.candies_double -= amount + elif q == "12": + self.candies_small -= amount + return response + + @property + def health_info(self): + self.update_citizen_info() + ret = "{}/{} + {}, {}hp/6m. {}xp until level up".format( + self.energy.recovered, + self.energy.limit, + self.energy.recoverable, + self.energy.interval, + self.details.xp_till_level_up + ) + return ret + + @property + def now(self) -> datetime.datetime: + """ + Returns aware datetime object localized to US/Pacific (eRepublik time) + :return: datetime.datetime + """ + return utils.now() + + def check_epic_battles(self): + active_fs = False + for battle_id in self.sorted_battles(self.config.sort_battles_time): + battle = self.all_battles.get(battle_id) + if not battle.is_air: + my_div: classes.BattleDivision = battle.div.get(self.division) + if my_div.epic and my_div.end > self.now: + if self.energy.food_fights > 50: + inv_allies = battle.invader.deployed + [battle.invader.id] + def_allies = battle.defender.deployed + [battle.defender.id] + all_allies = inv_allies + def_allies + if self.details.current_country not in all_allies: + self._travel(battle.defender.id, self.get_country_travel_region(battle.defender.id)) + side = battle.defender.id + else: + if self.details.current_country in inv_allies: + side = battle.invader.id + elif self.details.current_country in def_allies: + side = battle.defender.id + else: + self.write_log( + "Country {} not in all allies list ({}) and also not in inv allies ({}) nor def " + "allies ({})".format(self.details.current_country, all_allies, + inv_allies, def_allies)) + break + error_count = 0 + while self.energy.food_fights > 5 and error_count < 20: + errors = self.fight(battle_id, side_id=side, is_air=False, + count=self.energy.food_fights - 5) + if errors: + error_count += errors + if self.config.epic_hunt_ebs: + self.eat_ebs() + self.travel_to_residence() + break + elif bool(my_div.epic): + active_fs = True + + self.active_fs = active_fs + + def sorted_battles(self, sort_by_time: bool = False) -> List[int]: + r = self.update_war_info() + cs_battles_air: List[int] = [] + cs_battles_ground: List[int] = [] + deployed_battles_air: List[int] = [] + deployed_battles_ground: List[int] = [] + ally_battles_air: List[int] = [] + ally_battles_ground: List[int] = [] + other_battles_air: List[int] = [] + other_battles_ground: List[int] = [] + 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] + + # CS Battles + if self.details.citizenship in battle_sides: + if battle.is_air: + cs_battles_ground.append(battle.id) + else: + cs_battles_air.append(battle.id) + + # Current location battles: + elif self.details.current_country in battle_sides: + if battle.is_air: + deployed_battles_ground.append(battle.id) + else: + deployed_battles_air.append(battle.id) + + # Deployed battles and allied battles: + elif self.details.current_country in battle.invader.allies + battle.defender.allies + battle_sides: + if self.details.current_country in battle.invader.deployed + battle.defender.deployed: + if battle.is_air: + deployed_battles_ground.append(battle.id) + else: + deployed_battles_air.append(battle.id) + # Allied battles: + else: + if battle.is_air: + ally_battles_ground.append(battle.id) + else: + ally_battles_air.append(battle.id) + else: + if battle.is_air: + other_battles_ground.append(battle.id) + else: + other_battles_air.append(battle.id) + + ret_battles = [] + if r.get("citizen_contribution"): + battle_id = r.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 + + other_battles_air + other_battles_ground) + return ret_battles + + @property + def has_battle_contribution(self): + return bool(self.update_war_info().get("citizen_contribution", [])) + + def find_battle_and_fight(self): + if self.should_fight(False): + self.write_log("Checking for battles to fight in...") + for battle_id in self.sorted_battles(self.config.sort_battles_time): + battle = self.all_battles.get(battle_id) + div = 11 if battle.is_air else self.division + + allies = battle.invader.deployed + battle.defender.deployed + [battle.invader.id, battle.defender.id] + + travel_needed = self.details.current_country not in allies + + if battle.is_rw: + side_id = battle.defender.id if self.config.rw_def_side else battle.invader.id + else: + side_id = battle.defender.id if (self.details.current_country in battle.defender.allies + + [battle.defender.id, ]) else battle.invader.id + try: + def_points = battle.div.get(div).dom_pts.get('def') + inv_points = battle.div.get(div).dom_pts.get('inv') + except KeyError: + self.report_error(f"Division {div} not available for battle {battle.id}!") + def_points = inv_points = 3600 + kwargs = { + "bid": battle.id, + "air": "air" if battle.is_air else "ground", + "rw": "True" if battle.is_rw else "False", + "def": def_points, + "inv": inv_points, + "travel": "(TRAVEL)" if travel_needed else "", + } + self.write_log("Battle {bid}, type: {air:6}, rw: {rw:5}, " + "points: {def:4}:{inv:<4} {travel}".format(**kwargs)) + + points = def_points <= 1700 and inv_points <= 1700 + b_type = battle.is_air and self.config.air or not battle.is_air and self.config.ground + travel = (self.config.travel_to_fight and self.should_travel_to_fight() or self.config.force_travel) \ + if travel_needed else True + + if not (points and b_type and travel): + continue + + if battle.start > self.now: + self.sleep(utils.get_sleep_seconds(battle.start)) + + if travel_needed: + if battle.is_rw: + self._travel(battle.defender.id, self.get_country_travel_region(battle.defender.id)) + elif self.details.current_country not in battle.invader.allies: + self.travel(battle_id=battle.id) + side_id = battle.invader.id + else: + self._travel(battle.defender.id, self.get_country_travel_region(battle.defender.id)) + side_id = battle.defender.id + + self.fight(battle_id, side_id, battle.is_air) + self.travel_to_residence() + self.collect_weekly_reward() + break + + def fight(self, battle_id: int, side_id: int, is_air: bool = False, count: int = None): + data = dict(sideId=side_id, battleId=battle_id, _token=self.token) + error_count = 0 + ok_to_fight = True + if count is None: + count = self.should_fight(silent=False) + + total_damage = 0 + total_hits = 0 + + while ok_to_fight and error_count < 10: + while all((count > 0, error_count < 10, self.energy.recovered >= 50)): + hits, error, damage = self._shoot(is_air, data) + count -= hits + total_hits += hits + total_damage += damage + error_count += error + else: + self.eat() + if self.energy.recovered < 50 or error_count >= 10 or count <= 0: + self.write_log("Hits: {:>4} | Damage: {}".format(total_hits, total_damage)) + ok_to_fight = False + if total_damage: + self.reporter.report_action(json_val=dict(battle=battle_id, side=side_id, dmg=total_damage, + air=is_air, hits=total_hits), action="FIGHT") + if error_count: + return error_count + + def _shoot(self, air: bool, data: dict): + if air: + response = self.post_military_fight_air(self.token, data['battleId'], data['sideId']) + else: + response = self.post_military_fight_ground(self.token, data['battleId'], data['sideId']) + + if "Zone is not meant for " in response.text: + self.sleep(5) + return 0, 1, 0 + try: + j_resp = response.json() + except: + return 0, 10, 0 + hits = 0 + damage = 0 + err = False + if j_resp.get("error"): + if j_resp.get("message") == "SHOOT_LOCKOUT": + pass + else: + if j_resp.get("message") == "UNKNOWN_SIDE": + self._rw_choose_side(data["battleId"], data["sideId"]) + err = True + elif j_resp.get("message") == "ENEMY_KILLED": + hits = (self.energy.recovered - j_resp["details"]["wellness"]) // 10 + self.energy.recovered = j_resp["details"]["wellness"] + damage = j_resp["user"]["givenDamage"] * (1.1 if j_resp["oldEnemy"]["isNatural"] else 1) + else: + err = True + + return hits, err, damage + + def work_ot(self): + # I"m not checking for 1h cooldown. Beware of nightshift work, if calling more than once every 60min + self.update_job_info() + if self.ot_points >= 24 and self.energy.food_fights > 1: + r = self.post_economy_work_overtime(self.token) + if not r.json().get("status") and r.json().get("message") == "money": + self.resign() + self.find_new_job() + else: + self.reporter.report_action("WORK_OT", r.json()) + elif self.energy.food_fights < 1 and self.ot_points >= 24: + self._eat("blue") + if self.energy.food_fights < 1: + large = max(self.energy.reference_time, self.now) + small = min(self.energy.reference_time, self.now) + self.write_log("I don't have energy to work OT. Will sleep for {}s".format((large - small).seconds)) + self.sleep(int((large - small).total_seconds())) + self._eat("blue") + self.work_ot() + + def work(self): + if self.energy.food_fights >= 1: + response = self.post_economy_work(self.token, "work") + js = response.json() + good_msg = ["already_worked", "captcha"] + if not js.get("status") and not js.get("message") in good_msg: + self.update_citizen_info() + self.work() + else: + self.reporter.report_action("WORK", json_val=js) + else: + self._eat("blue") + if self.energy.food_fights < 1: + large = max(self.energy.reference_time, self.now) + small = min(self.energy.reference_time, self.now) + self.write_log("I don't have energy to work. Will sleep for {}s".format((large - small).seconds)) + self.sleep(int((large - small).total_seconds())) + self._eat("blue") + self.work() + + def train(self): + r = self.get_training_grounds_json() + tg_json = r.json() + self.details.gold = tg_json["page_details"]["gold"] + self.tg_contract.update({"free_train": tg_json["hasFreeTrain"]}) + if tg_json["contracts"]: + self.tg_contract.update(**tg_json["contracts"][0]) + + tgs = [] + for data in sorted(tg_json["grounds"], key=lambda k: k["cost"]): + if data["default"] and not data["trained"]: + tgs.append(data["id"]) + if tgs: + if self.energy.food_fights >= len(tgs): + response = self.post_economy_train(self.token, tgs) + if not response.json().get("status"): + self.update_citizen_info() + self.train() + else: + self.reporter.report_action("TRAIN", response.json()) + else: + self._eat("blue") + if self.energy.food_fights < len(tgs): + large = max(self.energy.reference_time, self.now) + small = min(self.energy.reference_time, self.now) + self.write_log("I don't have energy to train. Will sleep for {} seconds".format( + (large - small).seconds)) + self.sleep(int((large - small).total_seconds())) + self._eat("blue") + self.train() + + def work_employees(self) -> bool: + self.update_companies() + ret = True + work_units_needed = 0 + employee_companies = self.my_companies.get_employable_factories() + for c_id, preset_count in employee_companies.items(): + work_units_needed += preset_count + + if work_units_needed: + if work_units_needed <= self.my_companies.work_units: + self._do_wam_and_employee_work(employee_companies=employee_companies) + self.update_companies() + if self.my_companies.get_employable_factories(): + ret = False + else: + ret = True + + return ret + + def work_wam(self) -> bool: + self.update_citizen_info() + self.update_companies() + # Prevent messing up levelup with wam + if not (self.is_levelup_close and self.config.fight) or self.config.force_wam: + # Check for current region + regions = {} + for holding_id, holding in self.my_companies.holdings.items(): + if self.my_companies.get_holding_wam_companies(holding_id): + regions.update({holding["region_id"]: holding_id}) + + if self.details.current_region in regions: + self._do_wam_and_employee_work(regions.pop(self.details.current_region, None)) + + for holding_id in regions.values(): + self._do_wam_and_employee_work(holding_id) + + self.travel_to_residence() + else: + self.write_log("Did not wam because I would mess up levelup!") + + self.update_companies() + return not self.my_companies.get_total_wam_count() + + def _do_wam_and_employee_work(self, wam_holding_id: int = 0, employee_companies: dict = None) -> bool: + self.update_citizen_info() + if employee_companies is None: + employee_companies = {} + data = { + "action_type": "production", + "_token": self.token, + } + extra = {} + wam_list = [] + if wam_holding_id: + raw_count = self.my_companies.get_holding_wam_count(wam_holding_id, raw_factory=True) + fab_count = self.my_companies.get_holding_wam_count(wam_holding_id, raw_factory=False) + if raw_count + fab_count <= self.energy.food_fights: + raw_factories = None + elif not raw_count and fab_count <= self.energy.food_fights: + raw_factories = False + else: + raw_factories = True + + free_inventory = self.inventory["total"] - self.inventory["used"] + wam_list = self.my_companies.get_holding_wam_companies(wam_holding_id, + raw_factory=raw_factories)[:self.energy.food_fights] + has_space = False + while not has_space and wam_list: + extra_needed = self.my_companies.get_needed_inventory_usage(companies=wam_list) + has_space = extra_needed < free_inventory + if not has_space: + inv_w = len(str(self.inventory["total"])) + self.write_log( + "Inv: {:{inv_w}}/{:{inv_w}} ({:4.2f}), Energy: {}/{} + {} (+{}hp/6min) WAM count {:3}".format( + self.inventory["used"], self.inventory["total"], extra_needed, + self.energy.recovered, self.energy.limit, self.energy.recoverable, self.energy.interval, + len(wam_list), inv_w=inv_w + )) + wam_list.pop(-1) + + if wam_list or employee_companies: + data.update(extra) + if wam_list: + wam_holding = self.my_companies.holdings.get(wam_holding_id) + if not self.details.current_region == wam_holding['region_id']: + self.travel(holding_id=wam_holding_id, region_id=wam_holding['region_id']) + response = self.post_economy_work(self.token, "production", wam=wam_list, employ=employee_companies).json() + self.reporter.report_action("WORK_WAM_EMPLOYEES", response) + if response.get("status"): + if self.config.auto_sell: + for kind, data in response.get("result", {}).get("production", {}).items(): + if kind in self.config.auto_sell and data: + if kind in ["food", "weapon", "house", "airplane"]: + for quality, amount in data.items(): + self.sell_produced_product(kind, quality) + + elif kind.endswith("Raw"): + self.sell_produced_product(kind, 1) + + else: + raise classes.ErepublikException("Unknown kind produced '{kind}'".format(kind=kind)) + elif self.config.auto_buy_raw and re.search(r"not_enough_[^_]*_raw", response.get("message")): + raw_kind = re.search(r"not_enough_(\w+)_raw", response.get("message")) + if raw_kind: + raw_kind = raw_kind.group(1) + result = response.get("result", {}) + amount_remaining = round(result.get("consume") + 0.49) - round(result.get("stock") - 0.49) + industry = "{}Raw".format(raw_kind) + while amount_remaining > 0: + amount = amount_remaining + best_offer = self.get_market_offers(self.details.citizenship, industry, 1) + amount = best_offer['amount'] if amount >= best_offer['amount'] else amount + rj = self.buy_from_market(amount=best_offer['amount'], offer=best_offer['offer_id']).json() + if not rj.get('error'): + amount_remaining -= amount + else: + self.write_log(rj.get('message', "")) + break + else: + 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() + if wam_count: + self.write_log("Wam ff lockdown is now {}, was {}".format(wam_count, self.my_companies.ff_lockdown)) + self.my_companies.ff_lockdown = wam_count + return bool(wam_count) + + def sell_produced_product(self, kind: str, quality: int = 1, amount: int = 0): + if not amount: + inv_resp = self.update_inventory() + category = "rawMaterials" if kind.endswith("Raw") else "finalProducts" + item = "{}_{}".format(self.available_industries[kind], quality) + amount = inv_resp.get("inventoryItems").get(category).get("items").get(item).get("amount", 0) + + if amount >= 1: + lowest_price = self.get_market_offers(country_id=self.details.citizenship, + product=kind, quality=int(quality)) + + if lowest_price["citizen_id"] == self.details.citizen_id: + price = lowest_price["price"] + else: + price = lowest_price["price"] - 0.01 + + self.post_market_offer(industry=self.available_industries[kind], amount=int(amount), + quality=int(quality), price=price) + + def travel_to_residence(self): + self.update_citizen_info() + res_r = self.details.residence_region + if self.details.residence_country and res_r and not res_r == self.details.current_region: + self._travel(self.details.residence_country, self.details.residence_region) + + def get_country_travel_region(self, country_id: int) -> int or None: + r = self.get_travel_regions(country_id=country_id).json() + regions = r.get("regions") + regs = [] + if regions: + for region in regions.values(): + if region['countryId'] == country_id: # Is not occupied by other country + regs.append((region['id'], region['distanceInKm'])) + if regs: + return min(regs, key=lambda _: int(_[1]))[0] + else: + return None + + def travel(self, holding_id=0, battle_id=0, region_id=0): + r = self.get_travel_regions(holding_id, battle_id, region_id) + regions = r.json()["regions"] + closest_region = 99999 + country_id = int(r.json()["preselectCountryId"]) + region_id = int(r.json()["preselectRegionId"]) + if not any((region_id, country_id)): + for rid, info in regions.items(): + if info.get("distanceInKm", 99999) < closest_region: + closest_region = info.get("distanceInKm") + country_id = info.get("countryId") + region_id = rid + self._travel(country_id, region_id) + + def _travel(self, country_id: int, region_id: int = 0) -> Response: + data = { + "toCountryId": country_id, + "inRegionId": region_id, + "battleId": 0, + } + return self.post_travel(self.token, "moveAction", **data) + + def get_travel_regions(self, holding_id: int = 0, battle_id: int = 0, region_id: int = 0, + country_id: int = 0) -> Response: + data = { + "holdingId": holding_id, + "battleId": battle_id, + "regionId": region_id, + } + data.update(countryId=country_id) + return self.post_travel_data(self.token, **data) + + def parse_notifications(self, page: int = 1) -> list: + response = self.get_message_alerts(page) + notifications = re.findall(r"

(.*?)

", response.text, re.M | re.I | re.S) + return notifications + + def delete_notifications(self): + def notification_ids(html): + return re.findall(r"id=\"delete_alert_(\d+)\"", html) + + response = self.get_message_alerts() + while notification_ids(response.text): + response = self.post_messages_alert(self.token, notification_ids(response.text)) + + def collect_weekly_reward(self): + self.update_weekly_challenge() + + def collect_daily_task(self) -> Response or None: + self.update_citizen_info() + if self.details.daily_task_done and not self.details.daily_task_reward: + return self.post_daily_task_reward(self.token) + + def send_mail_to_owner(self) -> Response or None: + if not self.details.citizen_id == 1620414: + self.send_mail("Started", "time {}".format(self.now.strftime("%Y-%m-%d %H-%M-%S")), [1620414, ]) + self.sleep(1) + msg_id = re.search(r"", self.r.text).group(1) + return self.post_delete_message(self.token, [msg_id]) + + def get_market_offers(self, country_id: int = None, product: str = None, quality: int = None) -> dict: + ret = dict() + raw_short_names = dict(frm="foodRaw", wrm="weaponRaw", hrm="houseRaw", arm="airplaneRaw") + q1_industries = ["aircraft"] + list(raw_short_names.values()) + if product in raw_short_names: + quality = 1 + product = raw_short_names.get(product) + + item_data = dict(price=999999., country=0, amount=0, offer_id=0, citizen_id=0) + + items = {"food": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(), + q5=item_data.copy(), q6=item_data.copy(), q7=item_data.copy()), + "weapon": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(), + q5=item_data.copy(), q6=item_data.copy(), q7=item_data.copy()), + "house": dict(q1=item_data.copy(), q2=item_data.copy(), q3=item_data.copy(), q4=item_data.copy(), + q5=item_data.copy()), "aircraft": dict(q1=item_data.copy()), + "foodRaw": dict(q1=item_data.copy()), "weaponRaw": dict(q1=item_data.copy()), + "houseRaw": dict(q1=item_data.copy()), "airplaneRaw": dict(q1=item_data.copy())} + + countries = [country_id] if country_id else self.countries + if product not in self.available_industries: + self.write_log("Industry '{}' not implemented".format(product)) + return ret + + start_dt = self.now + for country in countries: + if not country_id and not self.get_country_travel_region(country): + continue + for industry in [product] or items: + for q in [quality] if quality else range(1, 8): + if (q > 1 and industry in q1_industries) or (q > 5 and industry == "house"): + break + + str_q = "q%i" % q + + data = {'country': country, 'industry': self.available_industries[industry], 'quality': q} + r = self.post_economy_marketplace(self.token, **data) + rjson = r.json() + obj = items[industry][str_q] + if not rjson.get("error", False): + for offer in rjson["offers"]: + if obj["price"] > float(offer["priceWithTaxes"]): + obj["price"] = float(offer["priceWithTaxes"]) + obj["country"] = int(offer["country_id"]) + obj["amount"] = int(offer["amount"]) + obj["offer_id"] = int(offer["id"]) + obj["citizen_id"] = int(offer["citizen_id"]) + elif obj["price"] == float(offer["priceWithTaxes"]) \ + and obj["amount"] < int(offer["amount"]): + obj["country"] = int(offer["country_id"]) + obj["amount"] = int(offer["amount"]) + obj["offer_id"] = int(offer["id"]) + self.write_log("Scraped market in {}!".format(self.now - start_dt)) + + if quality: + ret = items[product]["q%i" % quality] + elif product: + if product in raw_short_names.values(): + ret = items[product]["q1"] + else: + ret = items[product] + else: + ret = items + return ret + + def buy_food(self) -> Response or None: + self.update_money() + 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"] + 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] + + if cheapest["amount"] * hp_per_quality[cheapest_q] < hp_needed: + amount = cheapest["amount"] + else: + amount = hp_needed // hp_per_quality[cheapest_q] + + if amount * cheapest["price"] < self.details.cc: + data = dict(offer=cheapest["offer_id"], amount=amount, price=cheapest["price"], + cost=amount * cheapest["price"], quality=cheapest_q, energy=amount * hp_per_quality[cheapest_q]) + self.reporter.report_action("BUY_FOOD", json_val=data) + return self.buy_from_market(cheapest["offer_id"], amount) + else: + s = "Don't have enough money! Needed: {}cc, Have: {}cc".format(amount * cheapest["price"], self.details.cc) + self.write_log(s) + self.reporter.report_action("BUY_FOOD", value=s) + return None + + def get_monetary_offers(self, currency: int = 62) -> list: + if currency not in [1, 62]: + currency = 62 + resp = self.update_money(0, currency).json() + ret = [] + offers = re.findall(r"id='purchase_(\d+)' data-i18n='Buy for' data-currency='GOLD' " + r"data-price='(\d+\.\d+)' data-max='(\d+\.\d+)' trigger='purchase'", + resp["buy_mode"], re.M | re.I | re.S) + + for offer_id, price, amount in offers: + ret.append({"offer_id": int(offer_id), "price": float(price), "amount": float(amount)}) + + return sorted(ret, key=lambda o: (o["price"], -o["amount"])) + + def buy_monetary_market_offer(self, offer: int, amount: float, currency: int) -> bool: + response = self.post_economy_exchange_purchase(self.token, amount, currency, offer) + self.details.cc = float(response.json().get("ecash").get("value")) + self.details.gold = float(response.json().get("gold").get("value")) + self.reporter.report_action("BUY_GOLD", json_val=response.json(), + value="New amount {o.cc}cc, {o.gold}g".format(o=self.details)) + return not response.json().get("error", False) + + def activate_dmg_booster(self, battle_id: int) -> Response or None: + if self.config.boosters: + duration = 0 + if self.boosters.get("100_damageBoosters_5_600", 0) > 0: + duration = 600 + elif self.boosters.get("100_damageBoosters_5_7200", 0) > 1: + duration = 7200 + elif self.boosters.get("100_damageBoosters_5_28800", 0) > 2: + duration = 28800 + elif self.boosters.get("100_damageBoosters_5_86400", 0) > 2: + duration = 86400 + return self.post_fight_activate_booster(self.token, battle_id, 5, duration, "damage") + + def activate_battle_effect(self, battle_id: int, kind: str): + return self.post_activate_battle_effect(self.token, battle_id, kind, self.details.citizen_id) + + def activate_pp_booster(self, battle_id: int): + return self.post_fight_activate_booster(self.token, battle_id, 1, 180, "prestige_points") + + def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62): + """ currency: gold = 62, cc = 1 """ + return self.post_economy_donate_money_action(self.token, citizen_id, amount, currency) + + def donate_items(self, citizen_id: int = 1620414, amount: int = 0, industry_id: int = 1, + quality: int = 1) -> Response: + ind = {v: k for k, v in self.available_industries.items()} + self.write_log("D,{},q{},{},{}".format(amount, quality, ind[industry_id], citizen_id)) + return self.post_economy_donate_items_action(self.token, citizen_id, amount, industry_id, quality) + + def candidate_for_congress(self, presentation: str = "") -> Response: + return self.post_candidate_for_congress(self.token, presentation) + + def candidate_for_party_presidency(self) -> Response: + return self.get_candidate_party(self.politics.party_slug) + + def accept_money_donations(self): + 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(self.token, int(don_id.group(1))) + self.sleep(5) + + def reject_money_donations(self) -> int: + r = self.get_message_alerts() + 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(self.token, int(don_id)) + count += 1 + self.sleep(5) + r = self.get_message_alerts() + 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) + + def should_travel_to_fight(self) -> bool: + ret = False + if self.config.always_travel: + return True + if 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 + # 1h worth of energy + elif self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2: + ret = True + return ret + + def should_fight(self, silent: bool = True) -> int: + if not self.config.fight: + return 0 + count = 0 + log_msg = "" + force_fight = False + # Do levelup + if self.is_levelup_reachable: + log_msg = "Level up" + if self.should_do_levelup: + count = (self.energy.limit * 3) // 10 + force_fight = True + else: + self.write_log("Waiting for fully recovered energy before leveling up.", False) + + # Levelup reachable + elif self.is_levelup_close: + count = self.details.xp_till_level_up - (self.energy.limit // 10) + 5 + log_msg = "Fighting for close Levelup. Doing %i hits" % count + force_fight = True + + elif self.details.pp < 75: + count = 75 - self.details.pp + log_msg = "Obligatory fighting for at least 75pp" + force_fight = True + + elif self.config.continuous_fighting and self.has_battle_contribution: + count = self.energy.food_fights + log_msg = "Continuing to fight in previous battle" + + # All-in (type = all-in and full ff) + elif self.config.all_in and self.energy.available + self.energy.interval * 3 >= self.energy.limit * 2: + count = self.energy.food_fights + 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]): + count = self.energy.food_fights + log_msg = "Fighting all-in in AIR. Doing %i hits" % count + + # Get to next Energy +1 + elif self.next_reachable_energy and self.config.next_energy: + count = self.next_reachable_energy + log_msg = "Fighting for +1 energy. Doing %i hits" % count + + # 1h worth of energy + 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 + + 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 > 0: + log_msg = ("Fight count modified (old count: {} | FF: {} | " + "WAM ff_lockdown: {} | New count: {})").format( + count, self.energy.food_fights, self.my_companies.ff_lockdown, + self.energy.food_fights - self.my_companies.ff_lockdown) + count = self.energy.food_fights - self.my_companies.ff_lockdown + else: + count = 0 + if 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: + max_count = int((self.time_till_week_change - self.time_till_full_ff).total_seconds()) // 60 + log_msg = "End for Weekly challenge is near ({} | {})".format(max_count, count) + count = count if max_count > count else max_count + + if not silent: + self.write_log(log_msg, False) + + return count if count > 0 else 0 + + @property + def next_reachable_energy(self) -> int: + # Return pps for furthest __reachable__ +1 energy else 0 + max_pp = 0 + for pp_milestone in self.details.next_pp: + pp_milestone = int(pp_milestone) + if self.details.pp + self.energy.food_fights > pp_milestone: # if reachable set max pp + max_pp = pp_milestone + else: # rest are only bigger no need + break + return max_pp - self.details.pp if max_pp else 0 + + @property + def next_wc_start(self) -> datetime.datetime: + days = 1 - self.now.weekday() if 1 - self.now.weekday() > 0 else 1 - self.now.weekday() + 7 + return utils.good_timedelta(self.now.replace(hour=0, minute=0, second=0, microsecond=0), + datetime.timedelta(days=days)) + + @property + def time_till_week_change(self) -> datetime.timedelta: + return self.next_wc_start - self.now + + @property + def time_till_full_ff(self) -> datetime.timedelta: + energy = self.energy.recoverable + self.energy.recovered + if energy >= self.energy.limit * 2: + return datetime.timedelta(0) + minutes_needed = round((self.energy.limit * 2 - energy) / self.energy.interval) * 6 + return (self.energy.reference_time - self.now) + datetime.timedelta(minutes=minutes_needed) + + @property + def max_time_till_full_ff(self) -> datetime.timedelta: + """ + Max required time for 0 to full energy (0/0 -> limit/limit) (last interval rounded up) + :return: + """ + return datetime.timedelta(minutes=round((self.energy.limit * 2 / self.energy.interval) + 0.49) * 6) + + @property + def is_levelup_close(self) -> bool: + """ + If Energy limit * 2 >= xp till levelup * 10 + :return: bool + """ + return self.energy.limit * 2 >= self.details.xp_till_level_up * 10 + + @property + def is_levelup_reachable(self) -> bool: + """ + If Energy limit >= xp till levelup * 10 + :return: bool + """ + return self.energy.limit >= self.details.xp_till_level_up * 10 + + @property + def should_do_levelup(self) -> bool: + """ + If Energy limit >= xp till levelup * 10 + :return: bool + """ + if self.energy.limit - self.energy.interval <= self.energy.recoverable: + if self.is_levelup_reachable: + # 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 = 0): + return self.post_article_comments(self.token, article_id, page_id) + + def comment_article(self, article_id: int = 2645676, msg: str = None) -> Response: + if msg is None: + msg = self.eday + r = self.get_article_comments(article_id, 2) + r = self.get_article_comments(article_id, r.json()["pages"]) + comments = r.json()["comments"] + if not comments[max(comments.keys())]["isMyComment"]: + r = self.write_article_comment(msg, article_id) + return r + + def write_article_comment(self, message: str, article_id: int, parent_id: int = None): + return self.post_article_comments_create(self.token, message, article_id, parent_id) + + def publish_article(self, title: str, content: str, kind: int): + 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(self.token, title, content, self.details.citizenship, kind) + else: + raise classes.ErepublikException( + "Article kind must be one of:\n{}\n'{}' is not supported".format( + "\n".join(["{}: {}".format(k, v) for k, v in kinds.items()]), + kind + ) + ) + + def post_market_offer(self, industry: int, quality: int, amount: int, price: float) -> Response: + if industry not in self.available_industries.values(): + self.write_log("Trying to sell unsupported industry {}".format(industry)) + + data = { + "token": self.token, + "country": self.details.citizenship, + "industry": industry, + "quality": quality, + "amount": amount, + "price": price, + "buy": False, + } + ret = self.post_economy_marketplace_actions(**data) + self.reporter.report_action("SELL_PRODUCT", ret.json()) + return ret + + def buy_from_market(self, offer: int, amount: int) -> Response: + ret = self.post_economy_marketplace_actions(self.token, amount, True, offer=offer) + json_ret = ret.json() + if json_ret.get('error'): + return ret + else: + self.details.cc = ret.json()['currency'] + self.details.gold = ret.json()['gold'] + r_json = ret.json() + r_json.pop("offerUpdate", None) + self.reporter.report_action("BUY_PRODUCT", ret.json()) + return ret + + 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 + elif cdata["industry_token"] == "WEAPON": + raw = wrm + else: + continue + if cdata["is_raw"]: + raw += float(cdata["base_production"]) * cdata["effective_bonus"] / 100 + else: + raw -= cdata["effective_bonus"] / 100 * cdata["base_production"] * \ + cdata["upgrades"][str(cdata["quality"])]["raw_usage"] + if cdata["industry_token"] == "FOOD": + frm = raw + elif cdata["industry_token"] == "WEAPON": + wrm = raw + return frm, wrm + + def assign_factory_to_holding(self, factory_id: int, holding_id: int) -> Response: + """ + Assigns factory to new holding + :type factory_id: int + :type holding_id: int + :return: Response object + """ + return self.post_economy_assign_to_holding(self.token, factory_id, holding_id) + + def upgrade_factory(self, factory_id: int, level: int) -> Response: + return self.post_economy_upgrade_company(self.token, factory_id, level, self.details.pin) + + def create_factory(self, industry_id: int, building_type: int = 1) -> Response: + """ + param industry_ids: FRM={q1:7, q2:8, q3:9, q4:10, q5:11} WRM={q1:12, q2:13, q3:14, q4:15, q5:16} + HRM={q1:18, q2:19, q3:20, q4:21, q5:22} ARM={q1:24, q2:25, q3:26, q4:27, q5:28} + Factories={Food:1, Weapons:2, House:4, Aircraft:23} <- Building_type 1 + + Storage={1000: 1, 2000: 2} <- Building_type 2 + """ + return self.post_economy_create_company(self.token, industry_id, building_type) + + def dissolve_factory(self, factory_id: int) -> Response: + return self.post_economy_sell_company(self.token, factory_id, self.details.pin, sell=False) + + @property + def available_industries(self) -> Dict[str, int]: + """ + Returns currently available industries as dict(name: id) + :return: dict + """ + return {"food": 1, "weapon": 2, "house": 4, "aircraft": 23, + "foodRaw": 7, "weaponRaw": 12, "houseRaw": 17, "airplaneRaw": 24} + + def get_industry_id(self, industry_name: str) -> int: + """ + Returns industry id + :type industry_name: str + :return: int + """ + return self.available_industries.get(industry_name, 0) + + def buy_tg_contract(self) -> Response: + ret = self.post_buy_gold_items(self.token, 'gold', "TrainingContract2", 1) + self.reporter.report_action("BUY_TG_CONTRACT", ret.json()) + return ret + + def resign(self) -> Response or None: + self.update_job_info() + if self.r.json().get("isEmployee"): + self.reporter.report_action("RESIGN", self.r.json()) + return self.post_economy_resign(self.token) + return None + + def find_new_job(self) -> Response: + r = self.get_economy_job_market_json(self.details.current_country) + jobs = r.json().get("jobs") + data = dict(token=self.token, citizen=0, salary=10) + for posting in jobs: + salary = posting.get("netSalary") + limit = posting.get("salaryLimit", 0) + userid = posting.get("citizen").get("id") + + if (not limit or salary * 3 < limit) and salary > data["salary"]: + data.update({"citizen": userid, "salary": salary}) + self.reporter.report_action("APPLYING_FOR_JOB", jobs, str(data['citizen'])) + return self.post_economy_job_market_apply(**data) + + def add_friend(self, player_id: int) -> Response: + resp = self.get_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(self.token, int(player_id), True) + self.write_log("{:<64} (id:{:>11}) added as friend".format(rjson["name"], player_id)) + return r + return resp + + 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) + ret = {} + for name, id_ in re.findall(r'
', r.text): + ret.update({int(id_): name}) + return ret + + def get_party_members(self, party_id: int) -> Dict[int, str]: + ret = {} + r = super().get_party_members(party_id) + for id_, name in re.findall(r'', r.text): + ret.update({id_: name}) + return ret + + def get_country_mus(self, country_id: int) -> Dict[int, str]: + ret = {} + r = self.get_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) + for data in r.json()["mu_filter"]: + if data["id"]: + ret.update({data["id"]: data["name"]}) + return ret + + def get_mu_members(self, mu_id: int) -> Dict[int, str]: + ret = {} + r = self.get_military_unit_data(mu_id) + + for page in range(1, int(r.json()["panelContents"]["pages"]) + 1): + r = self.get_military_unit_data(mu_id, page) + for user in r.json()["panelContents"]["members"]: + if not user["isDead"]: + ret.update({user["citizenId"]: user["name"]}) + return ret + + def send_mail(self, subject: str, msg: str, ids: List[int] = None): + if ids is None: + ids = [1620414, ] + for player_id in ids: + self.post_messages_compose(self.token, 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(self.token, 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']}) + + 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() + 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) + resp = r.json() + for resident in resp["widgets"]["residents"]["residents"]: + self.add_friend(resident["citizenId"]) + + def schedule_attack(self, war_id: int, region_id: int, at_time: datetime) -> None: + if at_time: + self.sleep(utils.get_sleep_seconds(at_time)) + self.get_csrf_token() + self._launch_battle(war_id, region_id) + + def get_active_wars_with_regions(self): + self.get_country_military(self.countries.get(self.details.citizen_id)["name"]) + raise NotImplementedError + + def _launch_battle(self, war_id: int, region_id: int) -> Response: + return self.post_wars_attack_region(self.token, war_id, region_id) + + def state_update_repeater(self): + try: + start_time = self.now.replace(second=0, microsecond=0) + if start_time.minute <= 30: + start_time = start_time.replace(minute=30) + else: + start_time = start_time.replace(minute=0) + while not self.stop_threads.is_set(): + self.update_citizen_info() + start_time = utils.good_timedelta(start_time, datetime.timedelta(minutes=30)) + self.send_state_update() + self.send_inventory_update() + sleep_seconds = (start_time - self.now).total_seconds() + self.stop_threads.wait(sleep_seconds if sleep_seconds > 0 else 0) + except: + self.report_error() + + def send_state_update(self): + data = dict(xp=self.details.xp, cc=self.details.cc, gold=self.details.gold, pp=self.details.pp, + inv_total=self.inventory['total'], inv=self.inventory['used'], hp_limit=self.energy.limit, + hp_interval=self.energy.interval, hp_available=self.energy.available, food=self.food['total'], ) + self.reporter.send_state_update(**data) + + def send_inventory_update(self): + j = 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) + + def check_house_durability(self) -> Dict[int, datetime.datetime]: + ret = {} + inv = self.update_inventory() + if "activeEnhancements" in inv.get("inventoryItems", {}): + active = inv.get("inventoryItems", {}).get("activeEnhancements", {}).get("items", {}) + for q in range(1, 6): + if "4_%i_active" % q in active: + till = utils.good_timedelta(self.now, datetime.timedelta( + seconds=active["4_%i_active" % q]["active"]["time_left"])) + ret.update({q: till}) + return ret + + def buy_and_activate_house(self, q: int) -> Dict[int, datetime.datetime]: + inventory = self.update_inventory() + ok_to_activate = False + if not inventory.get("inventoryItems").get("finalProducts", {}).get("items", {}).get("4_{}".format(q)): + offers = [] + countries = [self.details.citizenship, ] + if self.details.current_country != self.details.citizenship: + countries.append(self.details.current_country) + for country in countries: + offers += [self.get_market_offers(country, "house", q)] + global_cheapest = self.get_market_offers(product="house", quality=q) + cheapest_offer = sorted(offers, key=lambda o: o["price"])[0] + region = self.get_country_travel_region(global_cheapest['country']) + if global_cheapest['price'] + 200 < cheapest_offer['price'] and region: + self._travel(global_cheapest['country'], region) + buy = self.buy_from_market(global_cheapest['offer_id'], 1) + else: + buy = self.buy_from_market(cheapest_offer['offer_id'], 1) + if buy.json()["error"]: + msg = "Unable to buy q{} house! \n{}".format(q, buy.json()['message']) + self.write_log(msg) + else: + ok_to_activate = True + else: + ok_to_activate = True + if ok_to_activate: + self.activate_house(q) + return self.check_house_durability() + + def renew_houses(self, forced: bool = False) -> Dict[int, datetime.datetime]: + """ + Renew all houses which endtime is in next 48h + :param forced: if true - renew all houses + :return: + """ + house_durability = self.check_house_durability() + for q, active_till in house_durability.items(): + if utils.good_timedelta(active_till, - datetime.timedelta(hours=48)) <= self.now or forced: + house_durability = self.buy_and_activate_house(q) + self.travel_to_residence() + return house_durability + + def activate_house(self, quality: int) -> datetime.datetime: + active_until = self.now + r = self.post_economy_activate_house(self.token, quality) + if r.json().get("status") and not r.json().get("error"): + house = r.json()["inventoryItems"]["activeEnhancements"]["items"]["4_%i_active" % quality] + active_until = utils.good_timedelta(active_until, datetime.timedelta(seconds=house["active"]["time_left"])) + return active_until + + def collect_anniversary_reward(self) -> Response: + return self.post_collect_anniversary_reward(self.token) + + 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(self.token, battle_id, battle.zone_id, round_id, division, 1, True) + return {battle.invader.id: r.json().get(str(battle.invader.id)).get("fighterData"), + battle.defender.id: r.json().get(str(battle.defender.id)).get("fighterData")} + + def contribute_cc_to_country(self, amount: int or float = 0) -> bool: + self.update_money() + amount = int(amount) + if self.details.cc < amount or amount < 20: + return False + json = dict(country=71, action='currency', value=amount) + self.reporter.report_action("CONTRIBUTE_CC", json) + r = self.post_country_donate(self.token, **json) + return r.json().get('status') or not r.json().get('error') + + def contribute_food_to_country(self, amount: int = 0, quality: int = 1) -> bool: + self.update_inventory() + amount = amount // 1 + if self.food["q" + str(quality)] < amount or amount < 10: + return False + json = dict(country=71, action='food', value=amount, quality=quality) + self.reporter.report_action("CONTRIBUTE_FOOD", json) + r = self.post_country_donate(self.token, **json) + return r.json().get('status') or not r.json().get('error') + + def contribute_gold_to_country(self, amount: int) -> bool: + self.update_money() + + if self.details.cc < amount: + return False + json = dict(country=71, action='gold', value=amount) + self.reporter.report_action("CONTRIBUTE_GOLD", json) + r = self.post_country_donate(self.token, **json) + 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".*?.*""", + self.r.text, re.S | re.M) + if post_to_wall_as: + self.post_country_post_create(self.token, message, max(post_to_wall_as)) + return True + return False + + def report_error(self, msg: str = ""): + utils.process_error(msg, self.name, sys.exc_info(), self, self.commit_id, False) + + def get_battle_top_10(self, battle_id: int) -> Dict[int, List[Tuple[int, int]]]: + battle = self.all_battles.get(battle_id) + round_id = battle.get('zone_id') + division = self.division if round_id % 4 else 11 + + resp = self.post_military_battle_console(self.token, battle_id, round_id, division).json() + resp.pop('rounds', None) + ret = dict() + for country_id, data in resp.items(): + ret.update({int(country_id): []}) + for place in sorted(data.get("fighterData", {}).values(), key=lambda _: -_['raw_value']): + ret[int(country_id)].append((place['citizenId'], place['raw_value'])) + + return ret + + def to_json(self, indent: bool = False) -> str: + return dumps(self.__dict__, cls=utils.MyJSONEncoder, indent=4 if indent else None, sort_keys=True) diff --git a/erepublik_script/classes.py b/erepublik_script/classes.py new file mode 100644 index 0000000..af1147a --- /dev/null +++ b/erepublik_script/classes.py @@ -0,0 +1,1046 @@ +# pylint: disable=fixme, line-too-long, missing-docstring, invalid-name +import datetime +import decimal +import hashlib +import random +import time +from collections import deque +from json import JSONDecodeError, loads, JSONEncoder +from typing import Any, Dict, List, Union + +from requests import Response, Session +from slugify import slugify + +from erepublik_script import utils + + +class ErepublikException(Exception): + def __init__(self, message): + super().__init__(message) + + +class ErepublikNetworkException(Exception): + def __init__(self, message, request): + super().__init__(message) + self.request = request + + +class MyCompanies: + work_units: int = 0 + next_ot_time: datetime.datetime + holdings: Dict[int, Dict] = dict() + companies: Dict[int, Dict] = dict() + ff_lockdown: int = 0 + + def __init__(self): + self.next_ot_time = utils.now() + + def prepare_holdings(self, holdings: dict): + """ + :param holdings: Parsed JSON to dict from en/economy/myCompanies + """ + self.holdings = {} + template = dict(id=0, num_factories=0, region_id=0, companies=[]) + + for holding_id, holding in holdings.items(): + tmp: Dict[str, Union[List[Any], Any]] = {} + for key in template: + if key == 'companies': + tmp.update({key: []}) + else: + tmp.update({key: holding[key]}) + self.holdings.update({int(holding_id): tmp}) + self.holdings.update({0: template}) # unassigned + + def prepare_companies(self, companies: dict): + """ + :param companies: Parsed JSON to dict from en/economy/myCompanies + """ + self.companies = {} + template = dict(id=None, quality=0, is_raw=False, resource_bonus=0, effective_bonus=0, raw_usage=0, + 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) + + for c_id, company in companies.items(): + tmp = {} + for key in template.keys(): + if key in ['id', 'holding_company_id']: + company[key] = int(company[key]) + tmp.update({key: company[key]}) + self.companies.update({int(c_id): tmp}) + + def update_holding_companies(self): + for company_id, company_data in self.companies.items(): + if company_id not in self.holdings[company_data['holding_company_id']]['companies']: + self.holdings[company_data['holding_company_id']]['companies'].append(company_id) + else: + for holding_id in self.holdings: + self.holdings[holding_id]['companies'].sort() + + def get_employable_factories(self) -> Dict[int, int]: + ret = {} + for company_id, company in self.companies.items(): + if company.get('preset_works'): + preset_works: int = int(company.get('preset_works', 0)) + ret.update({company_id: preset_works}) + return ret + + def get_total_wam_count(self) -> int: + ret = 0 + for holding_id in self.holdings: + ret += self.get_holding_wam_count(holding_id) + return ret + + def get_holding_wam_count(self, holding_id: int, raw_factory=None) -> int: + """ + Returns amount of wam enabled companies in the holding + :param holding_id: holding id + :param raw_factory: True - only raw, False - only factories, None - both + :return: int + """ + return len(self.get_holding_wam_companies(holding_id, raw_factory)) + + def get_holding_employee_count(self, holding_id): + employee_count = 0 + if holding_id in self.holdings: + for company_id in self.holdings.get(holding_id, {}).get('companies', []): + employee_count += self.companies.get(company_id).get('preset_works', 0) + return employee_count + + def get_holding_wam_companies(self, holding_id: int, raw_factory: bool = None) -> List[int]: + """ + Returns WAM enabled companies in the holding, True - only raw, False - only factories, None - both + :param holding_id: holding id + :param raw_factory: bool or None + :return: list + """ + raw = [] + factory = [] + if holding_id in self.holdings: + for company_id in self.holdings.get(holding_id, {}).get('companies', []): + company = self.companies.get(company_id, {}) + wam_enabled = bool(company.get('wam_enabled', {})) + already_worked = not company.get('already_worked', {}) + cannot_work_war = company.get("cannot_work_as_manager_reason", {}) == "war" + if wam_enabled and already_worked and not cannot_work_war: + if company.get('is_raw', False): + raw.append(company_id) + else: + factory.append(company_id) + if raw_factory is not None and not raw_factory: + return factory + elif raw_factory is not None and raw_factory: + return raw + elif raw_factory is None: + return raw + factory + else: + raise ErepublikException("raw_factory should be True/False/None") + + def get_needed_inventory_usage(self, company_id: int = None, companies: list = None) -> float: + if not any([companies, company_id]): + return 0. + if company_id: + if company_id not in self.companies: + raise ErepublikException("Company ({}) not in all companies list".format(company_id)) + company = self.companies[company_id] + if company.get("is_raw"): + return float(company["base_production"]) * company["effective_bonus"] + else: + products_made = company["base_production"] * company["effective_bonus"] / 100 + # raw_used = products_made * company['upgrades'][str(company['quality'])]['raw_usage'] * 100 + return float(products_made - company['raw_usage']) + if companies: + return float(sum([self.get_needed_inventory_usage(company_id=cid) for cid in companies])) + + raise ErepublikException("Wrong function call") + + +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', + # 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', + ] + debug = False + + def __init__(self): + super().__init__() + self.request_log_name = utils.get_file(utils.now().strftime("debug/requests_%Y-%m-%d.log")) + self.last_time = utils.now() + self.headers.update({ + 'User-Agent': random.choice(self.uas) + }) + + @property + def __dict__(self): + return dict(last_time=self.last_time, timeout=self.timeout, user_agent=self.headers['User-Agent'], + request_log_name=self.request_log_name) + + def request(self, method, url, *args, **kwargs): + self._slow_down_requests() + self._log_request(url, method, **kwargs) + resp = super().request(method, url, *args, **kwargs) + self._log_response(url, resp) + return resp + + def _slow_down_requests(self): + ltt = utils.good_timedelta(self.last_time, self.timeout) + if ltt > utils.now(): + seconds = (ltt - utils.now()).total_seconds() + time.sleep(seconds if seconds > 0 else 0) + self.last_time = utils.now() + + def _log_request(self, url, method, data=None, json=None, params=None, **kwargs): + if self.debug: + args = {} + args.update({'kwargs': kwargs}) + if data: + args.update({"data": data}) + + if json: + args.update({"json": json}) + + if params: + args.update({"params": params}) + + body = "[{dt}]\tURL: '{url}'\tMETHOD: {met}\tARGS: {args}\n".format(dt=utils.now().strftime("%F %T"), + url=url, met=method, args=args) + + with open(self.request_log_name, 'ab') as file: + file.write(body.encode("UTF-8")) + + def _log_response(self, url, resp, redirect: bool = False): + from erepublik_script import Citizen + if self.debug: + if resp.history and not redirect: + for hist_resp in resp.history: + self._log_request(hist_resp.request.url, "REDIRECT") + self._log_response(hist_resp.request.url, hist_resp, redirect=True) + + # TODO: Must thoroughly check response writing on windows systems + file_data = { + "path": 'debug/requests', + "time": self.last_time.strftime('%Y-%m-%d_%H-%M-%S'), + "name": slugify(url[len(Citizen.url):]), + "extra": "_REDIRECT" if redirect else "" + } + + try: + loads(resp.text) + file_data.update({"ext": "json"}) + except JSONDecodeError: + file_data.update({"ext": "html"}) + + filename = 'debug/requests/{time}_{name}{extra}.{ext}'.format(**file_data) + with open(utils.get_file(filename), 'wb') as f: + f.write(resp.text.encode('utf-8')) + + +class Config: + email = "" + password = "" + work = True + train = True + wam = False + auto_sell: List[str] = list() + auto_sell_all = False + employees = False + fight = False + air = False + ground = False + all_in = False + next_energy = False + boosters = False + travel_to_fight = False + always_travel = False + epic_hunt = False + epic_hunt_ebs = False + rw_def_side = False + interactive = True + continuous_fighting = False + auto_buy_raw = False + force_wam = False + sort_battles_time = True + force_travel = False + + @property + def wt(self): + return self.work and self.train + + 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, + ) + + +class Energy: + limit = 500 # energyToRecover + interval = 10 # energyPerInterval + recoverable = 0 # energyFromFoodRemaining + recovered = 0 # energy + _recovery_time = None + + def __init__(self): + self._recovery_time = utils.now() + + def __repr__(self): + return "{:4}/{:4} + {:4}, {:3}hp/6min".format(self.recovered, self.limit, self.recoverable, self.interval) + + def set_reference_time(self, recovery_time: datetime.datetime): + self._recovery_time = recovery_time.replace(microsecond=0) + + @property + def food_fights(self): + return (self.recoverable + self.recovered) // 10 + + @property + def reference_time(self): + if self.is_recovered_full or self._recovery_time < utils.now(): + ret = utils.now() + else: + ret = self._recovery_time + return ret + + @property + def is_recoverable_full(self): + return self.recoverable >= self.limit - self.interval + + @property + def is_recovered_full(self): + return self.recovered >= self.limit - self.interval + + @property + def is_energy_full(self): + return self.is_recoverable_full and self.is_recovered_full + + @property + def available(self): + return self.recovered + self.recoverable + + def __dict__(self): + return dict( + limit=self.limit, + interval=self.interval, + recoverable=self.recoverable, + recovered=self.recovered, + reference_time=self.reference_time + ) + + +class Details(object): + xp = 0 + cc = 0 + pp = 0 + pin = None + gold = 0 + next_pp: List[int] = [] + citizen_id = 0 + citizenship = 0 + current_region = 0 + current_country = 0 + residence_region = 0 + residence_country = 0 + daily_task_done = False + daily_task_reward = False + mayhem_skills = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, } + + @property + def xp_till_level_up(self): + if self.xp >= 10000: + next_level_up = (1 + (self.xp // 5000)) * 5000 + elif self.xp >= 7000: + next_level_up = 10000 + elif self.xp >= 3000: + next_level_up = (1 + ((self.xp - 1000) // 2000)) * 2000 + 1000 + elif self.xp >= 2000: + next_level_up = 3000 + elif self.xp >= 450: + next_level_up = (1 + (self.xp // 500)) * 500 + elif self.xp >= 370: + next_level_up = (1 + ((self.xp - 10) // 40)) * 40 + 10 + elif self.xp >= 300: + next_level_up = (1 + ((self.xp - 20) // 35)) * 35 + 20 + elif self.xp >= 150: + next_level_up = (1 + (self.xp // 30)) * 30 + elif self.xp >= 50: + next_level_up = (1 + ((self.xp - 10) // 20)) * 20 + 10 + elif self.xp >= 20: + next_level_up = (1 + ((self.xp - 5) // 15)) * 15 + 5 + else: + next_level_up = (1 + (self.xp // 10)) * 10 + return next_level_up - self.xp + + +class Politics: + is_party_member: bool = False + + party_id: int = 0 + party_slug: str = "" + is_party_president: bool = False + is_congressman: bool = False + is_country_president: bool = False + + +class House(object): + quality = None + unactivated_count = 0 + active_untill = utils.good_timedelta(utils.now(), -datetime.timedelta(days=1)) + + def __init__(self, quality: int): + if 0 < quality < 6: + self.quality = quality + + @property + def next_ot_point(self) -> datetime.datetime: + return self.active_untill + + +class CitizenAPI: + url = "https://www.erepublik.com/en" + _req = SlowRequests + + def __init__(self): + self._req = SlowRequests() + + def post(self, url: str, *args, **kwargs) -> Response: + return self._req.post(url, *args, **kwargs) + + def get(self, url: str, **kwargs) -> Response: + return self._req.get(url, **kwargs) + + def get_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: + 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: + return self.get("{}/main/citizen-hovercard/{}".format(self.url, citizen)) + + def get_citizen_profile(self, player_id: int): + return self.get("{}/main/citizen-profile-json/{}".format(self.url, player_id)) + + def get_citizen_daily_assistant(self): + return self.get("{}/main/citizenDailyAssistant".format(self.url)) + + def get_city_data_residents(self, city: int, page: int = 1, params: Dict[str, Any] = {}): + return self.get("{}/main/city-data/{}/residents".format(self.url, city), params={"currentPage": page, **params}) + + def get_country_military(self, country: str) -> Response: + return self.get("{}/country/military/{}".format(self.url, country)) + + def get_economy_inventory_items(self) -> Response: + return self.get("{}/economy/inventory-items/".format(self.url)) + + def get_economy_job_market_json(self, country: int) -> Response: + return self.get("{}/economy/job-market-json/{}/1/desc".format(self.url, country)) + + def get_economy_my_companies(self) -> Response: + return self.get("{}/economy/myCompanies".format(self.url)) + + def get_economy_my_market_offers(self) -> Response: + return self.get("{}/economy/myMarketOffers".format(self.url)) + + def get_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: + 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: + 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: + 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: + data = (country, weeks, mu, div) + return self.get("{}/main/leaderboards-kills-rankings/{}/{}/{}/{}".format(self.url, *data)) + + def get_main(self): + return self.get(self.url) + + def get_message_alerts(self, page: int = 1) -> Response: + return self.get_message_alerts(page) + + def get_military_campaigns(self) -> Response: + return self.get("{}/military/campaigns-new/".format(self.url)) + + def get_military_unit_data(self, unit_id: int, page: int = 1) -> Response: + params = {"groupId": unit_id, "panel": "members", "currentPage": page} + return self.get("{}/military/military-unit-data/".format(self.url), params=params) + + def get_money_donation_accept(self, token: str, donation_id: int) -> Response: + return self.get("{}/main/money-donation/accept/{}".format(self.url, donation_id), params={"_token": token}) + + def get_money_donation_reject(self, token: str, donation_id: int) -> Response: + return self.get("{}/main/money-donation/reject/{}".format(self.url, donation_id), params={"_token": token}) + + def get_party_members(self, party: int) -> Response: + return self.get("{}/main/party-members/{}".format(self.url, party)) + + def get_rankings_parties(self, country: int) -> Response: + return self.get("{}/main/rankings-parties/1/{}".format(self.url, country)) + + def get_training_grounds_json(self) -> Response: + return self.get("{}/main/training-grounds-json".format(self.url)) + + def get_weekly_challenge_data(self) -> Response: + return self.get("{}/main/weekly-challenge-data".format(self.url)) + + def post_activate_battle_effect(self, token: str, battle: int, kind: str, citizen_id: int) -> Response: + data = dict(battleId=battle, citizenId=citizen_id, type=kind, _token=token) + return self.post("{}/main/fight-activateBattleEffect".format(self.url), data=data) + + def post_article_comments(self, token: str, article: int, page: int = 0) -> Response: + data = dict(_token=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, token: str, message: str, article: int, parent: int = 0) -> Response: + data = dict(_token=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, token: str, 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=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_buy_gold_items(self, token: str, currency: str, item: str, amount: int) -> Response: + data = dict(itemId=item, currency=currency, amount=amount, _token=token) + return self.post("{}/main/buyGoldItems".format(self.url), data=data) + + def post_candidate_for_congress(self, token: str, presentation: str = "") -> Response: + data = dict(_token=token, presentation=presentation) + return self.post("{}/candidate-for-congress".format(self.url), data=data) + + def post_citizen_add_remove_friend(self, token: str, citizen: int, add: bool) -> Response: + data = dict(_token=token, citizenId=citizen, url="//www.erepublik.com/en/main/citizen-addRemoveFriend") + if add: + data.update({"action": "addFriend"}) + else: + data.update({"action": "removeFriend"}) + return self.post("{}/main/citizen-addRemoveFriend".format(self.url), data=data) + + def post_collect_anniversary_reward(self, token: str) -> Response: + return self.post("{}/main/collect-anniversary-reward".format(self.url), data={"_token": token}) + + def post_country_donate(self, token: str, country: int, action: str, value: Union[int, float], quality: int = None): + json = dict(countryId=country, action=action, _token=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, token: str) -> Response: + return self.post("{}/main/daily-tasks-reward".format(self.url), data=dict(_token=token)) + + def post_delete_message(self, token: str, msg_id: list) -> Response: + data = {"_token": token, "delete_message[]": msg_id} + return self.post("{}/main/messages-delete".format(self.url), data) + + def post_eat(self, token: str, color: str) -> Response: + data = dict(_token=token, buttonColor=color) + return self.post("{}/main/eat".format(self.url), params=data) + + def post_economy_activate_house(self, token: str, quality: int) -> Response: + data = {"action": "activate", "quality": quality, "type": "house", "_token": token} + return self.post("{}/economy/activateHouse".format(self.url), data=data) + + def post_economy_assign_to_holding(self, token: str, factory: int, holding: int) -> Response: + data = dict(_token=token, factoryId=factory, action="assign", holdingCompanyId=holding) + return self.post("{}/economy/assign-to-holding".format(self.url), data=data) + + def post_economy_create_company(self, token: str, industry: int, building_type: int = 1) -> Response: + data = {"_token": token, "company[industry_id]": industry, "company[building_type]": building_type} + return self.post("{}/economy/create-company".format(self.url), data=data, + headers={"Referer": "{}/economy/create-company".format(self.url)}) + + def post_economy_donate_items_action(self, token: str, citizen: int, amount: int, industry: int, + quality: int) -> Response: + data = dict(citizen_id=citizen, amount=amount, industry_id=industry, quality=quality, _token=token) + return self.post("{}/economy/donate-items-action".format(self.url), data=data, + headers={"Referer": "{}/economy/donate-items/{}".format(self.url, citizen)}) + + def post_economy_donate_money_action(self, token: str, citizen: int, amount: float = 0.0, + currency: int = 62) -> Response: + data = dict(citizen_id=citizen, _token=token, currency_id=currency, amount=amount) + return self.post("{}/economy/donate-money-action".format(self.url), data=data, + headers={"Referer": "{}/economy/donate-money/{}".format(self.url, citizen)}) + + def post_economy_exchange_purchase(self, token: str, amount: float, currency: int, offer: int) -> Response: + data = dict(_token=token, amount=amount, currencyId=currency, offerId=offer) + return self.post("{}/economy/exchange/purchase/".format(self.url), data=data) + + def post_economy_exchange_retrieve(self, token: str, personal: bool, page: int, currency: int) -> Response: + data = dict(_token=token, personalOffers=int(personal), page=page, currencyId=currency) + return self.post("{}/economy/exchange/retrieve/".format(self.url), data=data) + + def post_economy_job_market_apply(self, token: str, citizen: int, salary: int) -> Response: + data = dict(_token=token, citizenId=citizen, salary=salary) + return self.post("{}/economy/job-market-apply".format(self.url), data=data) + + def post_economy_marketplace(self, token: str, country: int, industry: int, quality: int, + order_asc: bool = True) -> Response: + data = dict(countryId=country, industryId=industry, quality=quality, ajaxMarket=1, + orderBy="price_asc" if order_asc else "price_desc", _token=token) + return self.post("{}/economy/marketplaceAjax".format(self.url), data=data) + + def post_economy_marketplace_actions(self, token: str, amount: int, buy: bool = False, **kwargs) -> Response: + if buy: + data = dict(_token=token, offerId=kwargs['offer'], amount=amount, orderBy="price_asc", currentPage=1, + buyAction=1) + else: + data = dict(_token=token, countryId=kwargs["country"], price=kwargs["price"], industryId=kwargs["industry"], + quality=kwargs["quality"], amount=amount, sellAction='postOffer') + return self.post("{}/economy/marketplaceActions".format(self.url), data=data) + + def post_economy_resign(self, token: str) -> Response: + return self.post("{}/economy/resign".format(self.url), + headers={"Content-Type": "application/x-www-form-urlencoded"}, + data={"_token": token, "action_type": "resign"}) + + def post_economy_sell_company(self, token: str, factory: int, pin: int = None, sell: bool = True) -> Response: + url = "{}/economy/sell-company/{}".format(self.url, factory) + data = dict(_token=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}) + + def post_economy_train(self, token: str, tg_ids: List[int]) -> Response: + data: Dict[str, Union[int, str]] = {} + if not tg_ids: + return self.get_training_grounds_json() + else: + for idx, tg_id in enumerate(tg_ids): + data["grounds[%i][id]" % idx] = tg_id + data["grounds[%i][train]" % idx] = 1 + if data: + data['_token'] = token + return self.post("{}/economy/train".format(self.url), data=data) + + def post_economy_upgrade_company(self, token: str, factory: int, level: int, pin: str = None) -> Response: + data = dict(_token=token, type="upgrade", companyId=factory, level=level, pin="" if pin is None else pin) + return self.post("{}/economy/upgrade-company".format(self.url), data=data) + + def post_economy_work(self, token: str, action_type: str, wam: List[int] = None, employ: Dict[int, int] = None): + """ + :return: requests.Response or None + """ + if employ is None: + employ = dict() + if wam is None: + wam = [] + data: Dict[str, Union[int, str]] = dict(action_type=action_type, _token=token) + if action_type == "work": + return self.post("{}/economy/work".format(self.url), data=data) + elif action_type == "production": + max_idx = 0 + for idx, company_id in enumerate(sorted(wam or [])): + data.update({ + "companies[%i][id]" % idx: company_id, + "companies[%i][employee_works]" % idx: employ.pop(company_id, 0), + "companies[%i][own_work]" % idx: 1 + }) + max_idx = idx + 1 + for idx, company_id in enumerate(sorted(employ or [])): + idx_ = idx + max_idx + data.update({ + "companies[%i][id]" % idx_: company_id, + "companies[%i][employee_works]" % idx_: employ.pop(company_id), + "companies[%i][own_work]" % idx_: 0 + }) + return self.post("{}/economy/work".format(self.url), data=data) + else: + return + + def post_economy_work_overtime(self, token: str) -> Response: + data = dict(action_type="workOvertime", _token=token) + return self.post("{}/economy/workOvertime".format(self.url), data=data) + + def post_forgot_password(self, token: str, email: str) -> Response: + data = dict(_token=token, email=email, commit="Reset password") + return self.post("{}/forgot-password".format(self.url), data=data) + + def post_fight_activate_booster(self, token: str, battle: int, quality: int, duration: int, kind: str) -> Response: + data = dict(type=kind, quality=quality, duration=duration, battleId=battle, _token=token) + return self.post("{}/military/fight-activateBooster".format(self.url), data=data) + + def post_login(self, token: str, email: str, password: str) -> Response: + data = dict(csrf_token=token, citizen_email=email, citizen_password=password, remember='on') + return self.post("{}/login".format(self.url), data=data) + + def post_messages_alert(self, token: str, notification_ids: list) -> Response: + data = {"_token": 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, token: str, 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=token, citizen_message=body) + return self.post("{}/main/messages-compose/{}}".format(self.url, url_pk), data=data) + + def post_military_battle_console(self, token: str, battle_id: int, round_id: int, division: int) -> Response: + data = dict(battleId=battle_id, zoneId=round_id, action="battleStatistics", round=round_id, division=division, + type="damage", leftPage=1, rightPage=1, _token=token) + return self.post("{}/military/battle-console".format(self.url, battle_id), data=data) + + def post_military_fight_air(self, token: str, battle_id: int, side_id: int) -> Response: + data = dict(sideId=side_id, battleId=battle_id, _token=token) + return self.post("{}/military/fight-shoooot/{}".format(self.url, battle_id), data=data) + + def post_military_fight_ground(self, token: str, battle_id: int, side_id: int) -> Response: + data = dict(sideId=side_id, battleId=battle_id, _token=token) + return self.post("{}/military/fight-shooot/{}".format(self.url, battle_id), data=data) + + def post_military_group_missions(self, token: str) -> Response: + data = dict(action="check", _token=token) + return self.post("{}/military/group-missions".format(self.url), data=data) + + def post_travel(self, token: str, check: str, **kwargs) -> Response: + data = dict(_token=token, check=check, **kwargs) + return self.post("{}/main/travel".format(self.url), data=data) + + def post_travel_data(self, token: str, **kwargs) -> Response: + return self.post("{}/main/travelData".format(self.url), data=dict(_token=token, **kwargs)) + + def post_wars_attack_region(self, token: str, war: int, region: int) -> Response: + data = dict(_token=token) + return self.post("{}/wars/attack-region/{}/{}".format(self.url, war, region), data=data) + + def post_weekly_challenge_reward(self, token: str, reward_id: int) -> Response: + data = dict(_token=token, rewardId=reward_id) + return self.post("{}/main/weekly-challenge-collect-reward".format(self.url), data=data) + + def post_write_article(self, token: str, title: str, content: str, location: int, kind: int) -> Response: + data = dict(_token=token, article_name=title, article_body=content, article_location=location, + article_category=kind) + return self.post("{}/main/write-article".format(self.url), data=data) + + # Wall Posts + # ## Country + + def post_country_comment_retrieve(self, token: str, post_id: int): + data = {"_token": token, "postId": post_id} + return self.post("{}/main/country-comment/retrieve/".format(self.url), data=data) + + def post_country_post_create(self, token: str, body: str, post_as: int): + data = {"_token": token, "post_message": body, "post_as": post_as} + return self.post("{}/main/country-post/create/".format(self.url), data=data) + + def post_country_post_retrieve(self, token: str): + data = {"_token": token, "page": 1, "switchedFrom": False} + return self.post("{}/main/country-post/retrieve/".format(self.url), data=data) + + # ## Military Unit + + def post_military_unit_comment_retrieve(self, token: str, post_id: int): + data = {"_token": token, "postId": post_id} + return self.post("{}/main/military-unit-comment/retrieve/".format(self.url), data=data) + + def post_military_unit_post_create(self, token: str, body: str, post_as: int): + data = {"_token": token, "post_message": body, "post_as": post_as} + return self.post("{}/main/military-unit-post/create/".format(self.url), data=data) + + def post_military_unit_post_retrieve(self, token: str): + data = {"_token": token, "page": 1, "switchedFrom": False} + return self.post("{}/main/military-unit-post/retrieve/".format(self.url), data=data) + + # ## Party + + def post_party_comment_retrieve(self, token: str, post_id: int): + data = {"_token": token, "postId": post_id} + return self.post("{}/main/party-comment/retrieve/".format(self.url), data=data) + + def post_party_post_create(self, token: str, body: str): + data = {"_token": token, "post_message": body} + return self.post("{}/main/party-post/create/".format(self.url), data=data) + + def post_party_post_retrieve(self, token: str): + data = {"_token": token, "page": 1, "switchedFrom": False} + return self.post("{}/main/party-post/retrieve/".format(self.url), data=data) + + # ## Friend's Wall + + def post_wall_comment_retrieve(self, token: str, post_id: int): + data = {"_token": token, "postId": post_id} + return self.post("{}/main/wall-comment/retrieve/".format(self.url), data=data) + + def post_wall_post_create(self, token: str, body: str): + data = {"_token": token, "post_message": body} + return self.post("{}/main/wall-post/create/".format(self.url), data=data) + + def post_wall_post_retrieve(self, token: str): + data = {"_token": token, "page": 1, "switchedFrom": False} + return self.post("{}/main/wall-post/retrieve/".format(self.url), data=data) + + +class Reporter: + __to_update: List[Dict[Any, Any]] = [] + name: str = "" + email: str = "" + citizen_id: int = 0 + key: str = "" + allowed: bool = False + + def __init__(self): + self._req = Session() + self.url = "https://api.erep.lv" + self._req.headers.update({"user-agent": "Bot reporter v2"}) + self.__registered: bool = False + + @property + def __dict__(self): + return dict(allowed=self.allowed, __to_update=self.__to_update) + + def do_init(self, name: str = "", email: str = "", citizen_id: int = 0): + self.name: str = name + self.email: str = email + self.citizen_id: int = citizen_id + self.key: str = "" + self.__update_key() + self.allowed = True + + def __update_key(self): + self.key = hashlib.md5(bytes(f"{self.name}:{self.email}", encoding="UTF-8")).hexdigest() + self.allowed = True + self.register_account() + + def __bot_update(self, data: dict) -> Response: + if self.__to_update: + for unreported_data in self.__to_update: + unreported_data.update(player_id=self.citizen_id, key=self.key) + self._req.post("{}/bot/update".format(self.url), json=unreported_data) + self.__to_update.clear() + r = self._req.post("{}/bot/update".format(self.url), json=data) + return r + + def register_account(self): + if not self.__registered: + try: + r = self.__bot_update(dict(key=self.key, check=True, player_id=self.citizen_id)) + if not r.json().get("status"): + self._req.post("{}/bot/register".format(self.url), json=dict(name=self.name, email=self.email, + player_id=self.citizen_id)) + finally: + self.__registered = True + self.report_action("STARTED", value=utils.now().strftime("%F %T")) + + def send_state_update(self, xp: int, cc: float, gold: float, inv_total: int, inv: int, + hp_limit: int, hp_interval: int, hp_available: int, food: int, pp: int): + + data = dict(key=self.key, player_id=self.citizen_id, state=dict( + xp=xp, cc=cc, gold=gold, inv_total=inv_total, inv_free=inv_total - inv, inv=inv, food=food, + pp=pp, hp_limit=hp_limit, hp_interval=hp_interval, hp_available=hp_available, + )) + + if self.allowed: + self.__bot_update(data) + + def report_action(self, action: str, json_val: Dict[Any, Any] = None, value: str = None): + if not self.key: + if not all([self.email, self.name, self.citizen_id]): + pass + json_data = {'player_id': self.citizen_id, 'key': self.key, 'log': dict(action=action)} + if json_val: + json_data['log'].update(dict(json=json_val)) + if value: + json_data['log'].update(dict(value=value)) + if self.allowed: + self.__bot_update(json_data) + else: + self.__to_update.append(json_data) + + +class MyJSONEncoder(JSONEncoder): + def default(self, o): + 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) + 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(_content=o._content.decode("UTF-8"), 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) + + +class BattleSide: + id: int + points: int + deployed: List[int] = list() + allies: List[int] = list() + + def __init__(self, country_id: int, points: int, allies: List[int], deployed: List[int]): + self.id = country_id + self.points = points + self.allies = [int(ally) for ally in allies] + self.deployed = [int(ally) for ally in deployed] + + +class BattleDivision: + end: datetime.datetime + epic: bool + dom_pts: Dict[str, int] = dict() + wall: Dict[str, Union[int, float]] = dict() + + @property + def div_end(self) -> bool: + return utils.now() >= self.end + + def __init__(self, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int, wall_for: int, wall_dom: float): + self.end = end + self.epic = epic + self.dom_pts.update({"inv": inv_pts, "def": def_pts}) + self.wall.update({"for": wall_for, "dom": wall_dom}) + + +class Battle(object): + id: int = 0 + war_id: int = 0 + zone_id: int = 0 + is_rw: bool = False + is_dict_lib: bool = False + start: datetime.datetime = None + invader: BattleSide = None + defender: BattleSide = None + div: Dict[int, BattleDivision] = dict() + + @property + def is_air(self) -> bool: + return not bool(self.zone_id % 4) + + def __init__(self, battle: dict): + self.id = int(battle.get('id', 0)) + self.war_id = int(battle.get('war_id', 0)) + self.zone_id = int(battle.get('zone_id', 0)) + self.is_rw = bool(battle.get('is_rw')) + self.is_as = bool(battle.get('is_as')) + self.is_dict_lib = bool(battle.get('is_dict')) or bool(battle.get('is_lib')) + self.start = datetime.datetime.fromtimestamp(int(battle.get('start', 0)), tz=utils.erep_tz) + + self.invader = BattleSide(battle.get('inv', {}).get('id'), battle.get('inv', {}).get('points'), + [row.get('id') for row in battle.get('inv', {}).get('ally_list')], + [row.get('id') for row in battle.get('inv', {}).get('ally_list') if row['deployed']]) + + self.defender = BattleSide(battle.get('def', {}).get('id'), battle.get('def', {}).get('points'), + [row.get('id') for row in battle.get('def', {}).get('ally_list')], + [row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']]) + + for div, data in battle.get('div', {}).items(): + div = int(div) + if data.get('end'): + end = datetime.datetime.fromtimestamp(data.get('end'), tz=utils.erep_tz) + else: + end = datetime.datetime.max + + self.div.update({div: BattleDivision(end, data.get('epic_type') in [1, 5], + data.get('dom_pts').get("inv"), data.get('dom_pts').get("def"), + data.get('wall').get("for"), data.get('wall').get("dom"))}) + + def __repr__(self): + now = utils.now() + is_started = self.start < utils.now() + if is_started: + timepart = "{}".format(now - self.start) + else: + timepart = "- {}".format(self.start - now) + return "Battle {} | {:>21.21}:{:<21.21} | Round {:2} | Start {}".format(self.id, + utils.COUNTRIES[self.invader.id], + utils.COUNTRIES[self.defender.id], + self.zone_id, timepart) + + +class EnergyToFight: + energy: int = 0 + + def __init__(self, energy: int = 0): + self.energy = energy + + def __int__(self): + return self.energy + + def __str__(self): + return str(self.energy) + + def __repr__(self): + return str(self.energy) + + @property + def i(self): + return self.__int__() + + @property + def s(self): + return self.__str__() + + def check(self, new_energy: int): + if not isinstance(new_energy, (tuple, int)): + return self.energy + if 0 < new_energy < self.energy: + self.energy = new_energy + return self.energy diff --git a/erepublik_script/cli.py b/erepublik_script/cli.py new file mode 100644 index 0000000..fbd007c --- /dev/null +++ b/erepublik_script/cli.py @@ -0,0 +1,423 @@ +# -*- coding: utf-8 -*- + +"""Console script for erepublik_script.""" + +__author__ = """Eriks Karls""" +__email__ = 'eriks@72.lv' +__version__ = '0.1.0' + +import json +import os +import random +import sys +import threading +from collections import defaultdict +from datetime import timedelta +from typing import List, Tuple + +import click + +from erepublik_script import classes, utils +from erepublik_script.citizen import Citizen + + +__all__ = ["Citizen"] + +INTERACTIVE = True +CONFIG = defaultdict(bool) + + +@click.command() +def main(args=None): + player = None + try: # If errors before player is initialized + while True: + player = Citizen(email=CONFIG['email'], password=CONFIG['password']) + if player.logged_in: + break + utils.silent_sleep(2) + player.config.work = CONFIG['work'] + player.config.train = CONFIG['train'] + player.config.ot = CONFIG['ot'] + player.config.wam = bool(CONFIG['wam']) + player.config.employees = bool(CONFIG['employ']) + player.config.auto_sell = CONFIG.get('auto_sell', []) + player.config.auto_sell_all = CONFIG.get('auto_sell_all', False) + player.config.auto_buy_raw = CONFIG.get('auto_buy_raw', False) + player.config.force_wam = CONFIG.get('force_wam', False) + player.config.fight = CONFIG['fight'] + player.config.air = CONFIG['air'] + player.config.ground = CONFIG['ground'] + player.config.all_in = CONFIG['all_in'] + player.config.next_energy = CONFIG['next_energy'] + player.config.boosters = CONFIG['boosters'] + player.config.travel_to_fight = CONFIG['travel_to_fight'] + player.config.always_travel = CONFIG.get('always_travel', False) + player.config.epic_hunt = CONFIG['epic_hunt'] + player.config.epic_hunt_ebs = CONFIG['epic_hunt_ebs'] + player.config.rw_def_side = CONFIG['rw_def_side'] + player.config.random_sleep = CONFIG['random_sleep'] + player.config.continuous_fighting = CONFIG['continuous_fighting'] + player.config.interactive = CONFIG['interactive'] + player.reporter.allowed = not CONFIG.get('reporting_is_not_allowed') + + player.set_debug(CONFIG.get('debug', False)) + while True: + try: + player.update_all() + break + except: + utils.silent_sleep(2) + + now = utils.now() + dt_max = now.replace(year=9999) + tasks = { + 'eat': now, + } + wam_hour = employ_hour = 14 + if player.config.work: + tasks.update({'work': now}) + if player.config.train: + tasks.update({'train': now}) + if player.config.ot: + tasks.update({'ot': now}) + if player.config.fight: + tasks.update({'fight': now}) + if player.config.wam: + wam_hour = 14 + if not isinstance(CONFIG['wam'], bool): + try: + wam_hour = abs(int(CONFIG['wam'])) % 24 + except ValueError: + pass + tasks.update({'wam': now.replace(hour=wam_hour, minute=0, second=0, microsecond=0)}) + if player.config.employees: + employ_hour = 8 + if not isinstance(CONFIG['employ'], bool): + try: + employ_hour = abs(int(CONFIG['employ'])) % 24 + except ValueError: + pass + tasks.update({'employ': now.replace(hour=employ_hour, minute=0, second=0, microsecond=0)}) + + if player.config.epic_hunt: + tasks['epic_hunt'] = now + + if CONFIG.get("renew_houses", True): + tasks['renew_houses'] = now + + if CONFIG.get('start_battles'): + """ {'start_battle': {war_id: {'regions': [region_id, ], + 'timing': ['at', 'hh:mm' | 'before', 'hh:mm' (before autoattack) | + 'auto' (after round for citizenship country's oldest battle or at 00:00) + 'rw', (after first round of RW if you are occupying)]}} """ + player.allowed_battles = CONFIG.get('start_battles', dict()) + raise classes.ErepublikException("Battle starting is not implemented") + + if player.reporter.allowed: + report = dict(CONFIG) + report.pop("email", None) + report.pop("password", None) + report.update( + VERSION=utils.VERSION, + COMMIT_ID=utils.COMMIT_ID + ) + player.reporter.report_action("ACTIVE_CONFIG", json_val=report) + # -1 because main thread is counted in + name = "{}-state_updater-{}".format(player.name, threading.active_count() - 1) + state_thread = threading.Thread(target=player.state_update_repeater, name=name) + state_thread.start() + + if CONFIG.get("congress", True): + tasks['congress'] = now.replace(hour=1, minute=30, second=0) + + if CONFIG.get("party_president", False): + tasks['party_president'] = now.replace(hour=1, minute=30, second=0) + + contribute_cc = int(CONFIG.get("contribute_cc", 0)) + if contribute_cc: + tasks['contribute_cc'] = now.replace(hour=2, minute=0, second=0) + + if CONFIG.get("gold_buy"): + tasks['gold_buy'] = now.replace(hour=23, minute=57, second=0, microsecond=0) + + error_count = 0 + while error_count < 3: + try: + now = utils.now() + player.update_all() + if tasks.get('work', dt_max) <= now: + player.write_log("Doing task: work") + player.update_citizen_info() + player.work() + if player.config.ot: + tasks['ot'] = now + player.collect_daily_task() + next_time = now.replace(hour=0, minute=0, second=0) + timedelta(days=1) + tasks.update({'work': next_time}) + + if tasks.get('train', dt_max) <= now: + player.write_log("Doing task: train") + player.update_citizen_info() + player.train() + player.collect_daily_task() + next_time = now.replace(hour=0, minute=0, second=0) + timedelta(days=1) + tasks.update({'train': next_time}) + + if tasks.get('wam', dt_max) <= now: + player.write_log("Doing task: Work as manager") + success = player.work_wam() + player.eat() + if success: + next_time = now.replace(hour=wam_hour, minute=0, second=0, microsecond=0) + timedelta(days=1) + else: + next_time = now.replace(second=0, microsecond=0) + timedelta(minutes=30) + + tasks.update({'wam': next_time}) + + if tasks.get('eat', dt_max) <= now: + player.write_log("Doing task: eat") + player.eat() + + if player.energy.food_fights > player.energy.limit // 10: + next_minutes = 12 + else: + next_minutes = (player.energy.limit - 5 * player.energy.interval) // player.energy.interval * 6 + + next_time = player.energy.reference_time + timedelta(minutes=next_minutes) + tasks.update({'eat': next_time}) + + if tasks.get('fight', dt_max) <= now or player.energy.is_energy_full: + fight_energy_debug_log: List[Tuple[int, str]] = [] + player.write_log("Doing task: fight") + player.write_log(player.health_info) + + if player.should_fight(): + player.find_battle_and_fight() + else: + player.collect_weekly_reward() + energy = classes.EnergyToFight(player.details.xp_till_level_up * 10 - player.energy.limit + 50) + fight_energy_debug_log.append(( + energy.i, + f"Levelup reachable {player.details.xp_till_level_up} * 10 - {player.energy.limit} + 50" + )) + + # Do levelup + energy.check(player.details.xp_till_level_up * 10 + 50) + fight_energy_debug_log.append(( + energy.i, f"Levelup {player.details.xp_till_level_up} * 10 + 50" + )) + + # if levelup is close stop queueing other fighting + if not player.is_levelup_close: + + # Obligatory need 75pp + if player.details.pp < 75: + energy.check(75 - player.details.pp) + fight_energy_debug_log.append((energy.i, f"Obligatory need 75pp: 75 - {player.details.pp}")) + + if player.config.continuous_fighting and player.has_battle_contribution: + energy.check(player.energy.interval) + fight_energy_debug_log.append((energy.i, f"continuous_fighting: {player.energy.interval}")) + + # All-in + if player.config.all_in: + energy.check(player.energy.limit * 2 - 3 * player.energy.interval) + fight_energy_debug_log.append(( + energy.i, f"All-in: {player.energy.limit} * 2 - 3 * {player.energy.interval}" + )) + elif player.energy.limit * 2 - 3 * player.energy.interval >= player.energy.recovered: + # 1h worth of energy + energy.check(player.energy.limit * 2 - 3 * player.energy.interval) + fight_energy_debug_log.append( + (energy.i, f"1h worth of energy: {player.energy.interval} * 10" + )) + + # All-in for AIR battles + if all([player.config.air, player.config.all_in, + player.energy.available >= player.energy.limit]): + energy.check(player.energy.limit) + fight_energy_debug_log.append(( + energy.i, f"All-in for AIR battles: {player.energy.limit}" + )) + + # Get to next Energy +1 + if player.next_reachable_energy and player.config.next_energy: + energy.check(player.next_reachable_energy * 10) + fight_energy_debug_log.append(( + energy.i, f"Get to next Energy +1: {player.next_reachable_energy} * 10" + )) + + energy = energy.i - player.energy.available + next_minutes = max([6, abs(energy) // player.energy.interval * 6]) + # utils.write_silent_log("\n".join([f"{energy} {info}" for energy, info in fight_energy_debug_log])) + next_time = player.energy.reference_time + timedelta(minutes=next_minutes) + tasks.update({'fight': next_time}) + + if tasks.get('ot', dt_max) <= now: + player.write_log("Doing task: ot") + if now > player.my_companies.next_ot_time: + player.work_ot() + next_time = now + timedelta(minutes=60) + else: + next_time = player.my_companies.next_ot_time + tasks.update({'ot': next_time}) + + if tasks.get('employ', dt_max) <= now: + player.write_log("Doing task: Employee work") + next_time = utils.now().replace(hour=employ_hour, minute=0, second=0) + timedelta(days=1) + next_time = next_time if player.work_employees() else tasks.get('employ') + timedelta(minutes=30) + tasks.update({'employ': next_time}) + + if tasks.get('epic_hunt', dt_max) <= now: + player.write_log("Doing task: EPIC check") + player.check_epic_battles() + if player.active_fs: + next_time = now + timedelta(minutes=1) + else: + next_time = tasks.get('eat') + tasks.update({'epic_hunt': next_time}) + + if tasks.get('gold_buy', dt_max) <= now: + player.write_log("Doing task: auto buy 10g") + for offer in player.get_monetary_offers(): + if offer['amount'] >= 10 and player.details.cc >= 20 * offer["price"]: + # TODO: check allowed amount to buy + if player.buy_monetary_market_offer(offer=offer['offer_id'], amount=10, currency=62): + break + + next_time = tasks.get('gold_buy') + timedelta(days=1) + tasks.update({'gold_buy': next_time}) + + if tasks.get('congress', dt_max) <= now: + if 1 <= now.day < 16: + next_time = now.replace(day=16) + elif 16 <= now.day < 24: + player.write_log("Doing task: candidate for congress") + player.candidate_for_congress() + if not now.month == 12: + next_time = now.replace(month=now.month + 1, day=16) + else: + next_time = now.replace(year=now.year + 1, month=1, day=16) + else: + if not now.month == 12: + next_time = now.replace(month=now.month + 1, day=16) + else: + next_time = now.replace(year=now.year + 1, month=1, day=16) + tasks.update({'congress': next_time.replace(hour=1, minute=30, second=0, microsecond=0)}) + + if tasks.get('party_president', dt_max) <= now: + if not now.day == 15: + player.write_log("Doing task: candidate for party president") + player.candidate_for_party_presidency() + if not now.month == 12: + next_time = now.replace(month=now.month + 1) + else: + next_time = now.replace(year=now.year + 1, month=1) + else: + if not now.month == 12: + next_time = now.replace(month=now.month + 1) + else: + next_time = now.replace(year=now.year + 1, month=1) + tasks.update(party_president=next_time.replace(day=16, hour=0, minute=0, second=0, microsecond=0)) + + if tasks.get('contribute_cc', dt_max) <= now: + if not now.weekday(): + player.update_money() + cc = (player.details.cc // contribute_cc) * contribute_cc + player.write_log("Doing task: Contribute {}cc to Latvia".format(cc)) + player.contribute_cc_to_country(cc) + next_time = now + timedelta(days=7 - now.weekday()) + next_time = next_time.replace(hour=2, minute=0, second=0) + tasks.update({'contribute_cc': next_time}) + + if tasks.get('renew_houses', dt_max) <= now: + player.write_log("Doing task: Renew houses") + end_times = player.renew_houses() + if end_times: + tasks.update(renew_houses=min(end_times.values()) - timedelta(hours=24)) + else: + player.write_log("No houses found! Forcing q1 usage...") + end_times = player.buy_and_activate_house(1) + if not end_times: + tasks.update(renew_houses=now + timedelta(hours=6)) + else: + tasks.update(renew_houses=min(end_times.values()) - timedelta(hours=24)) + + closest_next_time = dt_max + next_tasks = [] + for task, next_time in sorted(tasks.items(), key=lambda s: s[1]): + next_tasks.append("{}: {}".format(next_time.strftime('%F %T'), task)) + if next_time < closest_next_time: + closest_next_time = next_time + random_seconds = random.randint(0, 121) if player.config.random_sleep else 0 + sleep_seconds = int(utils.get_sleep_seconds(closest_next_time)) + if sleep_seconds <= 0: + raise classes.ErepublikException(f"Loop detected! Offending task: '{next_tasks[0]}'") + closest_next_time += timedelta(seconds=random_seconds) + player.write_log("My next Tasks and there time:\n" + "\n".join(sorted(next_tasks))) + player.write_log("Sleeping until (eRep): {} (sleeping for {}s + random {}s)".format( + closest_next_time.strftime("%F %T"), sleep_seconds, random_seconds)) + seconds_to_sleep = sleep_seconds + random_seconds if sleep_seconds > 0 else 0 + player.sleep(seconds_to_sleep) + + except classes.ErepublikNetworkException: + player.write_log('Network ERROR detected. Sleeping for 1min...') + player.sleep(60) + except (KeyboardInterrupt, SystemExit): + sys.exit(1) + except classes.ErepublikException as e: + utils.process_error(f"Known error detected! {e}", player.name, sys.exc_info(), player, utils.COMMIT_ID) + except: + utils.process_error("Unknown error!", player.name, sys.exc_info(), player, utils.COMMIT_ID) + error_count += 1 + if error_count < 3: + player.sleep(60) + finally: + if error_count >= 3: + player.stop_threads.set() + player.stop_threads.set() + player.write_log('Too many errors.') + except (KeyboardInterrupt, SystemExit): + sys.exit(1) + except classes.ErepublikException: + utils.process_error("[{}] To many errors.".format(utils.COMMIT_ID), player.name, sys.exc_info(), player, + utils.COMMIT_ID) + except: + if isinstance(player, Citizen): + name = player.name + elif CONFIG.get('email', None): + name = CONFIG['email'] + else: + name = "Uninitialized" + utils.process_error("[{}] Fatal error.".format(utils.COMMIT_ID), name, sys.exc_info(), player, utils.COMMIT_ID) + sys.exit(1) + + +if __name__ == "__main__": + assert sys.version_info >= (3, 7, 1) + + write_log = utils.write_silent_log + + try: + with open('config.json', 'r') as f: + CONFIG = json.load(f) + + write_log('Config file found. Checking...') + CONFIG = utils.parse_config(CONFIG) + except: + CONFIG = utils.parse_config() + + with open('config.json', 'w') as f: + json.dump(CONFIG, f, indent=True, sort_keys=True) + if CONFIG['interactive']: + write_log = utils.write_interactive_log + else: + write_log = utils.write_silent_log + write_log('\nTo quit press [ctrl] + [c]', False) + os.chdir(os.path.dirname(os.path.realpath(__file__))) + write_log('Version: ' + utils.VERSION) + while True: + main() + write_log("Restarting after 1h") + utils.interactive_sleep(60 * 60) diff --git a/erepublik_script/utils.py b/erepublik_script/utils.py new file mode 100644 index 0000000..2c956e5 --- /dev/null +++ b/erepublik_script/utils.py @@ -0,0 +1,730 @@ +import datetime +import inspect +import json +import os +import re +import sys +import time +import traceback +from collections import deque +from decimal import Decimal +from json import JSONEncoder +from pathlib import Path +from typing import Union + +import pytz +import requests +from requests import Response +from slugify import slugify + + +__all__ = ["FOOD_ENERGY", "VERSION", "COMMIT_ID", "COUNTRIES", "erep_tz", + "now", "localize_dt", "localize_timestamp", "good_timedelta", "eday_from_date", "date_from_eday", + "get_sleep_seconds", "interactive_sleep", "silent_sleep", + "write_silent_log", "write_interactive_log", "get_file", "write_file", + "send_email", "normalize_html_json", "process_error", ] + + +FOOD_ENERGY = dict(q1=2, q2=4, q3=6, q4=8, q5=10, q6=12, q7=20) +VERSION = "v0.14.1" +COMMIT_ID = "7b92e19" + +erep_tz = pytz.timezone('US/Pacific') +AIR_RANKS = {1: "Airman", 2: "Airman 1st Class", 3: "Airman 1st Class*", 4: "Airman 1st Class**", + 5: "Airman 1st Class***", 6: "Airman 1st Class****", 7: "Airman 1st Class*****", + 8: "Senior Airman", 9: "Senior Airman*", 10: "Senior Airman**", 11: "Senior Airman***", + 12: "Senior Airman****", 13: "Senior Airman*****", + 14: "Staff Sergeant", 15: "Staff Sergeant*", 16: "Staff Sergeant**", 17: "Staff Sergeant***", + 18: "Staff Sergeant****", 19: "Staff Sergeant*****", + 20: "Aviator", 21: "Aviator*", 22: "Aviator**", 23: "Aviator***", 24: "Aviator****", 25: "Aviator*****", + 26: "Flight Lieutenant", 27: "Flight Lieutenant*", 28: "Flight Lieutenant**", 29: "Flight Lieutenant***", + 30: "Flight Lieutenant****", 31: "Flight Lieutenant*****", + 32: "Squadron Leader", 33: "Squadron Leader*", 34: "Squadron Leader**", 35: "Squadron Leader***", + 36: "Squadron Leader****", 37: "Squadron Leader*****", + 38: "Chief Master Sergeant", 39: "Chief Master Sergeant*", 40: "Chief Master Sergeant**", + 41: "Chief Master Sergeant***", 42: "Chief Master Sergeant****", 43: "Chief Master Sergeant*****", + 44: "Wing Commander", 45: "Wing Commander*", 46: "Wing Commander**", 47: "Wing Commander***", + 48: "Wing Commander****", 49: "Wing Commander*****", + 50: "Group Captain", 51: "Group Captain*", 52: "Group Captain**", 53: "Group Captain***", + 54: "Group Captain****", 55: "Group Captain*****", + 56: "Air Commodore", 57: "Air Commodore*", 58: "Air Commodore**", 59: "Air Commodore***", + 60: "Air Commodore****", 61: "Air Commodore*****", } + +GROUND_RANKS = {1: "Recruit", 2: "Private", 3: "Private*", 4: "Private**", 5: "Private***", 6: "Corporal", + 7: "Corporal*", 8: "Corporal**", 9: "Corporal***", + 10: "Sergeant", 11: "Sergeant*", 12: "Sergeant**", 13: "Sergeant***", 14: "Lieutenant", + 15: "Lieutenant*", 16: "Lieutenant**", 17: "Lieutenant***", + 18: "Captain", 19: "Captain*", 20: "Captain**", 21: "Captain***", 22: "Major", 23: "Major*", + 24: "Major**", 25: "Major***", + 26: "Commander", 27: "Commander*", 28: "Commander**", 29: "Commander***", 30: "Lt Colonel", + 31: "Lt Colonel*", 32: "Lt Colonel**", 33: "Lt Colonel***", + 34: "Colonel", 35: "Colonel*", 36: "Colonel**", 37: "Colonel***", 38: "General", 39: "General*", + 40: "General**", 41: "General***", + 42: "Field Marshal", 43: "Field Marshal*", 44: "Field Marshal**", 45: "Field Marshal***", + 46: "Supreme Marshal", 47: "Supreme Marshal*", 48: "Supreme Marshal**", 49: "Supreme Marshal***", + 50: "National Force", 51: "National Force*", 52: "National Force**", 53: "National Force***", + 54: "World Class Force", 55: "World Class Force*", 56: "World Class Force**", + 57: "World Class Force***", 58: "Legendary Force", 59: "Legendary Force*", 60: "Legendary Force**", + 61: "Legendary Force***", + 62: "God of War", 63: "God of War*", 64: "God of War**", 65: "God of War***", 66: "Titan", 67: "Titan*", + 68: "Titan**", 69: "Titan***", + 70: "Legends I", 71: "Legends II", 72: "Legends III", 73: "Legends IV", 74: "Legends V", + 75: "Legends VI", 76: "Legends VII", 77: "Legends VIII", 78: "Legends IX", 79: "Legends X", + 80: "Legends XI", 81: "Legends XII", 82: "Legends XIII", 83: "Legends XIV", 84: "Legends XV", + 85: "Legends XVI", 86: "Legends XVII", 87: "Legends XVIII", 88: "Legends XIX", 89: "Legends XX", } + +COUNTRIES = {1: 'Romania', 9: 'Brazil', 10: 'Italy', 11: 'France', 12: 'Germany', 13: 'Hungary', 14: 'China', + 15: 'Spain', 23: 'Canada', 24: 'USA', 26: 'Mexico', 27: 'Argentina', 28: 'Venezuela', 29: 'United Kingdom', + 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', + 43: 'Turkey', 44: 'Greece', 45: 'Japan', 47: 'South Korea', 48: 'India', 49: 'Indonesia', 50: 'Australia', + 51: 'South Africa', 52: 'Republic of Moldova', 53: 'Portugal', 54: 'Ireland', 55: 'Denmark', 56: 'Iran', + 57: 'Pakistan', 58: 'Israel', 59: 'Thailand', 61: 'Slovenia', 63: 'Croatia', 64: 'Chile', 65: 'Serbia', + 66: 'Malaysia', 67: 'Philippines', 68: 'Singapore', 69: 'Bosnia and Herzegovina', 70: 'Estonia', + 71: 'Latvia', 72: 'Lithuania', 73: 'North Korea', 74: 'Uruguay', 75: 'Paraguay', 76: 'Bolivia', 77: 'Peru', + 78: 'Colombia', 79: 'Republic of Macedonia (FYROM)', 80: 'Montenegro', 81: 'Republic of China (Taiwan)', + 82: 'Cyprus', 83: 'Belarus', 84: 'New Zealand', 164: 'Saudi Arabia', 165: 'Egypt', + 166: 'United Arab Emirates', 167: 'Albania', 168: 'Georgia', 169: 'Armenia', 170: 'Nigeria', 171: 'Cuba'} + + +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(): + return datetime.datetime.now(erep_tz).replace(microsecond=0) + + +def localize_timestamp(timestamp: int): + return datetime.datetime.fromtimestamp(timestamp, erep_tz) + + +def localize_dt(dt: Union[datetime.date, datetime.datetime]): + if isinstance(dt, datetime.date): + dt = datetime.datetime.combine(dt, datetime.time(0, 0, 0)) + return erep_tz.localize(dt) + + +def good_timedelta(dt: datetime.datetime, td: datetime.timedelta) -> datetime.datetime: + return erep_tz.normalize(dt + td) + + +def eday_from_date(date: Union[datetime.date, datetime.datetime] = now()) -> int: + if isinstance(date, datetime.date): + date = datetime.datetime.combine(date, datetime.time(0, 0, 0)) + return (date - datetime.datetime(2007, 11, 20, 0, 0, 0)).days + + +def date_from_eday(eday: int) -> datetime.date: + return localize_dt(datetime.date(2007, 11, 20)) + datetime.timedelta(days=eday) + + +def get_sleep_seconds(time_untill: datetime.datetime) -> int: + """ time_until aware datetime object Wrapper for sleeping until """ + sleep_seconds = int((time_untill - now()).total_seconds()) + return sleep_seconds if sleep_seconds > 0 else 0 + + +def interactive_sleep(sleep_seconds: int): + while sleep_seconds > 0: + seconds = sleep_seconds + if (seconds - 1) // 1800: + seconds = seconds % 1800 if seconds % 1800 else 1800 + elif (seconds - 1) // 300: + seconds = seconds % 300 if seconds % 300 else 300 + elif (seconds - 1) // 60: + seconds = seconds % 60 if seconds % 60 else 60 + # elif (seconds - 1) // 30: + # seconds = seconds % 30 if seconds % 30 else 30 + else: + seconds = 1 + sys.stdout.write("\rSleeping for {:4} more seconds".format(sleep_seconds)) + sys.stdout.flush() + time.sleep(seconds) + sleep_seconds -= seconds + sys.stdout.write("\r") + + +silent_sleep = time.sleep + + +def _write_log(msg, timestamp: bool = True, should_print: bool = False): + erep_time_now = now() + txt = "[{}] {}".format(erep_time_now.strftime('%F %T'), msg) if timestamp else msg + if not os.path.isdir('log'): + os.mkdir('log') + with open("log/%s.log" % erep_time_now.strftime('%F'), 'a', encoding="utf-8") as f: + f.write("%s\n" % txt) + if should_print: + print(txt) + + +def write_interactive_log(*args, **kwargs): + _write_log(should_print=True, *args, **kwargs) + + +def write_silent_log(*args, **kwargs): + _write_log(should_print=False, *args, **kwargs) + + +def get_file(filepath: str) -> str: + file = Path(filepath) + if file.exists(): + if file.is_dir(): + return str(file / "new_file.txt") + else: + version = 1 + try: + version = int(file.suffix[1:]) + 1 + basename = file.stem + except ValueError: + basename = file.name + version += 1 + + full_name = file.parent / f"{basename}.{version}" + while full_name.exists(): + version += 1 + full_name = file.parent / f"{basename}.{version}" + return str(full_name) + else: + os.makedirs(file.parent, exist_ok=True) + return str(file) + + +def write_file(filename: str, content: str) -> int: + filename = get_file(filename) + with open(filename, 'ab') as f: + return f.write(content.encode("utf-8")) + + +def write_request(response: requests.Response, is_error: bool = False): + from erepublik_script import Citizen + # Remove GET args from url name + url = response.url + last_index = url.index("?") if "?" in url else len(response.url) + + name = slugify(response.url[len(Citizen.url):last_index]) + html = response.text + + try: + json.loads(html) + ext = "json" + except json.decoder.JSONDecodeError: + ext = "html" + + if not is_error: + filename = "debug/requests/{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext) + write_file(filename, html) + else: + return {"name": "{}_{}.{}".format(now().strftime('%F_%H-%M-%S'), name, ext), + "content": html.encode('utf-8'), + "mimetype": "application/json" if ext == "json" else "text/html"} + + +def send_email(name, content: list, player=None, local_vars=dict, promo: bool = False, captcha: bool = False): + from erepublik_script import Citizen + + file_content_template = "{title}{body}" + if isinstance(player, Citizen): + resp = write_request(player.r, is_error=True) + else: + resp = {"name": "None.html", "mimetype": "text/html", + "content": file_content_template.format(body="
".join(content), title="Error"), } + + if promo: + resp = {"name": "%s.html" % name, "mimetype": "text/html", + "content": file_content_template.format(title="Promo", body="
".join(content))} + subject = "[eBot][{}] Promos: {}".format(now().strftime('%F %T'), name) + + elif captcha: + resp = {"name": "%s.html" % name, "mimetype": "text/html", + "content": file_content_template.format(title="ReCaptcha", body="
".join(content))} + subject = "[eBot][{}] RECAPTCHA: {}".format(now().strftime('%F %T'), name) + else: + subject = "[eBot][%s] Bug trace: %s" % (now().strftime('%F %T'), name) + + body = "".join(traceback.format_stack()) + \ + "\n\n" + \ + "\n".join(content) + data = dict(send_mail=True, subject=subject, bugtrace=body) + if promo: + data.update({'promo': True}) + elif captcha: + data.update({'captcha': True}) + else: + data.update({"bug": True}) + + files = [('file', (resp.get("name"), resp.get("content"), resp.get("mimetype"))), ] + filename = "log/%s.log" % now().strftime('%F') + if os.path.isfile(filename): + files.append(('file', (filename[4:], open(filename, 'rb'), "text/plain"))) + if local_vars: + if "state_thread" in local_vars: + local_vars.pop('state_thread', None) + files.append(('file', ("local_vars.json", json.dumps(local_vars, indent=2, + cls=MyJSONEncoder, sort_keys=True), "application/json"))) + if isinstance(player, Citizen): + files.append(('file', ("instance.json", player.to_json(indent=True), "application/json"))) + requests.post('https://pasts.72.lv', data=data, files=files) + + +def parse_input(msg: str) -> bool: + msg += " (y|n):" + data = None + while data not in ['', 'y', 'Y', 'n', 'N']: + try: + data = input(msg) + except EOFError: + data = 'n' + + return data in ['', 'y', 'Y'] + + +def parse_config(config=None) -> dict: + if config is None: + config = {} + + if not config.get('email'): + config['email'] = input("Player email: ") + + if not config.get('password'): + config['password'] = input("Player password: ") + + if 'wt' in config: + config['work'] = config['wt'] + config['train'] = config['wt'] + + if 'work' not in config: + config['work'] = parse_input('Should I work') + + if 'train' not in config: + config['train'] = parse_input('Should I train') + + if 'ot' not in config: + config['ot'] = parse_input('Should I work overtime') + + if 'wam' not in config: + config['wam'] = parse_input('Should I WAM') + + if 'employ' not in config: + config['employ'] = parse_input('Should I employ employees') + + if config['wam'] or config['employ']: + if "autosell" in config: + config.pop("autosell") + if "autosell_raw" in config: + config.pop("autosell_raw") + if "autosell_final" in config: + config.pop("autosell_final") + + if 'auto_sell' not in config or not isinstance(config['auto_sell'], list): + if parse_input('Should I auto sell produced products'): + config['auto_sell'] = [] + if parse_input("Should I auto sell Food products"): + if parse_input("Should I auto sell Food products"): + config['auto_sell'].append("food") + if parse_input("Should I auto sell Weapon products"): + config['auto_sell'].append("weapon") + if parse_input("Should I auto sell House products"): + config['auto_sell'].append("house") + if parse_input("Should I auto sell Aircraft products"): + config['auto_sell'].append("airplane") + if parse_input("Should I auto sell raw products"): + if parse_input("Should I auto sell Food raw"): + config['auto_sell'].append("foodRaw") + if parse_input("Should I auto sell Weapon raw"): + config['auto_sell'].append("weaponRaw") + if parse_input("Should I auto sell House raw"): + config['auto_sell'].append("houseRaw") + if parse_input("Should I auto sell Airplane raw"): + config['auto_sell'].append("airplaneRaw") + if config['auto_sell']: + if 'auto_sell_all' not in config: + print("When selling produced items should I also sell items already in inventory?") + config['auto_sell_all'] = parse_input('Y - sell all, N - only just produced') + else: + config['auto_sell_all'] = False + + if 'auto_buy_raw' not in config: + config['auto_buy_raw'] = parse_input('Should I auto buy raw deficit at WAM or employ') + else: + config['auto_sell'] = [] + config['auto_sell_all'] = False + config['auto_buy_raw'] = False + + if 'fight' not in config: + config['fight'] = parse_input('Should I fight') + + if config.get('fight'): + if 'air' not in config: + config['air'] = parse_input('Should I fight in AIR') + + if 'ground' not in config: + config['ground'] = parse_input('Should I fight in GROUND') + + if 'all_in' not in config: + print("When full energy should I go all in") + config['all_in'] = parse_input('Y - all in, N - 1h worth of energy') + + if 'next_energy' not in config: + config['next_energy'] = parse_input('Should I fight when next pp +1 energy available') + + if 'boosters' not in config: + config['boosters'] = parse_input('Should I use +50% dmg boosters') + + if 'travel_to_fight' not in config: + config['travel_to_fight'] = parse_input('Should I travel to fight') + + if 'epic_hunt' not in config: + config['epic_hunt'] = parse_input('Should I check for epic battles') + if not config['epic_hunt']: + config['epic_hunt_ebs'] = False + + if not config['epic_hunt']: + config['epic_hunt_ebs'] = False + elif 'epic_hunt_ebs' not in config: + config['epic_hunt_ebs'] = parse_input('Should I eat EBs when fighting in epic battle') + + if 'rw_def_side' not in config: + config['rw_def_side'] = parse_input('Should I fight on defenders side in RWs') + + if 'continuous_fighting' not in config: + config['continuous_fighting'] = parse_input('If already fought in any battle, \n' + 'should I continue to fight all FF in that battle') + else: + config['air'] = False + config['ground'] = False + config['all_in'] = False + config['next_energy'] = False + config['boosters'] = False + config['travel_to_fight'] = False + config['epic_hunt'] = False + config['epic_hunt_ebs'] = False + config['rw_def_side'] = False + config['continuous_fighting'] = False + + if 'debug' not in config: + config['debug'] = parse_input('Should I generate debug files') + + if 'random_sleep' not in config: + config['random_sleep'] = parse_input('Should I add random amount (0-120sec) to sleep time') + + if 'gold_buy' not in config: + config['gold_buy'] = parse_input('Should I auto buy 10g every day') + + if 'interactive' not in config: + config['interactive'] = parse_input('Should I print output to console?') + + return config + + +def normalize_html_json(js: str) -> str: + js = re.sub(r' \'(.*?)\'', lambda a: '"%s"' % a.group(1), js) + js = re.sub(r'(\d\d):(\d\d):(\d\d)', r'\1\2\3', js) + js = re.sub(r'([{\s,])(\w+)(:)(?!"})', r'\1"\2"\3', js) + js = re.sub(r',\s*}', '}', js) + return js + + +def process_error(log_info: str, name: str, exc_info: tuple, citizen=None, commit_id: str = None, + interactive: bool = False): + """ + Process error logging and email sending to developer + :param error: + :param interactive: Should print interactively + :param log_info: String to be written in output + :param name: String Instance name + :param exc_info: tuple output from sys.exc_info() + :param citizen: Citizen instance + :param commit_id: Code's version by commit id + """ + type_, value_, traceback_ = exc_info + bugtrace = [] if not commit_id else ["Commit id: %s" % commit_id, ] + bugtrace += [str(value_), str(type_), ''.join(traceback.format_tb(traceback_))] + + if interactive: + write_interactive_log(log_info) + else: + write_silent_log(log_info) + send_email(name, bugtrace, citizen, local_vars=inspect.trace()[-1][0].f_locals) + + +def aviator_support(citizen, send_food=False, free_food=None): + forbidden_ids = [] + if free_food is None: + free_food = {} # {"q1": 0, "q2": 1000, ...} + context = {'PLAYER_COUNT': 0, 'TABLE': "", + 'STARTING_ENERGY': sum([amount * FOOD_ENERGY[q] for q, amount in free_food.items()]), + 'TOTAL_CC': 0, 'TOTAL_ENERGY': 0, 'END_ENERGY': 0} + from erepublik_script import Citizen + if not isinstance(citizen, Citizen): + from .classes import ErepublikException + raise ErepublikException("\"citizen\" must be instance of erepublik.Citizen") + citizen.config.interactive = True + aviators = dict() + time_string = "%Y-%m-%d %H:%M:%S" + latest_article = requests.get('https://erep.lv/aviator/latest_article/').json() + for quality, amount in latest_article.get('free_food', {}).items(): + free_food[quality] = free_food.get(quality, 0) + amount + + if not latest_article.get('status'): + from .classes import ErepublikException + raise ErepublikException('Article ID and week problem') + context.update(WEEK=latest_article.get('week', 0) + 1) + comments = citizen.post_article_comments(citizen.token, latest_article.get('article_id'), 1).json() + ranking = citizen.get_leaderboards_kills_aircraft_rankings(71, 1, 0).json() + + if not comments.get("comments", {}): + from .classes import ErepublikException + raise ErepublikException("No comments found") + for comment_data in comments.get("comments", {}).values(): + if comment_data.get('authorId') == 1954361: + start_dt = localize_dt(datetime.datetime.strptime(comment_data.get('createdAt'), time_string)) + days_ahead = 1 - start_dt.weekday() + if days_ahead <= 0: + days_ahead += 7 + end_dt = (good_timedelta(start_dt, datetime.timedelta(days_ahead))).replace(hour=0, minute=0, second=0) + if not comment_data.get('replies', {}): + from .classes import ErepublikException + raise ErepublikException("No replies found") + + for reply_data in comment_data.get('replies').values(): + if localize_dt(datetime.datetime.strptime(reply_data.get('createdAt'), time_string)) > end_dt: + continue + if re.search(r'piesakos', reply_data.get('message'), re.I): + aviators.update({int(reply_data.get('authorId')): dict( + id=reply_data.get('authorId'), name="", kills=0, rank=0, residency=None, health=0, extra=[], + factories=0 + )}) + + context['PLAYER_COUNT'] = len(aviators) + write_interactive_log("{:^9} | {:<28} | {:4} | {:26} | {:6} | {}".format( + "ID", "Vārds", "Kili", "Gaisa rangs", "Energy", "Aktivizētās mājas" + )) + + for player_top_data in ranking.get('top'): + player_id = int(player_top_data.get('id')) + if player_id in aviators: + aviators[player_id]["kills"] = int(player_top_data['values']) + + for aviator_id, aviator_data in aviators.items(): + aviator_info = citizen.get_citizen_profile(aviator_id).json() + aviator_data.update({ + 'rank': aviator_info['military']['militaryData']['aircraft']['rankNumber'], + 'name': aviator_info['citizen']['name'], + 'residency': aviator_info['city']['residenceCityId'] + }) + + if aviator_info.get("isBanned"): + aviator_data.update({'health': 0, 'extra': ["BANNED", ]}) + else: + if aviator_data['rank'] < 44: + if aviator_data['rank'] < 38: + health = aviator_data['kills'] * 30 + else: + health = aviator_data['kills'] * 20 + has_pp = False + if aviator_info.get("activePacks"): + has_pp = bool(aviator_info.get("activePacks").get("power_pack")) + max_health = 7 * 24 * (500 if has_pp else 300) + if health < max_health: + aviator_data['health'] = health + else: + aviator_data['health'] = max_health + + if not aviator_data["residency"]: + aviator_data['health'] = 0 + aviator_data['extra'].append("No residency set") + else: + residency = citizen.get_city_data_residents( + aviator_data["residency"], params={"search": aviator_data['name']} + ).json() + + for resident in residency.get('widgets', {}).get('residents', {}).get('residents'): + if int(resident.get('citizenId')) == aviator_id: + if resident['numFactories']: + aviator_data['factories'] = resident['numFactories'] + else: + aviator_data['factories'] = 0 + if not resident.get('activeHouses'): + aviator_data['health'] = 0 + if resident['numHouses']: + aviator_data['extra'].append(", ".join(resident['activeHouses'])) + else: + aviator_data['extra'].append("Nav māja") + aviator_data['health'] = 0 + + else: + aviator_data['extra'].append("Rank") + + write_interactive_log("{id:>9} | {name:<28} | {kills:4} | {:26} | {health:6} | {}".format( + AIR_RANKS[aviator_data['rank']], + ", ".join(aviator_data["extra"]), + **aviator_data) + ) + + db_post_data = [] + for aviator_id, aviator_data in aviators.items(): + db_post_data.append(dict(id=aviator_id, name=aviator_data['name'], + rank=aviator_data['rank'], factory_count=aviator_data['factories'])) + requests.post('https://erep.lv/aviator/set/', json=db_post_data) + + for aviator_id, new in aviators.items(): + resp = requests.get('https://erep.lv/aviator/check/{}/'.format(aviator_id)) + if not resp.json()['status']: + aviators[aviator_id]['health'] = 0 + aviators[aviator_id]['extra'] = ["Nav izmaiņas fabriku skaitā", ] + + for player_id in forbidden_ids: + if player_id in aviators: + aviators[player_id]['health'] = 0 + if "BANNED" not in aviators[player_id]['extra']: + aviators[player_id]['extra'] = ["Aizliegta pieteikšanās", ] + + sent_data = [] + if send_food: + for aviator_data in sorted(aviators.values(), key=lambda t: (-t["health"], -t['kills'])): + remaining = aviator_data['health'] + if not remaining: + sent_data.append({ + "player_id": aviator_data['id'], "name": aviator_data['name'], "quality": 0, + "amount": 0, "energy": 0, "price": 0, "cost": 0, + }) + while remaining > 0: + o = [] + if free_food: + # Reversed because need to start with higher qualities so that q1 stays available + for quality in reversed(list(free_food.keys())): + if free_food[quality]: + o.append((quality, {'price': 0., 'amount': free_food[quality]})) + else: + free_food.pop(quality) + if not free_food: + offers = citizen.get_market_offers(71, product="food") + o += sorted(offers.items(), key=lambda v: (v[1]['price'] / FOOD_ENERGY[v[0]], + -v[1]['amount'] * FOOD_ENERGY[v[0]])) + + for _o in o: + q, q_data = _o + if FOOD_ENERGY[q] <= remaining: + break + else: + write_interactive_log( + "{name} needs to receive extra {remaining}hp".format(name=aviator_data['name'], + remaining=remaining)) + break + + if q_data['amount'] * FOOD_ENERGY[q] <= remaining: + amount = q_data['amount'] + else: + amount = remaining // FOOD_ENERGY[q] + + if q_data['price']: + # print(f"citizen._buy_market_offer(offer={q_data['offer_id']}, amount={amount})") + citizen.post_economy_marketplace_actions(citizen.token, amount=amount, buy=True, + offer=q_data["offer_id"]) + else: + free_food[q] -= amount + + # print(f"citizen.donate_items(citizen_id={aviator_data['id']}, + # amount={amount}, industry_id=1, quality={int(q[1])})") + citizen.donate_items(citizen_id=aviator_data['id'], amount=amount, industry_id=1, quality=int(q[1])) + remaining -= amount * FOOD_ENERGY[q] + context['TOTAL_CC'] += q_data['price'] * amount + context['TOTAL_ENERGY'] += amount * FOOD_ENERGY[q] + sent_data.append( + {"player_id": aviator_data['id'], "name": aviator_data['name'], "quality": q, "amount": amount, + "energy": amount * FOOD_ENERGY[q], "price": q_data['price'], + "cost": q_data['price'] * amount, }) + + with open(get_file("{eday}.csv".format(eday=eday_from_date(now()))), 'a') as f: + f.write('PlayerID, Quality, Amount, Energy, Price, Cost\n') + for player_data in sent_data: + f.write('{player_id}, {quality}, {amount}, {energy}, {price}, {cost}\n'.format(**player_data)) + + columns = ('[columns][b]Spēlētajs[/b]\n' + '{players}[nextcol][b]Kili[/b]\n' + '{kills}\n' + '[nextcol][right][b]Enerģija[/b]\n' + '{health}\n' + '[/right][/columns]') + player_template = '[b][url=https://www.erepublik.com/en/citizen/profile/{id}]{name}[/url][/b]' + players = [] + kills = [] + health = [] + write_interactive_log("\n".join(["{}: {}".format(q, a) for q, a in free_food.items()])) + context['TOTAL_CC'] = round(context['TOTAL_CC'], 2) + context["END_ENERGY"] = sum([amount * FOOD_ENERGY[q] for q, amount in free_food.items()]) + data = {} + for row in sent_data: + pid = int(row['player_id']) + if pid not in data: + data.update({pid: dict(id=pid, name=row['name'], energy=0, cost=0, kills=aviators[pid]['kills'])}) + + data[pid]["energy"] += row['energy'] + data[pid]["cost"] += row['cost'] + + for pid, player_data in sorted(aviators.items(), key=lambda t: (-t[1]["health"], -t[1]['kills'])): + players.append(player_template.format(id=pid, name=player_data['name'])) + kills.append(str(player_data['kills'])) + health.append(str(player_data['health'] or ", ".join(player_data['extra']))) + else: + context['TABLE'] = columns.format( + players="\n".join(players), + kills="\n".join(kills), + health="\n".join(health) + ) + + if os.path.isfile("scripts/KM_piloti.txt"): + with open("scripts/KM_piloti.txt") as f: + template = f.read() + article = template.format(**context) + with open(get_file("{eday}.txt".format(eday=eday_from_date(now()))), "w") as f: + f.write(article) + if send_food: + article_data = dict( + title="[KM] Gaisa maizītes [d{} {}]".format(citizen.eday, citizen.now.strftime("%H:%M")), + content=article, + kind=3 + ) + from_eday = eday_from_date(good_timedelta(now(), - datetime.timedelta(days=now().weekday() + 6))) + till_eday = eday_from_date(good_timedelta(now(), - datetime.timedelta(days=now().weekday()))) + comment_data = dict( + message="★★★★ MAIZE PAR NEDĒĻU [DAY {}-{}] IZDALĪTA ★★★★\n★ Apgādei piesakāmies šī komentāra reply " + "komentāros ar saucienu - piesakos! ★".format(from_eday, till_eday)) + total_cc = int(round(context['TOTAL_CC'])) + wall_body = ("★★★ [ KONGRESA BALSOJUMS ] ★★★\n\nDotācija pilotiem par d{}-{} {}cc apmērā.\n\n" + "Balsot ar Par/Pret\nBalsošanas laiks 24h līdz d{} {}").format( + from_eday, till_eday, total_cc, citizen.eday + 1, citizen.now.strftime("%H:%M")) + + citizen.write_log("Publishing info:\n\n### Article ###\n{}\n\n{}\n\n### Wall ###\n{}".format( + article_data['title'], comment_data['message'], wall_body + )) + + KM_account: Citizen = Citizen("kara-ministrija@erep.lv", "KMPar0le") + KM_account.set_debug(True) + KM_account.update_citizen_info() + resp = KM_account.publish_article(**article_data) + article_id = resp.history[1].url.split("/")[-3] + comment_data.update({"article_id": article_id}) + KM_account.write_article_comment(**comment_data) + citizen.write_on_country_wall(wall_body) + requests.post('https://erep.lv/aviator/latest_article/', + data=dict(week=context["WEEK"], article_id=article_id)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..53cd795 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +ipython==7.3.0 +pycryptodome==3.7.3 +PyInstaller==3.4 +python-slugify==2.0.1 +pytz==2018.9 +requests==2.21.0 diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..8cf206c --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,10 @@ +pip==18.1 +bumpversion==0.5.3 +wheel==0.32.1 +watchdog==0.9.0 +flake8==3.5.0 +tox==3.5.2 +coverage==4.5.1 +Sphinx==1.8.1 +twine==1.12.1 +ipython==7.3.0 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..b00f490 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,24 @@ +[bumpversion] +current_version = 0.1.0 +commit = True +tag = True + +[bumpversion:file:setup.py] +search = version='{current_version}' +replace = version='{new_version}' + +[bumpversion:file:erepublik_script/__init__.py] +search = __version__ = '{current_version}' +replace = __version__ = '{new_version}' + +[bdist_wheel] +universal = 1 + +[flake8] +exclude = docs +max-line-length = 120 +ignore = E722 + +[aliases] +# Define setup.py command aliases here + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0f34596 --- /dev/null +++ b/setup.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""The setup script.""" + +from setuptools import setup, find_packages + +with open('README.rst') as readme_file: + readme = readme_file.read() + +with open('HISTORY.rst') as history_file: + history = history_file.read() + +requirements = ['Click>=6.0', ] + +setup_requirements = [ ] + +test_requirements = [ ] + +setup( + author="Eriks Karls", + author_email='eriks@72.lv', + classifiers=[ + 'Development Status :: 2 - Pre-Alpha', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Natural Language :: English', + "Programming Language :: Python :: 2", + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ], + description="Python package for eRepublik automated playing", + entry_points={ + 'console_scripts': [ + 'erepublik_script=erepublik_script.cli:main', + ], + }, + install_requires=requirements, + license="MIT license", + long_description=readme + '\n\n' + history, + include_package_data=True, + keywords='erepublik_script', + name='erepublik_script', + packages=find_packages(include=['erepublik_script']), + setup_requires=setup_requirements, + test_suite='tests', + tests_require=test_requirements, + url='https://github.com/eeriks/erepublik_script', + version='0.1.0', + zip_safe=False, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..3518b83 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +"""Unit test package for erepublik_script.""" diff --git a/tests/test_erepublik_script.py b/tests/test_erepublik_script.py new file mode 100644 index 0000000..1945655 --- /dev/null +++ b/tests/test_erepublik_script.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for `erepublik_script` package.""" + + +import unittest +from click.testing import CliRunner + +from erepublik_script import erepublik_script +from erepublik_script import cli + + +class TestErepublik_script(unittest.TestCase): + """Tests for `erepublik_script` package.""" + + def setUp(self): + """Set up test fixtures, if any.""" + + def tearDown(self): + """Tear down test fixtures, if any.""" + + def test_000_something(self): + """Test something.""" + + def test_command_line_interface(self): + """Test the CLI.""" + runner = CliRunner() + result = runner.invoke(cli.main) + assert result.exit_code == 0 + assert 'erepublik_script.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 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..96540a4 --- /dev/null +++ b/tox.ini @@ -0,0 +1,21 @@ +[tox] +envlist = py27, py34, py35, py36, flake8 + +[travis] +python = + 3.6: py36 + 3.5: py35 + 3.4: py34 + 2.7: py27 + +[testenv:flake8] +basepython = python +deps = flake8 +commands = flake8 erepublik_script + +[testenv] +setenv = + PYTHONPATH = {toxinidir} + +commands = python setup.py test +