Compare commits

...

12 Commits

Author SHA1 Message Date
ce7874fbf5 Bump version: 0.20.1 → 0.20.1.1 2020-06-18 10:14:50 +03:00
6abfc98fbd Test requirements 2020-06-18 10:13:55 +03:00
66f0e648df Citizen.to_json() bugfixed and optimised 2020-06-18 10:10:22 +03:00
7cf6cf0e12 Bump version: 0.20.0 → 0.20.1 2020-06-16 17:00:09 +03:00
a825917a98 WAM/Employ bugfix
Company sorting bugfix
2020-06-16 16:59:56 +03:00
603604213d Requirement update 2020-06-15 16:22:53 +03:00
f83df449ae Merge commit '98947e6bbe6feda9f80d630b54c132fa2d5a5949' into v0.20.0
* commit '98947e6bbe6feda9f80d630b54c132fa2d5a5949':
  Update pythonpackage.yml
  Create pythonpackage.yml
2020-06-15 16:03:02 +03:00
b480ed7230 Companies and holdings created as python objects from Dicts 2020-06-15 16:02:36 +03:00
67677f356f eRepublik updated contributions endpoint 2020-06-15 15:59:03 +03:00
ff869e0403 Bomb deploy bugfix 2020-06-15 15:47:48 +03:00
98947e6bbe Update pythonpackage.yml 2020-03-03 19:18:33 +02:00
24d81bbadf Create pythonpackage.yml 2020-03-03 19:16:19 +02:00
10 changed files with 374 additions and 195 deletions

33
.github/workflows/pythonpackage.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Python package
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements_dev.txt
- name: Lint with flake8
run: |
pip install flake8
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pip install pytest
pytest

View File

@ -2,8 +2,8 @@
language: python
python:
- 3.8
- 3.7
- 3.6
# Command to install dependencies, e.g. pip install -r requirements_dev.txt --use-mirrors
install: pip install -U tox-travis

View File

@ -2,6 +2,17 @@
History
=======
0.20.0 (2020-06-15)
-------------------
* Massive restructuring
* Restricted IP check
* Bomb deploy improvements
* More verbose action logging
* Division switching for maverick scripts
* New medal endpoint is correctly parsed
* WAM/Employ modularized
0.19.0 (2020-01-13)
-------------------
* Created method for current products on sale.

View File

@ -4,8 +4,8 @@
__author__ = """Eriks Karls"""
__email__ = 'eriks@72.lv'
__version__ = '0.20.0'
__commit_id__ = "4e33717"
__version__ = '0.20.1.1'
__commit_id__ = "6abfc98"
from erepublik import classes, utils
from erepublik.citizen import Citizen

View File

@ -427,7 +427,7 @@ class BaseCitizen(CitizenAPI):
def __dict__(self):
ret = super().__dict__.copy()
ret.pop('stop_threads', None)
ret.pop('_Citizen__last_war_update_data', None)
ret.pop('_CitizenMilitary__last_war_update_data', None)
return ret
@ -657,6 +657,7 @@ class BaseCitizen(CitizenAPI):
r'CSRF attack detected|meta http-equiv="refresh"|not_authenticated', response.text))
def _report_action(self, action: str, msg: str, **kwargs):
kwargs = utils.json.loads(utils.json.dumps(kwargs or {}, cls=MyJSONEncoder))
action = action[:32]
self.write_log(msg)
if self.reporter.allowed:
@ -842,7 +843,7 @@ class CitizenCompanies(BaseCitizen):
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']:
if not self.details.current_region == wam_holding.region:
self.write_log("Unable to work as manager because of location - please travel!")
return
@ -850,7 +851,7 @@ class CitizenCompanies(BaseCitizen):
if sum(employ_factories.values()) > self.my_companies.work_units:
employ_factories = {}
response = self._post_economy_work("production", wam=wam_list, employ=employ_factories).json()
response = self._post_economy_work("production", wam=[c.id for c in wam_list], employ=employ_factories).json()
return response
def update_companies(self):
@ -861,19 +862,15 @@ class CitizenCompanies(BaseCitizen):
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(utils.json.loads(have_companies.group(1)))
self.my_companies.prepare_holdings(utils.json.loads(have_holdings.group(1)))
self.my_companies.update_holding_companies()
self.my_companies.prepare_companies(utils.json.loads(have_companies.group(1)))
def assign_factory_to_holding(self, factory_id: int, holding_id: int) -> Response:
"""
Assigns factory to new holding
"""
company = self.my_companies.companies[factory_id]
company_name = self.factories[company['industry_id']]
if not company['is_raw']:
company_name += f" q{company['quality']}"
self.write_log(f"{company_name} moved to {holding_id}")
self.write_log(f"{company} moved to {holding_id}")
return self._post_economy_assign_to_holding(factory_id, holding_id)
def upgrade_factory(self, factory_id: int, level: int) -> Response:
@ -887,7 +884,11 @@ class CitizenCompanies(BaseCitizen):
Storage={1000: 1, 2000: 2} <- Building_type 2
"""
company_name = self.factories[industry_id]
company_name = {1: "Food", 2: "Weapons", 4: "House", 23: "Aircraft",
7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
18: "HRM q1", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5", }[industry_id]
if building_type == 2:
company_name = f"Storage"
self.write_log(f"{company_name} created!")
@ -895,10 +896,7 @@ class CitizenCompanies(BaseCitizen):
def dissolve_factory(self, factory_id: int) -> Response:
company = self.my_companies.companies[factory_id]
company_name = self.factories[company['industry_id']]
if not company['is_raw']:
company_name += f" q{company['quality']}"
self.write_log(f"{company_name} dissolved!")
self.write_log(f"{company} dissolved!")
return self._post_economy_sell_company(factory_id, self.details.pin, sell=False)
@ -1030,7 +1028,7 @@ class CitizenEconomy(CitizenTravel):
ret = self._post_economy_marketplace_actions(**data)
message = (f"Posted market offer for {amount}q{quality} "
f"{self.get_industry_name(industry)} for price {price}cc")
self._report_action("ECONOMY_SELL_PRODUCTS", message, **ret.json())
self._report_action("ECONOMY_SELL_PRODUCTS", message, kwargs=ret.json())
return ret
def buy_from_market(self, offer: int, amount: int) -> dict:
@ -1042,7 +1040,7 @@ class CitizenEconomy(CitizenTravel):
self.details.cc = ret.json()['currency']
self.details.gold = ret.json()['gold']
json_ret.pop("offerUpdate", None)
self._report_action("BOUGHT_PRODUCTS", "", **json_ret)
self._report_action("BOUGHT_PRODUCTS", "", kwargs=json_ret)
return json_ret
def get_market_offers(self, product_name: str, quality: int = None, country_id: int = None) -> Dict[str, OfferItem]:
@ -1102,7 +1100,7 @@ class CitizenEconomy(CitizenTravel):
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 * utils.FOOD_ENERGY[cheapest_q])
self._report_action("BUY_FOOD", "", **data)
self._report_action("BUY_FOOD", "", kwargs=data)
self.buy_from_market(cheapest.offer_id, amount)
self.update_inventory()
else:
@ -1139,11 +1137,11 @@ class CitizenEconomy(CitizenTravel):
self.details.cc = float(response.json().get("ecash").get("value"))
self.details.gold = float(response.json().get("gold").get("value"))
if response.json().get('error'):
self._report_action("BUY_GOLD", "Unable to buy gold!", **response.json())
self._report_action("BUY_GOLD", "Unable to buy gold!", kwargs=response.json())
self.stop_threads.wait()
return False
else:
self._report_action("BUY_GOLD", f"New amount {self.details.cc}cc, {self.details.gold}g", **response.json())
self._report_action("BUY_GOLD", f"New amount {self.details.cc}cc, {self.details.gold}g", kwargs=response.json())
return True
def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62) -> bool:
@ -1180,8 +1178,10 @@ class CitizenEconomy(CitizenTravel):
f"Unable to donate {amount}q{quality} "
f"{self.get_industry_name(industry_id)}, not enough left!", success=False)
return 0
available = re.search(rf"Cannot transfer the items because the user has only (\d+) free slots in (his|her) "
rf"storage.", response.text).group(1)
available = re.search(
rf"Cannot transfer the items because the user has only (\d+) free slots in (his|her) storage.",
response.text
).group(1)
self._report_action("DONATE_ITEMS",
f"Unable to donate {amount}q{quality}{self.get_industry_name(industry_id)}"
f", receiver has only {available} storage left!", success=False)
@ -1201,7 +1201,7 @@ class CitizenEconomy(CitizenTravel):
return True
else:
self._report_action("CONTRIBUTE_CC", f"Unable to contribute {amount}cc to {utils.COUNTRIES[country_id]}'s"
f" treasury", **r.json())
f" treasury", kwargs=r.json())
return False
def contribute_food_to_country(self, amount: int = 0, quality: int = 1, country_id: int = 71) -> bool:
@ -1218,7 +1218,7 @@ class CitizenEconomy(CitizenTravel):
return True
else:
self._report_action("CONTRIBUTE_FOOD", f"Unable to contribute {amount}q{quality} food to "
f"{utils.COUNTRIES[country_id]}'s treasury", **r.json())
f"{utils.COUNTRIES[country_id]}'s treasury", kwargs=r.json())
return False
def contribute_gold_to_country(self, amount: int, country_id: int = 71) -> bool:
@ -1236,7 +1236,7 @@ class CitizenEconomy(CitizenTravel):
return True
else:
self._report_action("CONTRIBUTE_GOLD", f"Unable to contribute {amount}g to {utils.COUNTRIES[country_id]}'s"
f" treasury", **r.json())
f" treasury", kwargs=r.json())
return False
@ -1263,7 +1263,7 @@ class CitizenMedia(BaseCitizen):
return True
else:
self._report_action("ARTICLE_ENDORSE", f"Unable to endorse article ({article_id}) with {amount}cc",
**resp)
kwargs=resp)
return False
else:
return False
@ -1275,7 +1275,7 @@ class CitizenMedia(BaseCitizen):
self._report_action("ARTICLE_VOTE", f"Voted article {article_id}", success=True)
return True
else:
self._report_action("ARTICLE_VOTE", f"Unable to vote for article {article_id}", **resp)
self._report_action("ARTICLE_VOTE", f"Unable to vote for article {article_id}", kwargs=resp)
return False
def get_article_comments(self, article_id: int, page_id: int = 1) -> Dict[str, Any]:
@ -1295,7 +1295,7 @@ class CitizenMedia(BaseCitizen):
resp = self._post_main_write_article(**data)
try:
article_id = int(resp.history[1].url.split("/")[-3])
self._report_action("ARTICLE_PUBLISH", f"Published new article \"{title}\" ({article_id})", **data)
self._report_action("ARTICLE_PUBLISH", f"Published new article \"{title}\" ({article_id})", kwargs=data)
except: # noqa
article_id = 0
return article_id
@ -1383,7 +1383,7 @@ class CitizenMilitary(CitizenTravel):
if not battle.is_air:
for weapon in available_weapons:
try:
if weapon['weaponQuantity'] > 30 and weapon['damage'] > weapon_damage:
if weapon['weaponQuantity'] > 30 and weapon['weaponInfluence'] > weapon_damage:
weapon_quality = int(weapon['weaponId'])
except ValueError:
pass
@ -1394,7 +1394,7 @@ class CitizenMilitary(CitizenTravel):
battle_zone = battle.div[11 if battle.is_air else self.division].battle_zone_id
r = self._post_military_change_weapon(battle_id, battle_zone, quality)
influence = r.json().get('weaponInfluence')
self._report_action("MILITARY_WEAPON", f"Switched to q{quality} weapon, new influence {influence}", **r.json())
self._report_action("MILITARY_WEAPON", f"Switched to q{quality} weapon, new influence {influence}", kwargs=r.json())
return influence
def check_epic_battles(self):
@ -1462,7 +1462,7 @@ class CitizenMilitary(CitizenTravel):
battle_list = sorted(self.all_battles.values(), key=lambda b: b.id)
contribution_json: Response = self._get_military_campaigns_json_citizen()
contributions: List[Dict[str, int]] = contribution_json.json().get('contributions', [])
contributions: List[Dict[str, int]] = contribution_json.json().get('contributions') or []
contributions.sort(key=lambda b: -b.get('damage'))
ret_battles += [int(b.get('battle_id', 0)) for b in contributions if b.get('battle_id')]
@ -1700,6 +1700,8 @@ class CitizenMilitary(CitizenTravel):
deployed_count += 1
elif r.get('message') == 'LOCKED':
sleep(0.5)
else:
errors += 1
if has_traveled:
self.travel_to_residence()
@ -1968,8 +1970,8 @@ class CitizenSocial(BaseCitizen):
if ids is None:
ids = [1620414, ]
for player_id in ids:
self._report_action("SOCIAL_MESSAGE", f"Sent a message to {player_id}", **dict(subject=subject, msg=msg,
id=player_id))
self._report_action("SOCIAL_MESSAGE", f"Sent a message to {player_id}", kwargs=dict(subject=subject, msg=msg,
id=player_id))
self._post_main_messages_compose(subject, msg, [player_id])
def write_on_country_wall(self, message: str) -> bool:
@ -1978,7 +1980,7 @@ class CitizenSocial(BaseCitizen):
self.r.text, re.S | re.M)
r = self._post_main_country_post_create(message, max(post_to_wall_as, key=int) if post_to_wall_as else 0)
self._report_action("SOCIAL_WRITE_WALL_COUNTRY", f"Wrote a message to the country wall", msg=message)
self._report_action("SOCIAL_WRITE_WALL_COUNTRY", f"Wrote a message to the country wall", kwargs=message)
return r.json()
def add_friend(self, player_id: int) -> Response:
@ -2154,7 +2156,7 @@ class CitizenTasks(BaseCitizen):
def resign_from_employer(self) -> bool:
r = self.update_job_info()
if r.json().get("isEmployee"):
self._report_action("ECONOMY_RESIGN", f"Resigned from employer!", **r.json())
self._report_action("ECONOMY_RESIGN", f"Resigned from employer!", kwargs=r.json())
self._post_economy_resign()
return True
return False
@ -2165,7 +2167,7 @@ class CitizenTasks(BaseCitizen):
extra = ret.json()
except: # noqa
extra = {}
self._report_action("ECONOMY_TG_CONTRACT", f"Bought TG Contract", **extra)
self._report_action("ECONOMY_TG_CONTRACT", f"Bought TG Contract", kwargs=extra)
return ret
def find_new_job(self) -> Response:
@ -2180,12 +2182,12 @@ class CitizenTasks(BaseCitizen):
if (not limit or salary * 3 < limit) and salary > data["salary"]:
data.update({"citizen": citizen_id, "salary": salary})
self._report_action("ECONOMY_APPLY_FOR_JOB", f"I'm working now for {str(data['citizen'])}", **r.json())
self._report_action("ECONOMY_APPLY_FOR_JOB", f"I'm working now for {str(data['citizen'])}", kwargs=r.json())
return self._post_economy_job_market_apply(**data)
def apply_to_employer(self, employer_id: int, salary: float) -> bool:
data = dict(citizenId=employer_id, salary=salary)
self._report_action("ECONOMY_APPLY_FOR_JOB", f"I'm working now for #{employer_id}", **data)
self._report_action("ECONOMY_APPLY_FOR_JOB", f"I'm working now for #{employer_id}", kwargs=data)
r = self._post_economy_job_market_apply(employer_id, salary)
return bool(r.json().get('status'))
@ -2230,6 +2232,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.telegram.send_message(f"*Started* {utils.now():%F %T}")
self.__last_full_update = utils.good_timedelta(self.now, - timedelta(minutes=5))
self.update_all(True)
def update_citizen_info(self, html: str = None):
"""
@ -2279,7 +2282,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if (title, reward) not in data:
data[(title, reward)] = {'about': about, 'kind': title, 'reward': reward, "count": count,
"currency": currency, "params": params}
"currency": currency, "params": medal.get('details', {})}
else:
data[(title, reward)]['count'] += count
self._post_main_global_alerts_close(medal.get('id'))
@ -2438,7 +2441,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if response is None:
return
if response.get("status"):
self._report_action("WORK_AS_MANAGER", f"Worked as manager", **response)
self._report_action("WORK_AS_MANAGER", f"Worked as manager", kwargs=response)
if self.config.auto_sell:
for kind, data in response.get("result", {}).get("production", {}).items():
if data and kind in self.config.auto_sell:
@ -2478,7 +2481,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self._wam(holding_id)
else:
msg = "I was not able to wam and or employ because:\n{}".format(response)
self._report_action("WORK_AS_MANAGER", f"Worked as manager failed: {msg}", **response)
self._report_action("WORK_AS_MANAGER", f"Worked as manager failed: {msg}", kwargs=response)
self.write_log(msg)
def work_as_manager(self) -> bool:
@ -2497,7 +2500,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
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})
regions.update({holding.region: holding_id})
# Check for current region
if self.details.current_region in regions:
@ -2521,3 +2524,22 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.update_companies()
return bool(self.my_companies.get_total_wam_count())
def sorted_battles(self, sort_by_time: bool = True) -> List[int]:
battles = self.reporter.fetch_battle_priorities(self.details.current_country)
return battles + super().sorted_battles(sort_by_time)
def command_central(self):
while not self.stop_threads.is_set():
try:
tasks = self.reporter.fetch_tasks()
for task, args in tasks:
try:
fn = getattr(self, task)
if callable(fn):
fn(*args)
except AttributeError:
continue
self.stop_threads.wait(90)
except: # noqa
self.report_error("Command central has broken")

View File

@ -1,18 +1,20 @@
import datetime
import decimal
import hashlib
import threading
from collections import defaultdict, deque
from typing import Any, Dict, Iterable, List, NamedTuple, Tuple, Union
from collections import defaultdict
from decimal import Decimal
from typing import Any, Dict, List, NamedTuple, Tuple, Union, Optional
from requests import Response, Session, post
from erepublik import utils
from erepublik.utils import json
try:
import simplejson as json
except ImportError:
import json
INDUSTRIES = {1: "Food", 2: "Weapons", 4: "House", 23: "Aircraft",
7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
18: "HRM q1", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5", }
class ErepublikException(Exception):
@ -26,75 +28,218 @@ class ErepublikNetworkException(ErepublikException):
self.request = request
class Holding:
id: int
region: int
companies: List["Company"]
def __init__(self, _id: int, region: int):
self.id: int = _id
self.region: int = region
self.companies: List["Company"] = list()
@property
def wam_count(self) -> int:
return sum([company.wam_enabled and not company.already_worked for company in self.companies])
@property
def wam_companies(self) -> List["Company"]:
return [company for company in self.companies if company.wam_enabled]
@property
def employable_companies(self) -> List["Company"]:
return [company for company in self.companies if company.preset_works]
def add_company(self, company: "Company"):
self.companies.append(company)
self.companies.sort()
def get_wam_raw_usage(self) -> Dict[str, Decimal]:
frm = Decimal("0.00")
wrm = Decimal("0.00")
for company in self.wam_companies:
if company.industry in [1, 7, 8, 9, 10, 11]:
frm += company.raw_usage
elif company.industry in [2, 12, 13, 14, 15, 16]:
wrm += company.raw_usage
return dict(frm=frm, wrm=wrm)
def __str__(self):
name = f"Holding (#{self.id}) with {len(self.companies)} "
if len(self.companies) % 10 == 1:
name += "company"
else:
name += "companies"
return name
def __repr__(self):
return str(self)
@property
def __dict__(self):
return dict(name=str(self), id=self.id, region=self.region, companies=self.companies, wam_count=self.wam_count)
class Company:
holding: Holding
id: int
quality: int
is_raw: bool
raw_usage: Decimal
products_made: Decimal
wam_enabled: bool
can_wam: bool
cannot_wam_reason: str
industry: int
already_worked: bool
preset_works: int
def __init__(
self, holding: Holding, _id: int, quality: int, is_raw: bool, effective_bonus: Decimal, raw_usage: Decimal,
base_production: Decimal, wam_enabled: bool, can_wam: bool, cannot_wam_reason: str, industry: int,
already_worked: bool, preset_works: int
):
self.holding: Holding = holding
self.id: int = _id
self.industry: int = industry
self.quality: int = self._get_real_quality(quality)
self.is_raw: bool = is_raw
self.wam_enabled: bool = wam_enabled
self.can_wam: bool = can_wam
self.cannot_wam_reason: str = cannot_wam_reason
self.already_worked: bool = already_worked
self.preset_works: int = preset_works
self.products_made = self.raw_usage = Decimal(base_production) * Decimal(effective_bonus)
if not self.is_raw:
self.raw_usage = - self.products_made * raw_usage
def _get_real_quality(self, quality) -> int:
# 7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
# 12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
# 18: "HRM q1", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
# 24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5",
if 7 <= self.industry <= 11:
return self.industry % 6
elif 12 <= self.industry <= 16:
return self.industry % 11
elif 18 <= self.industry <= 22:
return self.industry % 17
elif 24 <= self.industry <= 28:
return self.industry % 23
else:
return quality
@property
def _internal_industry(self) -> int:
# 7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
# 12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
# 18: "HRM q1", 19: "HRM q2", 20: "HRM q3", 21: "HRM q4", 22: "HRM q5",
# 24: "ARM q1", 25: "ARM q2", 26: "ARM q3", 27: "ARM q4", 28: "ARM q5",
if 7 <= self.industry <= 11:
return 7
elif 12 <= self.industry <= 16:
return 12
elif 18 <= self.industry <= 22:
return 18
elif 24 <= self.industry <= 28:
return 24
else:
return self.industry
@property
def _sort_keys(self):
return not self.is_raw, self._internal_industry, -self.quality, self.id
def __hash__(self):
return hash(self._sort_keys)
def __lt__(self, other: "Company"):
return self._sort_keys < other._sort_keys
def __le__(self, other: "Company"):
return self._sort_keys <= other._sort_keys
def __gt__(self, other: "Company"):
return self._sort_keys > other._sort_keys
def __ge__(self, other: "Company"):
return self._sort_keys >= other._sort_keys
def __eq__(self, other: "Company"):
return self._sort_keys == other._sort_keys
def __ne__(self, other: "Company"):
return self._sort_keys != other._sort_keys
def __str__(self):
name = f"(#{self.id:>9d}) {INDUSTRIES[self.industry]}"
if not self.is_raw:
name += f" q{self.quality}"
return name
def __repr__(self):
return str(self)
@property
def __dict__(self):
return dict(name=str(self), holding=self.holding.id, id=self.id, quality=self.quality, is_raw=self.is_raw,
raw_usage=self.raw_usage, products_made=self.products_made, wam_enabled=self.wam_enabled,
can_wam=self.can_wam, cannot_wam_reason=self.cannot_wam_reason, industry=self.industry,
already_worked=self.already_worked, preset_works=self.preset_works)
class MyCompanies:
work_units: int = 0
next_ot_time: datetime.datetime
holdings: Dict[int, Dict] = None
companies: Dict[int, Dict] = None
ff_lockdown: int = 0
holdings: Dict[int, Holding]
companies: List[Company]
def __init__(self):
self.holdings = dict()
self.companies = dict()
self.holdings: Dict[int, Holding] = dict()
self.companies: List[Company] = list()
self.next_ot_time = utils.now()
def prepare_holdings(self, holdings: dict):
def prepare_holdings(self, holdings: Dict[str, Dict[str, Any]]):
"""
:param holdings: Parsed JSON to dict from en/economy/myCompanies
"""
self.holdings.clear()
template = dict(id=0, num_factories=0, region_id=0, companies=[])
for holding in holdings.values():
if holding.get('id') not in self.holdings:
self.holdings.update({int(holding.get('id')): Holding(holding['id'], holding['region_id'])})
if not self.holdings.get(0):
self.holdings.update({0: Holding(0, 0)}) # unassigned
for holding_id, holding in holdings.items():
tmp: Dict[str, Union[Iterable[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):
def prepare_companies(self, companies: Dict[str, Dict[str, Any]]):
"""
:param companies: Parsed JSON to dict from en/economy/myCompanies
"""
self.companies.clear()
template = dict(id=None, quality=0, is_raw=False, resource_bonus=0, effective_bonus=0, raw_usage=0,
base_production=0, wam_enabled=False, can_work_as_manager=False, industry_id=0, todays_works=0,
preset_own_work=0, already_worked=False, can_assign_employees=False, preset_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])
elif key == "raw_usage":
if not company.get("is_raw") and company.get('upgrades'):
company[key] = company.get('upgrades').get(str(company["quality"])).get('raw_usage')
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)
for holding_id in self.holdings:
self.holdings[holding_id]['companies'].sort()
self.__clear_data()
for company_dict in companies.values():
holding = self.holdings.get(int(company_dict['holding_company_id']))
quality = company_dict.get('quality')
is_raw = company_dict.get('is_raw')
if is_raw:
raw_usage = Decimal('0.0')
else:
raw_usage = Decimal(str(company_dict.get('upgrades').get(str(quality)).get('raw_usage')))
company = Company(
holding, company_dict.get('id'), quality, is_raw,
Decimal(str(company_dict.get('effective_bonus'))) / 100,
raw_usage, Decimal(str(company_dict.get('base_production'))), company_dict.get('wam_enabled'),
company_dict.get('can_work_as_manager'), company_dict.get('cannot_work_as_manager_reason'),
company_dict.get('industry_id'), company_dict.get('already_worked'), company_dict.get('preset_works')
)
self.companies.append(company)
holding.add_company(company)
def get_employable_factories(self) -> Dict[int, int]:
ret = {}
for company_id, company in self.companies.items():
if company.get('preset_works'):
ret[company_id] = int(company.get('preset_works', 0))
return ret
return {company.id: company.preset_works for company in self.companies if company.preset_works}
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
return sum([holding.wam_count for holding in self.holdings.values()])
def get_holding_wam_count(self, holding_id: int, raw_factory=None) -> int:
"""
@ -105,14 +250,7 @@ class MyCompanies:
"""
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]:
def get_holding_wam_companies(self, holding_id: int, raw_factory: bool = None) -> List[Company]:
"""
Returns WAM enabled companies in the holding, True - only raw, False - only factories, None - both
:param holding_id: holding id
@ -122,19 +260,12 @@ class MyCompanies:
raw = []
factory = []
if holding_id in self.holdings:
for company_id in sorted(self.holdings.get(holding_id, {}).get('companies', []),
key=lambda cid: (-self.companies[cid].get('is_raw'), # True, False
self.companies[cid].get('industry_id'), # F W H A
-self.companies[cid].get('quality'),)): # 7, 6, .. 2, 1
company = self.companies.get(company_id, {})
wam_enabled = bool(company.get('wam_enabled', {}))
already_worked = not company.get('already_worked', {})
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)
for company in self.holdings[holding_id].wam_companies:
if not company.already_worked and not company.cannot_wam_reason == "war":
if company.is_raw:
raw.append(company)
else:
factory.append(company_id)
factory.append(company)
if raw_factory is not None and not raw_factory:
return factory
elif raw_factory is not None and raw_factory:
@ -144,56 +275,29 @@ class MyCompanies:
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]))
@staticmethod
def get_needed_inventory_usage(companies: Union[Company, List[Company]]) -> Decimal:
raise ErepublikException("Wrong function call")
def get_wam_raw_usage(self) -> Dict[str, float]:
frm = 0.00
wrm = 0.00
for company in self.companies.values():
if company['wam_enabled']:
effective_bonus = float(company["effective_bonus"])
base_prod = float(company["base_production"])
raw = base_prod * effective_bonus / 100
if not company["is_raw"]:
raw *= -company["raw_usage"]
if company["industry_id"] in [1, 7, 8, 9, 10, 11]:
frm += raw
elif company["industry_id"] in [2, 12, 13, 14, 15, 16]:
wrm += raw
return {'frm': int(frm * 1000) / 1000, 'wrm': int(wrm * 1000) / 1000}
if isinstance(companies, list):
return sum([company.products_made * 100 if company.is_raw else 1 for company in companies])
else:
return companies.products_made
def __str__(self):
name = []
for holding_id in sorted(self.holdings.keys()):
if not holding_id:
name.append(f"Unassigned - {len(self.holdings[0]['companies'])}")
else:
name.append(f"{holding_id} - {len(self.holdings[holding_id]['companies'])}")
return " | ".join(name)
return f"MyCompanies: {len(self.companies)} companies in {len(self.holdings)} holdings"
# @property
# def __dict__(self):
# ret = {}
# for key in dir(self):
# if not key.startswith('_'):
# ret[key] = getattr(self, key)
# return ret
def __repr__(self):
return str(self)
def __clear_data(self):
for holding in self.holdings.values():
holding.companies.clear()
self.companies.clear()
@property
def __dict__(self):
return dict(name=str(self), work_units=self.work_units, next_ot_time=self.next_ot_time, ff_lockdown=self.ff_lockdown, holdings=self.holdings,
company_count=len(self.companies))
class Config:
@ -470,15 +574,30 @@ class Reporter:
def report_promo(self, kind: str, time_until: datetime.datetime):
self._req.post(f"{self.url}/promos/add/", data=dict(kind=kind, time_untill=time_until))
def fetch_battle_priorities(self, country_id: int) -> List[int]:
try:
battle_response = self._req.get(f'{self.url}/api/v1/battles/{country_id}')
return battle_response.json().get('battle_ids', [])
except: # noqa
return []
def fetch_tasks(self) -> Optional[Tuple[str, Tuple[Any]]]:
try:
task_response = self._req.get(f'{self.url}/api/v1/command',
params=dict(citizen=self.citizen_id, key=self.key))
return task_response.json().get('task_collection')
except: # noqa
return
class MyJSONEncoder(json.JSONEncoder):
def default(self, o):
from erepublik.citizen import Citizen
if isinstance(o, decimal.Decimal):
return float("{:.02f}".format(o))
if isinstance(o, Decimal):
return float(f"{o:.02f}")
elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"),
tzinfo=o.tzinfo.tzname if o.tzinfo else None)
tzinfo=str(o.tzinfo) if o.tzinfo else None)
elif isinstance(o, datetime.date):
return dict(__type__='date', date=o.strftime("%Y-%m-%d"))
elif isinstance(o, datetime.timedelta):
@ -488,18 +607,14 @@ class MyJSONEncoder(json.JSONEncoder):
return dict(headers=o.headers.__dict__, url=o.url, text=o.text)
elif hasattr(o, '__dict__'):
return o.__dict__
elif isinstance(o, (deque, set)):
elif isinstance(o, set):
return list(o)
elif isinstance(o, Citizen):
return o.to_json()
try:
return super().default(o)
except TypeError as e:
name = None
for ___, ____ in globals().copy().items():
if id(o) == id(____):
name = ___
return dict(__error__=str(e), __type__=str(type(o)), __name__=name)
except Exception as e: # noqa
return 'Object is not JSON serializable'
class BattleSide:

View File

@ -323,8 +323,7 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Mapping[A
if "state_thread" in local_vars:
local_vars.pop('state_thread', None)
from erepublik.classes import MyJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, indent=2,
cls=MyJSONEncoder, sort_keys=True), "application/json")))
files.append(('file', ("local_vars.json", json.dumps(local_vars, 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)

View File

@ -1,16 +1,17 @@
bumpversion==0.5.3
coverage==5.0.3
bump2version==1.0.0
coverage==5.1
edx-sphinx-theme==1.5.0
flake8==3.7.9
ipython==7.12.0
flake8==3.8.3
ipython==7.15.0
isort==4.3.21
pip==20.0.2
pip==20.1.1
PyInstaller==3.6
pytz==2019.3
pytz==2020.1
requests==2.23.0
setuptools==45.2.0
Sphinx==2.4.2
tox==3.14.5
responses==0.10.15
setuptools==47.1.1
Sphinx==3.1.1
tox==3.15.2
twine==3.1.1
watchdog==0.10.2
wheel==0.34.2

View File

@ -1,10 +1,9 @@
[bumpversion]
current_version = 0.20.0
current_version = 0.20.1.1
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
serialize =
{major}.{minor}.{patch}.{dev}
serialize = {major}.{minor}.{patch}.{dev}
{major}.{minor}.{patch}
[bumpversion:file:setup.py]
@ -24,4 +23,3 @@ max-line-length = 120
ignore = E722 F401
[aliases]

View File

@ -11,7 +11,7 @@ with open('README.rst') as readme_file:
with open('HISTORY.rst') as history_file:
history = history_file.read()
requirements = ['pytz==2019.3', 'requests==2.23.0']
requirements = ['pytz==2020.1', 'requests==2.23.0']
setup_requirements = []
@ -38,11 +38,11 @@ setup(
keywords='erepublik',
name='eRepublik',
packages=find_packages(include=['erepublik']),
python_requires='>=3.7.*, <4',
python_requires='>=3.7, <4',
setup_requires=setup_requirements,
test_suite='tests',
tests_require=test_requirements,
url='https://github.com/eeriks/erepublik/',
version='0.20.0',
version='0.20.1.1',
zip_safe=False,
)