Compare commits

...

26 Commits

Author SHA1 Message Date
7ac22b5e11 Bump version: 0.20.1.6 → 0.20.1.7 2020-07-03 23:56:07 +03:00
5bd3d72a63 Quickfix for summer terrains 2020-07-03 23:54:32 +03:00
33d2c641df Config.json creator HTML 2020-07-03 15:32:13 +03:00
d1e078e443 Bump version: 0.20.1.5 → 0.20.1.6 2020-06-29 14:58:27 +03:00
71c64b0cf5 minor tweaks 2020-06-29 14:57:40 +03:00
3b5b15553d Bomb deploy bugfix where deploying in RW could leave player stranded in non residency region. Battle update bugfix where self.all_battles could be empty while re-parsing every battle 2020-06-29 14:48:37 +03:00
d077e10f15 Bump version: 0.20.1.4 → 0.20.1.5 2020-06-26 17:27:11 +03:00
8eb5235f12 Deploy bombs in RWs bugfix 2020-06-26 17:27:02 +03:00
a873d223c1 Bump version: 0.20.1.3 → 0.20.1.4 2020-06-25 14:18:41 +03:00
ed96143c80 Flake8
Battle.div is no longer a defaultdict, but should raise error if trying to access division which isn't assigned
2020-06-25 14:17:05 +03:00
b49e4ab594 Bump version: 0.20.1.2 → 0.20.1.3 2020-06-19 13:44:39 +03:00
2f69090c03 bugfix 2020-06-19 13:44:33 +03:00
df170048af Bump version: 0.20.1.1 → 0.20.1.2 2020-06-19 13:37:02 +03:00
8ca845cf17 Add damage amount to inventory bomb 2020-06-19 13:36:45 +03:00
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
12 changed files with 865 additions and 257 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 language: python
python: python:
- 3.8
- 3.7 - 3.7
- 3.6
# Command to install dependencies, e.g. pip install -r requirements_dev.txt --use-mirrors # Command to install dependencies, e.g. pip install -r requirements_dev.txt --use-mirrors
install: pip install -U tox-travis install: pip install -U tox-travis

View File

@ -2,6 +2,17 @@
History 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) 0.19.0 (2020-01-13)
------------------- -------------------
* Created method for current products on sale. * Created method for current products on sale.

387
docs/index.html Normal file
View File

@ -0,0 +1,387 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="Eriks Karls">
<meta name="generator" content="Jekyll v4.0.1">
<title>eBot configuration</title>
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
crossorigin="anonymous">
<!-- JS, Popper.js, and jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI"
crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="p-3 text-center">
<h1>eBot configuration file generator</h1>
<!-- <h2>-->
<!-- <span class="d-inline d-sm-none d-md-none d-lg-none d-xl-none">XS</span>-->
<!-- <span class="d-none d-sm-inline d-md-none d-lg-none d-xl-none">SM</span>-->
<!-- <span class="d-none d-sm-none d-md-inline d-lg-none d-xl-none">MD</span>-->
<!-- <span class="d-none d-sm-none d-md-none d-lg-inline d-xl-none">LG</span>-->
<!-- <span class="d-none d-sm-none d-md-none d-lg-none d-xl-inline">XL</span>-->
<!-- </h2>-->
</div>
<div class="row pt-4">
<div class="col-12">
<form>
<div class="row">
<div class="col-12 col-sm-8 col-md-6">
<h3>Login data</h3>
<div class="form-group">
<input type="email" class="form-control form-control-sm" onchange="updateJson()" id="email" placeholder="E-mail...">
<input type="password" class="form-control form-control-sm mt-3" onchange="updateJson()" id="password" disabled placeholder="Password..."
aria-describedby="passwordHelpBlock">
<small id="passwordHelpBlock" class="form-text text-muted"><strong>NEVER</strong> enter Your passwords on 3rd party sites and <strong class="text-upper">DO NOT</strong> reuse Your
password!</small>
</div>
</div>
<div class="col-6 col-md-3">
<h3>Basic tasks</h3>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="work" checked>
<label class="custom-control-label" for="work">Work</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="train" checked>
<label class="custom-control-label" for="train">Train</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="ot" checked>
<label class="custom-control-label" for="ot">Work overtime</label>
</div>
</div>
<div class="col-6 col-md-3">
<h3>Misc</h3>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="renew_houses" checked>
<label class="custom-control-label" for="renew_houses">Auto renew houses</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="random_sleep" checked>
<label class="custom-control-label" for="random_sleep">Random sleep</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="buy_gold">
<label class="custom-control-label" for="buy_gold">Auto buy 10g</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="interactive" checked>
<label class="custom-control-label" for="interactive">Interactive</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="debug">
<label class="custom-control-label" for="debug">Debug</label>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-sm-6">
<h3>Production</h3>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="wam" checked>
<label class="custom-control-label" for="wam">Work as manager</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="employ">
<label class="custom-control-label" for="employ">Employ employees</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_buy_raw" checked>
<label class="custom-control-label" for="auto_buy_raw">Auto buy missing RAW</label>
</div>
</div>
<div class="form-group">
<h6 class="">Auto sell produced products</h6>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_frm">
<label class="custom-control-label" for="auto_sell_frm">Food Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_food">
<label class="custom-control-label" for="auto_sell_food">Food</label>
</div>
<div class="clearfix"></div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_wrm">
<label class="custom-control-label" for="auto_sell_wrm">Weapon Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_weapons">
<label class="custom-control-label" for="auto_sell_weapons">Weapon</label>
</div>
<div class="clearfix"></div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_hrm">
<label class="custom-control-label" for="auto_sell_hrm">House Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_house">
<label class="custom-control-label" for="auto_sell_house">House</label>
</div>
<div class="clearfix"></div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_arm">
<label class="custom-control-label" for="auto_sell_arm">Aircraft Raw</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_air">
<label class="custom-control-label" for="auto_sell_air">Aircraft Weapon</label>
</div>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="auto_sell_all">
<label class="custom-control-label" for="auto_sell_all">Auto sell all (also from inventory)</label>
</div>
</div>
<div class="col-12 col-sm-6">
<h3>Fighting</h3>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="fight" checked>
<label class="custom-control-label" for="fight">Fight</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="air" checked>
<label class="custom-control-label" for="air">Air</label>
</div>
<div class="custom-control custom-switch custom-control-inline">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="ground">
<label class="custom-control-label" for="ground">Ground</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="boosters">
<label class="custom-control-label" for="boosters">Use ground boosters</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="continuous_fighting">
<label class="custom-control-label" for="continuous_fighting">Continue fighting all FF in round</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="next_energy" checked>
<label class="custom-control-label" for="next_energy">Fight for next WC +1hp/6min if reachable by FF</label>
</div>
</div>
<div class="form-group">
<div class="form-check form-check-inline">
<input type="radio" class="form-check-input" onchange="updateJson()" id="all_in" name="fight_amount" value="all_in">
<label class="form-check-label" for="all_in">All energy</label>
</div>
<div class="form-check form-check-inline">
<input type="radio" class="form-check-input" onchange="updateJson()" id="h_energy" name="fight_amount" value="h_energy" checked>
<label class="form-check-label" for="h_energy">1h energy</label>
</div>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="rw_def_side" checked>
<label class="custom-control-label" for="rw_def_side">In RWs fight on right side (occupier/defender)</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="travel_to_fight" checked>
<label class="custom-control-label" for="travel_to_fight">Travel to fight</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="epic_hunt">
<label class="custom-control-label" for="epic_hunt">Hunt epics</label>
</div>
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" onchange="updateJson()" id="epic_hunt_ebs">
<label class="custom-control-label" for="epic_hunt_ebs">Spend <small>[all]</small> EBs in epics</label>
</div>
</div>
</div>
</form>
</div>
<div class="col-12">
<pre id="json-output"></pre>
</div>
</div>
</div>
<script>
function disable(element){
element.checked = false;
element.disabled = true;
}
function updateJson() {
let config = {};
let email = document.getElementById('email'); // Generated
config.email = email.value;
config.password = "";
let work = document.getElementById('work'); // Generated
config.work = work.checked;
let train = document.getElementById('train'); // Generated
config.train = train.checked;
let ot = document.getElementById('ot'); // Generated
config.ot = ot.checked;
let renew_houses = document.getElementById('renew_houses'); // Generated
config.renew_houses = renew_houses.checked;
let random_sleep = document.getElementById('random_sleep'); // Generated
config.random_sleep = random_sleep.checked;
let buy_gold = document.getElementById('buy_gold'); // Generated
config.buy_gold = buy_gold.checked;
let interactive = document.getElementById('interactive'); // Generated
config.interactive = interactive.checked;
let debug = document.getElementById('debug'); // Generated
config.debug = debug.checked;
let wam = document.getElementById('wam'); // Generated
config.wam = wam.checked;
let employ = document.getElementById('employ'); // Generated
config.employ = employ.checked;
let auto_buy_raw = document.getElementById('auto_buy_raw'); // Generated
let auto_sell_all = document.getElementById('auto_sell_all'); // Generated
let auto_sell_frm = document.getElementById('auto_sell_frm'); // Generated
let auto_sell_food = document.getElementById('auto_sell_food'); // Generated
let auto_sell_wrm = document.getElementById('auto_sell_wrm'); // Generated
let auto_sell_weapons = document.getElementById('auto_sell_weapons'); // Generated
let auto_sell_hrm = document.getElementById('auto_sell_hrm'); // Generated
let auto_sell_house = document.getElementById('auto_sell_house'); // Generated
let auto_sell_arm = document.getElementById('auto_sell_arm'); // Generated
let auto_sell_air = document.getElementById('auto_sell_air'); // Generated
if (config.wam || config.employ) {
auto_buy_raw.disabled = false;
auto_sell_all.disabled = false;
auto_sell_frm.disabled = false;
auto_sell_food.disabled = false;
auto_sell_wrm.disabled = false;
auto_sell_weapons.disabled = false;
auto_sell_hrm.disabled = false;
auto_sell_house.disabled = false;
auto_sell_arm.disabled = false;
auto_sell_air.disabled = false;
} else {
disable(auto_buy_raw);
disable(auto_sell_all);
disable(auto_sell_food);
disable(auto_sell_weapons);
disable(auto_sell_house);
disable(auto_sell_air);
disable(auto_sell_frm);
disable(auto_sell_wrm);
disable(auto_sell_hrm);
disable(auto_sell_arm);
}
config.auto_buy_raw = auto_buy_raw.checked;
config.auto_sell_all = auto_sell_all.checked;
config.auto_sell = [];
if (auto_sell_food.checked) config.auto_sell.push("food");
if (auto_sell_weapons.checked) config.auto_sell.push("weapon");
if (auto_sell_house.checked) config.auto_sell.push("house");
if (auto_sell_air.checked) config.auto_sell.push("airplane");
if (auto_sell_frm.checked) config.auto_sell.push("foodRaw");
if (auto_sell_wrm.checked) config.auto_sell.push("weaponRaw");
if (auto_sell_hrm.checked) config.auto_sell.push("houseRaw");
if (auto_sell_arm.checked) config.auto_sell.push("airplaneRaw");
let fight = document.getElementById('fight'); // Generated
config.fight = fight.checked;
let air = document.getElementById('air'); // Generated
let ground = document.getElementById('ground'); // Generated
let boosters = document.getElementById('boosters'); // Generated
let continuous_fighting = document.getElementById('continuous_fighting'); // Generated
let next_energy = document.getElementById('next_energy'); // Generated
let all_in = document.getElementById('all_in'); // Generated
let h_energy = document.getElementById('h_energy'); // Generated
let rw_def_side = document.getElementById('rw_def_side'); // Generated
let travel_to_fight = document.getElementById('travel_to_fight'); // Generated
let epic_hunt = document.getElementById('epic_hunt'); // Generated
let epic_hunt_ebs = document.getElementById('epic_hunt_ebs'); // Generated
if (config.fight){
air.disabled = false;
ground.disabled = false;
boosters.disabled = false;
continuous_fighting.disabled = false;
next_energy.disabled = false;
all_in.disabled = false;
h_energy.disabled = false;
rw_def_side.disabled = false;
travel_to_fight.disabled = false;
epic_hunt.disabled = false;
epic_hunt_ebs.disabled = false;
if (!epic_hunt.checked) disable(epic_hunt_ebs);
} else {
disable(air);
disable(ground);
disable(boosters);
disable(continuous_fighting);
disable(next_energy);
disable(all_in);
disable(h_energy);
disable(rw_def_side);
disable(travel_to_fight);
disable(epic_hunt);
disable(epic_hunt_ebs);
}
config.air = air.checked;
config.ground = ground.cehcked;
config.boosters = boosters.checked;
config.continuous_fighting = continuous_fighting.checked;
config.next_energy = next_energy.checked;
config.all_in = all_in.checked;
config.rw_def_side = rw_def_side.checked;
config.travel_to_fight = travel_to_fight.checked;
config.epic_hunt = epic_hunt.checked;
config.epic_hunt_ebs = config.epic_hunt ? epic_hunt_ebs.checked : config.epic_hunt;
let pre = document.getElementById('json-output');
pre.textContent = JSON.stringify(config, null, 2);
}
updateJson();
</script>
</body>
</html>
<!--
{
"email": "",
"password": "",
"work": true,
"train": true,
"ot": true,
"renew_houses": true,
"random_sleep": true,
"buy_gold": true,
"interactive": true,
"debug": true,
"wam": true,
"employ": true,
"auto_buy_raw": true,
"auto_sell_all": true,
"auto_sell": [
"food",
"weapon",
"house",
"airplane",
"foodRaw",
"weaponRaw",
"houseRaw",
"airplaneRaw"
],
"fight": true,
"air": true,
"boosters": true,
"continuous_fighting": true,
"next_energy": true,
"all_in": false,
"rw_def_side": true,
"travel_to_fight": true,
"epic_hunt": true,
"epic_hunt_ebs": true
}
-->

View File

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

View File

@ -12,8 +12,8 @@ from requests import HTTPError, RequestException, Response
from erepublik import utils from erepublik import utils
from erepublik.access_points import CitizenAPI from erepublik.access_points import CitizenAPI
from erepublik.classes import (Battle, BattleDivision, Config, Details, Energy, ErepublikException, from erepublik.classes import Battle, BattleDivision, Config, Details, Energy, \
MyCompanies, MyJSONEncoder, OfferItem, Politics, Reporter, TelegramBot) ErepublikException, MyCompanies, MyJSONEncoder, OfferItem, Politics, Reporter, TelegramBot
class BaseCitizen(CitizenAPI): class BaseCitizen(CitizenAPI):
@ -322,6 +322,14 @@ class BaseCitizen(CitizenAPI):
if item.get('type') in ('damageBoosters', "aircraftDamageBoosters"): if item.get('type') in ('damageBoosters', "aircraftDamageBoosters"):
data = {data['durability']: data} data = {data['durability']: data}
else: else:
if item.get('type') == 'bomb':
firepower = 0
try:
firepower = item.get('attributes').get('firePower').get('value', 0)
except AttributeError:
pass
finally:
data.update(fire_power=firepower)
data = {data['quality']: data} data = {data['quality']: data}
final_items[kind].update(data) final_items[kind].update(data)
@ -427,7 +435,7 @@ class BaseCitizen(CitizenAPI):
def __dict__(self): def __dict__(self):
ret = super().__dict__.copy() ret = super().__dict__.copy()
ret.pop('stop_threads', None) ret.pop('stop_threads', None)
ret.pop('_Citizen__last_war_update_data', None) ret.pop('_CitizenMilitary__last_war_update_data', None)
return ret return ret
@ -656,7 +664,17 @@ class BaseCitizen(CitizenAPI):
return bool(re.search(r'body id="error"|Internal Server Error|' return bool(re.search(r'body id="error"|Internal Server Error|'
r'CSRF attack detected|meta http-equiv="refresh"|not_authenticated', response.text)) r'CSRF attack detected|meta http-equiv="refresh"|not_authenticated', response.text))
def _report_action(self, action: str, msg: str, **kwargs): def _report_action(self, action: str, msg: str, **kwargs: Optional[Dict[str, Any]]):
""" Report action to all available reporting channels
:type action: str
:type msg: str
:type kwargs: Optional[Dict[str, Any]]
:param action: Action taken
:param msg: Message about the action
:param kwargs: Extra information regarding action
"""
kwargs = utils.json.loads(utils.json.dumps(kwargs or {}, cls=MyJSONEncoder))
action = action[:32] action = action[:32]
self.write_log(msg) self.write_log(msg)
if self.reporter.allowed: if self.reporter.allowed:
@ -829,8 +847,8 @@ class CitizenCompanies(BaseCitizen):
raw_factories = True raw_factories = True
free_inventory = self.inventory["total"] - self.inventory["used"] free_inventory = self.inventory["total"] - self.inventory["used"]
wam_list = self.my_companies.get_holding_wam_companies(wam_holding_id, wam_list = self.my_companies.get_holding_wam_companies(
raw_factory=raw_factories)[:self.energy.food_fights] wam_holding_id, raw_factory=raw_factories)[:self.energy.food_fights]
has_space = False has_space = False
while not has_space and wam_list: while not has_space and wam_list:
extra_needed = self.my_companies.get_needed_inventory_usage(companies=wam_list) extra_needed = self.my_companies.get_needed_inventory_usage(companies=wam_list)
@ -842,7 +860,7 @@ class CitizenCompanies(BaseCitizen):
data.update(extra) data.update(extra)
if wam_list: if wam_list:
wam_holding = self.my_companies.holdings.get(wam_holding_id) 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!") self.write_log("Unable to work as manager because of location - please travel!")
return return
@ -850,7 +868,8 @@ class CitizenCompanies(BaseCitizen):
if sum(employ_factories.values()) > self.my_companies.work_units: if sum(employ_factories.values()) > self.my_companies.work_units:
employ_factories = {} 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 return response
def update_companies(self): def update_companies(self):
@ -861,19 +880,15 @@ class CitizenCompanies(BaseCitizen):
have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html) have_holdings = re.search(r"var holdingCompanies\s+= ({.*}});", html)
have_companies = re.search(r"var companies\s+= ({.*}});", html) have_companies = re.search(r"var companies\s+= ({.*}});", html)
if have_holdings and have_companies: if have_holdings and have_companies:
self.my_companies.prepare_companies(utils.json.loads(have_companies.group(1)))
self.my_companies.prepare_holdings(utils.json.loads(have_holdings.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: def assign_factory_to_holding(self, factory_id: int, holding_id: int) -> Response:
""" """
Assigns factory to new holding Assigns factory to new holding
""" """
company = self.my_companies.companies[factory_id] company = self.my_companies.companies[factory_id]
company_name = self.factories[company['industry_id']] self.write_log(f"{company} moved to {holding_id}")
if not company['is_raw']:
company_name += f" q{company['quality']}"
self.write_log(f"{company_name} moved to {holding_id}")
return self._post_economy_assign_to_holding(factory_id, holding_id) return self._post_economy_assign_to_holding(factory_id, holding_id)
def upgrade_factory(self, factory_id: int, level: int) -> Response: def upgrade_factory(self, factory_id: int, level: int) -> Response:
@ -887,18 +902,19 @@ class CitizenCompanies(BaseCitizen):
Storage={1000: 1, 2000: 2} <- Building_type 2 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: if building_type == 2:
company_name = f"Storage" company_name = "Storage"
self.write_log(f"{company_name} created!") self.write_log(f'{company_name} created!')
return self._post_economy_create_company(industry_id, building_type) return self._post_economy_create_company(industry_id, building_type)
def dissolve_factory(self, factory_id: int) -> Response: def dissolve_factory(self, factory_id: int) -> Response:
company = self.my_companies.companies[factory_id] company = self.my_companies.companies[factory_id]
company_name = self.factories[company['industry_id']] self.write_log(f"{company} dissolved!")
if not company['is_raw']:
company_name += f" q{company['quality']}"
self.write_log(f"{company_name} dissolved!")
return self._post_economy_sell_company(factory_id, self.details.pin, sell=False) return self._post_economy_sell_company(factory_id, self.details.pin, sell=False)
@ -1030,7 +1046,7 @@ class CitizenEconomy(CitizenTravel):
ret = self._post_economy_marketplace_actions(**data) ret = self._post_economy_marketplace_actions(**data)
message = (f"Posted market offer for {amount}q{quality} " message = (f"Posted market offer for {amount}q{quality} "
f"{self.get_industry_name(industry)} for price {price}cc") 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 return ret
def buy_from_market(self, offer: int, amount: int) -> dict: def buy_from_market(self, offer: int, amount: int) -> dict:
@ -1042,7 +1058,7 @@ class CitizenEconomy(CitizenTravel):
self.details.cc = ret.json()['currency'] self.details.cc = ret.json()['currency']
self.details.gold = ret.json()['gold'] self.details.gold = ret.json()['gold']
json_ret.pop("offerUpdate", None) json_ret.pop("offerUpdate", None)
self._report_action("BOUGHT_PRODUCTS", "", **json_ret) self._report_action("BOUGHT_PRODUCTS", "", kwargs=json_ret)
return json_ret return json_ret
def get_market_offers(self, product_name: str, quality: int = None, country_id: int = None) -> Dict[str, OfferItem]: def get_market_offers(self, product_name: str, quality: int = None, country_id: int = None) -> Dict[str, OfferItem]:
@ -1102,7 +1118,7 @@ class CitizenEconomy(CitizenTravel):
if amount * cheapest.price < self.details.cc: if amount * cheapest.price < self.details.cc:
data = dict(offer=cheapest.offer_id, amount=amount, price=cheapest.price, 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]) 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.buy_from_market(cheapest.offer_id, amount)
self.update_inventory() self.update_inventory()
else: else:
@ -1139,11 +1155,12 @@ class CitizenEconomy(CitizenTravel):
self.details.cc = float(response.json().get("ecash").get("value")) self.details.cc = float(response.json().get("ecash").get("value"))
self.details.gold = float(response.json().get("gold").get("value")) self.details.gold = float(response.json().get("gold").get("value"))
if response.json().get('error'): 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() self.stop_threads.wait()
return False return False
else: 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 return True
def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62) -> bool: def donate_money(self, citizen_id: int = 1620414, amount: float = 0.0, currency: int = 62) -> bool:
@ -1168,23 +1185,25 @@ class CitizenEconomy(CitizenTravel):
if re.search(rf"Successfully transferred {amount} item\(s\) to", response.text): if re.search(rf"Successfully transferred {amount} item\(s\) to", response.text):
msg = (f"Successfully donated {amount}q{quality} {self.get_industry_name(industry_id)} " msg = (f"Successfully donated {amount}q{quality} {self.get_industry_name(industry_id)} "
f"to citizen with id {citizen_id}!") f"to citizen with id {citizen_id}!")
self._report_action("DONATE_ITEMS", msg, success=True) self._report_action("DONATE_ITEMS", msg)
return amount return amount
elif re.search('You must wait 5 seconds before donating again', response.text): elif re.search('You must wait 5 seconds before donating again', response.text):
self.write_log(f"Previous donation failed! Must wait at least 5 seconds before next donation!") self.write_log('Previous donation failed! Must wait at least 5 seconds before next donation!')
self.sleep(5) self.sleep(5)
return self.donate_items(citizen_id, int(amount), industry_id, quality) return self.donate_items(citizen_id, int(amount), industry_id, quality)
else: else:
if re.search(r"You do not have enough items in your inventory to make this donation", response.text): if re.search(r'You do not have enough items in your inventory to make this donation', response.text):
self._report_action("DONATE_ITEMS", self._report_action("DONATE_ITEMS",
f"Unable to donate {amount}q{quality} " f"Unable to donate {amount}q{quality} "
f"{self.get_industry_name(industry_id)}, not enough left!", success=False) f"{self.get_industry_name(industry_id)}, not enough left!")
return 0 return 0
available = re.search(rf"Cannot transfer the items because the user has only (\d+) free slots in (his|her) " available = re.search(
rf"storage.", response.text).group(1) r'Cannot transfer the items because the user has only (\d+) free slots in (his|her) storage.',
self._report_action("DONATE_ITEMS", response.text
f"Unable to donate {amount}q{quality}{self.get_industry_name(industry_id)}" ).group(1)
f", receiver has only {available} storage left!", success=False) 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!')
self.sleep(5) self.sleep(5)
return self.donate_items(citizen_id, int(available), industry_id, quality) return self.donate_items(citizen_id, int(available), industry_id, quality)
@ -1196,12 +1215,11 @@ class CitizenEconomy(CitizenTravel):
data = dict(country=country_id, action='currency', value=amount) data = dict(country=country_id, action='currency', value=amount)
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
if r.json().get('status') or not r.json().get('error'): if r.json().get('status') or not r.json().get('error'):
self._report_action("CONTRIBUTE_CC", f"Contributed {amount}cc to {utils.COUNTRIES[country_id]}'s treasury", self._report_action("CONTRIBUTE_CC", f'Contributed {amount}cc to {utils.COUNTRIES[country_id]}\'s treasury')
success=True)
return True return True
else: else:
self._report_action("CONTRIBUTE_CC", f"Unable to contribute {amount}cc to {utils.COUNTRIES[country_id]}'s" 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 return False
def contribute_food_to_country(self, amount: int = 0, quality: int = 1, country_id: int = 71) -> bool: def contribute_food_to_country(self, amount: int = 0, quality: int = 1, country_id: int = 71) -> bool:
@ -1214,11 +1232,11 @@ class CitizenEconomy(CitizenTravel):
if r.json().get('status') or not r.json().get('error'): if r.json().get('status') or not r.json().get('error'):
self._report_action("CONTRIBUTE_FOOD", f"Contributed {amount}q{quality} food to " self._report_action("CONTRIBUTE_FOOD", f"Contributed {amount}q{quality} food to "
f"{utils.COUNTRIES[country_id]}'s treasury", success=True) f"{utils.COUNTRIES[country_id]}'s treasury")
return True return True
else: else:
self._report_action("CONTRIBUTE_FOOD", f"Unable to contribute {amount}q{quality} food to " 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 return False
def contribute_gold_to_country(self, amount: int, country_id: int = 71) -> bool: def contribute_gold_to_country(self, amount: int, country_id: int = 71) -> bool:
@ -1231,12 +1249,11 @@ class CitizenEconomy(CitizenTravel):
r = self._post_main_country_donate(**data) r = self._post_main_country_donate(**data)
if r.json().get('status') or not r.json().get('error'): if r.json().get('status') or not r.json().get('error'):
self._report_action("CONTRIBUTE_GOLD", f"Contributed {amount}g to {utils.COUNTRIES[country_id]}'s treasury", self._report_action("CONTRIBUTE_GOLD", f"Contributed {amount}g to {utils.COUNTRIES[country_id]}'s treasury")
success=True)
return True return True
else: else:
self._report_action("CONTRIBUTE_GOLD", f"Unable to contribute {amount}g to {utils.COUNTRIES[country_id]}'s" 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 return False
@ -1259,11 +1276,11 @@ class CitizenMedia(BaseCitizen):
if amount in (5, 50, 100): if amount in (5, 50, 100):
resp = self._post_main_donate_article(article_id, amount).json() resp = self._post_main_donate_article(article_id, amount).json()
if not bool(resp.get('error')): if not bool(resp.get('error')):
self._report_action("ARTICLE_ENDORSE", f"Endorsed article ({article_id}) with {amount}cc", success=True) self._report_action("ARTICLE_ENDORSE", f"Endorsed article ({article_id}) with {amount}cc")
return True return True
else: else:
self._report_action("ARTICLE_ENDORSE", f"Unable to endorse article ({article_id}) with {amount}cc", self._report_action("ARTICLE_ENDORSE", f"Unable to endorse article ({article_id}) with {amount}cc",
**resp) kwargs=resp)
return False return False
else: else:
return False return False
@ -1272,10 +1289,10 @@ class CitizenMedia(BaseCitizen):
resp = self._post_main_vote_article(article_id).json() resp = self._post_main_vote_article(article_id).json()
if not bool(resp.get('error')): if not bool(resp.get('error')):
self._report_action("ARTICLE_VOTE", f"Voted article {article_id}", success=True) self._report_action("ARTICLE_VOTE", f"Voted article {article_id}")
return True return True
else: 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 return False
def get_article_comments(self, article_id: int, page_id: int = 1) -> Dict[str, Any]: def get_article_comments(self, article_id: int, page_id: int = 1) -> Dict[str, Any]:
@ -1295,7 +1312,7 @@ class CitizenMedia(BaseCitizen):
resp = self._post_main_write_article(**data) resp = self._post_main_write_article(**data)
try: try:
article_id = int(resp.history[1].url.split("/")[-3]) 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 except: # noqa
article_id = 0 article_id = 0
return article_id return article_id
@ -1325,25 +1342,23 @@ class CitizenMilitary(CitizenTravel):
if r_json.get("countries"): if r_json.get("countries"):
if self.all_battles is None: if self.all_battles is None:
self.all_battles = {} self.all_battles = {}
else:
self.all_battles.clear()
if self.countries is None: if self.countries is None:
self.countries = {} self.countries = {}
else: countries = {}
self.countries.clear()
for c_id, c_data in r_json.get("countries").items(): for c_id, c_data in r_json.get("countries").items():
if int(c_id) not in self.countries: if int(c_id) not in countries:
self.countries.update({ countries.update({
int(c_id): {"name": c_data.get("name"), "allies": c_data.get("allies")} int(c_id): {"name": c_data.get("name"), "allies": c_data.get("allies")}
}) })
else: else:
self.countries[int(c_id)].update(allies=c_data.get("allies")) countries[int(c_id)].update(allies=c_data.get("allies"))
self.countries = countries
self.__last_war_update_data = r_json self.__last_war_update_data = r_json
if r_json.get("battles"): if r_json.get("battles"):
all_battles = {}
for battle_data in r_json.get("battles", {}).values(): for battle_data in r_json.get("battles", {}).values():
self.all_battles[battle_data.get('id')] = Battle(battle_data) all_battles[battle_data.get('id')] = Battle(battle_data)
self.all_battles = all_battles
def get_battle_for_war(self, war_id: int) -> Optional[Battle]: def get_battle_for_war(self, war_id: int) -> Optional[Battle]:
self.update_war_info() self.update_war_info()
@ -1383,7 +1398,7 @@ class CitizenMilitary(CitizenTravel):
if not battle.is_air: if not battle.is_air:
for weapon in available_weapons: for weapon in available_weapons:
try: try:
if weapon['weaponQuantity'] > 30 and weapon['damage'] > weapon_damage: if weapon['weaponQuantity'] > 30 and weapon['weaponInfluence'] > weapon_damage:
weapon_quality = int(weapon['weaponId']) weapon_quality = int(weapon['weaponId'])
except ValueError: except ValueError:
pass pass
@ -1394,7 +1409,8 @@ class CitizenMilitary(CitizenTravel):
battle_zone = battle.div[11 if battle.is_air else self.division].battle_zone_id battle_zone = battle.div[11 if battle.is_air else self.division].battle_zone_id
r = self._post_military_change_weapon(battle_id, battle_zone, quality) r = self._post_military_change_weapon(battle_id, battle_zone, quality)
influence = r.json().get('weaponInfluence') 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,"
f" new influence {influence}", kwargs=r.json())
return influence return influence
def check_epic_battles(self): def check_epic_battles(self):
@ -1462,7 +1478,7 @@ class CitizenMilitary(CitizenTravel):
battle_list = sorted(self.all_battles.values(), key=lambda b: b.id) battle_list = sorted(self.all_battles.values(), key=lambda b: b.id)
contribution_json: Response = self._get_military_campaigns_json_citizen() 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')) 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')] ret_battles += [int(b.get('battle_id', 0)) for b in contributions if b.get('battle_id')]
@ -1681,6 +1697,9 @@ class CitizenMilitary(CitizenTravel):
count = 1 count = 1
has_traveled = False has_traveled = False
battle = self.all_battles.get(battle_id) battle = self.all_battles.get(battle_id)
if battle.is_rw:
has_traveled = self.travel_to_battle(battle_id, [battle.defender.id])
self._rw_choose_side(battle.id, battle.invader.id if inv_side else battle.defender.id)
if inv_side: if inv_side:
good_countries = [battle.invader.id] + battle.invader.deployed good_countries = [battle.invader.id] + battle.invader.deployed
if self.details.current_country not in good_countries: if self.details.current_country not in good_countries:
@ -1700,6 +1719,8 @@ class CitizenMilitary(CitizenTravel):
deployed_count += 1 deployed_count += 1
elif r.get('message') == 'LOCKED': elif r.get('message') == 'LOCKED':
sleep(0.5) sleep(0.5)
else:
errors += 1
if has_traveled: if has_traveled:
self.travel_to_residence() self.travel_to_residence()
@ -1766,11 +1787,11 @@ class CitizenMilitary(CitizenTravel):
return quality return quality
def activate_battle_effect(self, battle_id: int, kind: str) -> Response: def activate_battle_effect(self, battle_id: int, kind: str) -> Response:
self._report_action("MILITARY_BOOSTER", f"Activated {kind} booster") self._report_action('MILITARY_BOOSTER', f'Activated {kind} booster')
return self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id) return self._post_main_activate_battle_effect(battle_id, kind, self.details.citizen_id)
def activate_pp_booster(self, battle_id: int) -> Response: def activate_pp_booster(self, battle_id: int) -> Response:
self._report_action("MILITARY_BOOSTER", f"Activated PrestigePoint booster") self._report_action('MILITARY_BOOSTER', 'Activated PrestigePoint booster')
return self._post_military_fight_activate_booster(battle_id, 1, 180, "prestige_points") return self._post_military_fight_activate_booster(battle_id, 1, 180, "prestige_points")
def _rw_choose_side(self, battle_id: int, side_id: int) -> Response: def _rw_choose_side(self, battle_id: int, side_id: int) -> Response:
@ -1947,29 +1968,29 @@ class CitizenPolitics(BaseCitizen):
return ret return ret
def candidate_for_congress(self, presentation: str = "") -> Response: def candidate_for_congress(self, presentation: str = "") -> Response:
self._report_action("POLITIC_CONGRESS", f"Applied for congress elections") self._report_action('POLITIC_CONGRESS', 'Applied for congress elections')
return self._post_candidate_for_congress(presentation) return self._post_candidate_for_congress(presentation)
def candidate_for_party_presidency(self) -> Response: def candidate_for_party_presidency(self) -> Response:
self._report_action("POLITIC_PARTY_PRESIDENT", f"Applied for party president elections") self._report_action('POLITIC_PARTY_PRESIDENT', 'Applied for party president elections')
return self._get_candidate_party(self.politics.party_slug) return self._get_candidate_party(self.politics.party_slug)
class CitizenSocial(BaseCitizen): class CitizenSocial(BaseCitizen):
def send_mail_to_owner(self): def send_mail_to_owner(self):
if not self.details.citizen_id == 1620414: if not self.details.citizen_id == 1620414:
self.send_mail("Started", "time {}".format(self.now.strftime("%Y-%m-%d %H-%M-%S")), [1620414, ]) self.send_mail('Started', f'time {self.now.strftime("%Y-%m-%d %H-%M-%S")}', [1620414, ])
self.sleep(1) self.sleep(1)
msg_id = re.search(r"<input type=\"hidden\" value=\"(\d+)\" " msg_id = re.search(r'<input type="hidden" value="(\d+)" '
r"id=\"delete_message_(\d+)\" name=\"delete_message\[]\">", self.r.text).group(1) r'id="delete_message_(\d+)" name="delete_message\[]">', self.r.text).group(1)
self._post_delete_message([msg_id]) self._post_delete_message([msg_id])
def send_mail(self, subject: str, msg: str, ids: List[int] = None): def send_mail(self, subject: str, msg: str, ids: List[int] = None):
if ids is None: if ids is None:
ids = [1620414, ] ids = [1620414, ]
for player_id in ids: for player_id in ids:
self._report_action("SOCIAL_MESSAGE", f"Sent a message to {player_id}", **dict(subject=subject, msg=msg, self._report_action('SOCIAL_MESSAGE', f'Sent a message to {player_id}',
id=player_id)) kwargs=dict(subject=subject, msg=msg, id=player_id))
self._post_main_messages_compose(subject, msg, [player_id]) self._post_main_messages_compose(subject, msg, [player_id])
def write_on_country_wall(self, message: str) -> bool: def write_on_country_wall(self, message: str) -> bool:
@ -1978,16 +1999,16 @@ class CitizenSocial(BaseCitizen):
self.r.text, re.S | re.M) 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) 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', 'Wrote a message to the country wall')
return r.json() return r.json()
def add_friend(self, player_id: int) -> Response: def add_friend(self, player_id: int) -> Response:
resp = self._get_main_citizen_hovercard(player_id) resp = self._get_main_citizen_hovercard(player_id)
r_json = resp.json() r_json = resp.json()
if not any([r_json["isBanned"], r_json["isDead"], r_json["isFriend"], r_json["isOrg"], r_json["isSelf"]]): if not any([r_json['isBanned'], r_json['isDead'], r_json['isFriend'], r_json['isOrg'], r_json['isSelf']]):
r = self._post_main_citizen_add_remove_friend(int(player_id), True) r = self._post_main_citizen_add_remove_friend(int(player_id), True)
self.write_log(f"{r_json['name']:<64} (id:{player_id:>11}) added as friend") self.write_log(f"{r_json['name']:<64} (id:{player_id:>11}) added as friend")
self._report_action("SOCIAL_ADD_FRIEND", f"{r_json['name']:<64} (id:{player_id:>11}) added as friend") self._report_action('SOCIAL_ADD_FRIEND', f"{r_json['name']:<64} (id:{player_id:>11}) added as friend")
return r return r
return resp return resp
@ -2154,7 +2175,7 @@ class CitizenTasks(BaseCitizen):
def resign_from_employer(self) -> bool: def resign_from_employer(self) -> bool:
r = self.update_job_info() r = self.update_job_info()
if r.json().get("isEmployee"): if r.json().get("isEmployee"):
self._report_action("ECONOMY_RESIGN", f"Resigned from employer!", **r.json()) self._report_action('ECONOMY_RESIGN', 'Resigned from employer!', kwargs=r.json())
self._post_economy_resign() self._post_economy_resign()
return True return True
return False return False
@ -2165,7 +2186,7 @@ class CitizenTasks(BaseCitizen):
extra = ret.json() extra = ret.json()
except: # noqa except: # noqa
extra = {} extra = {}
self._report_action("ECONOMY_TG_CONTRACT", f"Bought TG Contract", **extra) self._report_action('ECONOMY_TG_CONTRACT', 'Bought TG Contract', kwargs=extra)
return ret return ret
def find_new_job(self) -> Response: def find_new_job(self) -> Response:
@ -2180,12 +2201,12 @@ class CitizenTasks(BaseCitizen):
if (not limit or salary * 3 < limit) and salary > data["salary"]: if (not limit or salary * 3 < limit) and salary > data["salary"]:
data.update({"citizen": citizen_id, "salary": 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) return self._post_economy_job_market_apply(**data)
def apply_to_employer(self, employer_id: int, salary: float) -> bool: def apply_to_employer(self, employer_id: int, salary: float) -> bool:
data = dict(citizenId=employer_id, salary=salary) 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) r = self._post_economy_job_market_apply(employer_id, salary)
return bool(r.json().get('status')) return bool(r.json().get('status'))
@ -2230,6 +2251,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.telegram.send_message(f"*Started* {utils.now():%F %T}") self.telegram.send_message(f"*Started* {utils.now():%F %T}")
self.__last_full_update = utils.good_timedelta(self.now, - timedelta(minutes=5)) self.__last_full_update = utils.good_timedelta(self.now, - timedelta(minutes=5))
self.update_all(True)
def update_citizen_info(self, html: str = None): def update_citizen_info(self, html: str = None):
""" """
@ -2279,7 +2301,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if (title, reward) not in data: if (title, reward) not in data:
data[(title, reward)] = {'about': about, 'kind': title, 'reward': reward, "count": count, data[(title, reward)] = {'about': about, 'kind': title, 'reward': reward, "count": count,
"currency": currency, "params": params} "currency": currency, "params": medal.get('details', {})}
else: else:
data[(title, reward)]['count'] += count data[(title, reward)]['count'] += count
self._post_main_global_alerts_close(medal.get('id')) self._post_main_global_alerts_close(medal.get('id'))
@ -2438,7 +2460,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
if response is None: if response is None:
return return
if response.get("status"): if response.get("status"):
self._report_action("WORK_AS_MANAGER", f"Worked as manager", **response) self._report_action('WORK_AS_MANAGER', 'Worked as manager', kwargs=response)
if self.config.auto_sell: if self.config.auto_sell:
for kind, data in response.get("result", {}).get("production", {}).items(): for kind, data in response.get("result", {}).get("production", {}).items():
if data and kind in self.config.auto_sell: if data and kind in self.config.auto_sell:
@ -2478,7 +2500,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self._wam(holding_id) self._wam(holding_id)
else: else:
msg = "I was not able to wam and or employ because:\n{}".format(response) msg = "I was not able to wam and or employ because:\n{}".format(response)
self._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) self.write_log(msg)
def work_as_manager(self) -> bool: def work_as_manager(self) -> bool:
@ -2497,7 +2519,7 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
regions = {} regions = {}
for holding_id, holding in self.my_companies.holdings.items(): for holding_id, holding in self.my_companies.holdings.items():
if self.my_companies.get_holding_wam_companies(holding_id): 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 # Check for current region
if self.details.current_region in regions: if self.details.current_region in regions:
@ -2516,8 +2538,27 @@ class Citizen(CitizenAnniversary, CitizenCompanies, CitizenEconomy, CitizenLeade
self.travel_to_residence() self.travel_to_residence()
return bool(wam_count) return bool(wam_count)
else: else:
self.write_log("Did not WAM because I would mess up levelup!") self.write_log('Did not WAM because I would mess up levelup!')
self.my_companies.ff_lockdown = 0 self.my_companies.ff_lockdown = 0
self.update_companies() self.update_companies()
return bool(self.my_companies.get_total_wam_count()) 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 is broken')

View File

@ -1,18 +1,19 @@
import datetime import datetime
import decimal
import hashlib import hashlib
import threading import threading
from collections import defaultdict, deque from decimal import Decimal
from typing import Any, Dict, Iterable, List, NamedTuple, Tuple, Union from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union
from requests import Response, Session, post from requests import Response, Session, post
from erepublik import utils from erepublik import utils
from erepublik.utils import json
try: INDUSTRIES = {1: "Food", 2: "Weapons", 4: "House", 23: "Aircraft",
import simplejson as json 7: "FRM q1", 8: "FRM q2", 9: "FRM q3", 10: "FRM q4", 11: "FRM q5",
except ImportError: 12: "WRM q1", 13: "WRM q2", 14: "WRM q3", 15: "WRM q4", 16: "WRM q5",
import json 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): class ErepublikException(Exception):
@ -26,75 +27,218 @@ class ErepublikNetworkException(ErepublikException):
self.request = request 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: class MyCompanies:
work_units: int = 0 work_units: int = 0
next_ot_time: datetime.datetime next_ot_time: datetime.datetime
holdings: Dict[int, Dict] = None
companies: Dict[int, Dict] = None
ff_lockdown: int = 0 ff_lockdown: int = 0
holdings: Dict[int, Holding]
companies: List[Company]
def __init__(self): def __init__(self):
self.holdings = dict() self.holdings: Dict[int, Holding] = dict()
self.companies = dict() self.companies: List[Company] = list()
self.next_ot_time = utils.now() 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 :param holdings: Parsed JSON to dict from en/economy/myCompanies
""" """
self.holdings.clear() for holding in holdings.values():
template = dict(id=0, num_factories=0, region_id=0, companies=[]) 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(): def prepare_companies(self, companies: Dict[str, Dict[str, Any]]):
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):
""" """
:param companies: Parsed JSON to dict from en/economy/myCompanies :param companies: Parsed JSON to dict from en/economy/myCompanies
""" """
self.companies.clear() self.__clear_data()
template = dict(id=None, quality=0, is_raw=False, resource_bonus=0, effective_bonus=0, raw_usage=0, for company_dict in companies.values():
base_production=0, wam_enabled=False, can_work_as_manager=False, industry_id=0, todays_works=0, holding = self.holdings.get(int(company_dict['holding_company_id']))
preset_own_work=0, already_worked=False, can_assign_employees=False, preset_works=0, quality = company_dict.get('quality')
holding_company_id=None, is_assigned_to_holding=False, cannot_work_as_manager_reason=False) is_raw = company_dict.get('is_raw')
if is_raw:
for c_id, company in companies.items(): raw_usage = Decimal('0.0')
tmp = {} else:
for key in template.keys(): raw_usage = Decimal(str(company_dict.get('upgrades').get(str(quality)).get('raw_usage')))
if key in ['id', 'holding_company_id']: company = Company(
company[key] = int(company[key]) holding, company_dict.get('id'), quality, is_raw,
elif key == "raw_usage": Decimal(str(company_dict.get('effective_bonus'))) / 100,
if not company.get("is_raw") and company.get('upgrades'): raw_usage, Decimal(str(company_dict.get('base_production'))), company_dict.get('wam_enabled'),
company[key] = company.get('upgrades').get(str(company["quality"])).get('raw_usage') company_dict.get('can_work_as_manager'), company_dict.get('cannot_work_as_manager_reason'),
tmp.update({key: company[key]}) company_dict.get('industry_id'), company_dict.get('already_worked'), company_dict.get('preset_works')
self.companies.update({int(c_id): tmp}) )
self.companies.append(company)
def update_holding_companies(self): holding.add_company(company)
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()
def get_employable_factories(self) -> Dict[int, int]: def get_employable_factories(self) -> Dict[int, int]:
ret = {} return {company.id: company.preset_works for company in self.companies if company.preset_works}
for company_id, company in self.companies.items():
if company.get('preset_works'):
ret[company_id] = int(company.get('preset_works', 0))
return ret
def get_total_wam_count(self) -> int: def get_total_wam_count(self) -> int:
ret = 0 return sum([holding.wam_count for holding in self.holdings.values()])
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: def get_holding_wam_count(self, holding_id: int, raw_factory=None) -> int:
""" """
@ -105,14 +249,7 @@ class MyCompanies:
""" """
return len(self.get_holding_wam_companies(holding_id, raw_factory)) return len(self.get_holding_wam_companies(holding_id, raw_factory))
def get_holding_employee_count(self, holding_id): def get_holding_wam_companies(self, holding_id: int, raw_factory: bool = None) -> List[Company]:
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 Returns WAM enabled companies in the holding, True - only raw, False - only factories, None - both
:param holding_id: holding id :param holding_id: holding id
@ -122,19 +259,12 @@ class MyCompanies:
raw = [] raw = []
factory = [] factory = []
if holding_id in self.holdings: if holding_id in self.holdings:
for company_id in sorted(self.holdings.get(holding_id, {}).get('companies', []), for company in self.holdings[holding_id].wam_companies:
key=lambda cid: (-self.companies[cid].get('is_raw'), # True, False if not company.already_worked and not company.cannot_wam_reason == "war":
self.companies[cid].get('industry_id'), # F W H A if company.is_raw:
-self.companies[cid].get('quality'),)): # 7, 6, .. 2, 1 raw.append(company)
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: else:
factory.append(company_id) factory.append(company)
if raw_factory is not None and not raw_factory: if raw_factory is not None and not raw_factory:
return factory return factory
elif raw_factory is not None and raw_factory: elif raw_factory is not None and raw_factory:
@ -144,56 +274,29 @@ class MyCompanies:
else: else:
raise ErepublikException("raw_factory should be True/False/None") raise ErepublikException("raw_factory should be True/False/None")
def get_needed_inventory_usage(self, company_id: int = None, companies: list = None) -> float: @staticmethod
if not any([companies, company_id]): def get_needed_inventory_usage(companies: Union[Company, List[Company]]) -> Decimal:
return 0.
if company_id: if isinstance(companies, list):
if company_id not in self.companies: return sum([company.products_made * 100 if company.is_raw else 1 for company in 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: else:
products_made = company["base_production"] * company["effective_bonus"] / 100 return companies.products_made
# 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")
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}
def __str__(self): def __str__(self):
name = [] return f"MyCompanies: {len(self.companies)} companies in {len(self.holdings)} holdings"
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)
# @property def __repr__(self):
# def __dict__(self): return str(self)
# ret = {}
# for key in dir(self): def __clear_data(self):
# if not key.startswith('_'): for holding in self.holdings.values():
# ret[key] = getattr(self, key) holding.companies.clear()
# return ret 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: class Config:
@ -470,15 +573,30 @@ class Reporter:
def report_promo(self, kind: str, time_until: datetime.datetime): 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)) 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): class MyJSONEncoder(json.JSONEncoder):
def default(self, o): def default(self, o):
from erepublik.citizen import Citizen from erepublik.citizen import Citizen
if isinstance(o, decimal.Decimal): if isinstance(o, Decimal):
return float("{:.02f}".format(o)) return float(f"{o:.02f}")
elif isinstance(o, datetime.datetime): elif isinstance(o, datetime.datetime):
return dict(__type__='datetime', date=o.strftime("%Y-%m-%d"), time=o.strftime("%H:%M:%S"), 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): elif isinstance(o, datetime.date):
return dict(__type__='date', date=o.strftime("%Y-%m-%d")) return dict(__type__='date', date=o.strftime("%Y-%m-%d"))
elif isinstance(o, datetime.timedelta): elif isinstance(o, datetime.timedelta):
@ -488,18 +606,14 @@ class MyJSONEncoder(json.JSONEncoder):
return dict(headers=o.headers.__dict__, url=o.url, text=o.text) return dict(headers=o.headers.__dict__, url=o.url, text=o.text)
elif hasattr(o, '__dict__'): elif hasattr(o, '__dict__'):
return o.__dict__ return o.__dict__
elif isinstance(o, (deque, set)): elif isinstance(o, set):
return list(o) return list(o)
elif isinstance(o, Citizen): elif isinstance(o, Citizen):
return o.to_json() return o.to_json()
try: try:
return super().default(o) return super().default(o)
except TypeError as e: except Exception as e: # noqa
name = None return 'Object is not JSON serializable'
for ___, ____ in globals().copy().items():
if id(o) == id(____):
name = ___
return dict(__error__=str(e), __type__=str(type(o)), __name__=name)
class BattleSide: class BattleSide:
@ -528,21 +642,28 @@ class BattleDivision:
def div_end(self) -> bool: def div_end(self) -> bool:
return utils.now() >= self.end return utils.now() >= self.end
def __init__(self, **kwargs): def __init__(self, div_id: int, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int,
wall_for: int, wall_dom: float, def_medal: Tuple[int, int], inv_medal: Tuple[int, int]):
"""Battle division helper class """Battle division helper class
:param kwargs: must contain keys: :type div_id: int
div_id: int, end: datetime.datetime, epic: bool, inv_pts: int, def_pts: int, :type end: datetime.datetime
wall_for: int, wall_dom: float, def_medal: Tuple[int, int], inv_medal: Tuple[int, int] :type epic: bool
:type inv_pts: int
:type def_pts: int
:type wall_for: int
:type wall_dom: float
:type def_medal: Tuple[int, int]
:type inv_medal: Tuple[int, int]
""" """
self.battle_zone_id = kwargs.get("div_id", 0) self.battle_zone_id = div_id
self.end = kwargs.get("end", 0) self.end = end
self.epic = kwargs.get("epic", 0) self.epic = epic
self.dom_pts = dict({"inv": kwargs.get("inv_pts", 0), "def": kwargs.get("def_pts", 0)}) self.dom_pts = dict({"inv": inv_pts, "def": def_pts})
self.wall = dict({"for": kwargs.get("wall_for", 0), "dom": kwargs.get("wall_dom", 0)}) self.wall = dict({"for": wall_for, "dom": wall_dom})
self.def_medal = {"id": kwargs.get("def_medal", 0)[0], "dmg": kwargs.get("def_medal", 0)[1]} self.def_medal = {"id": def_medal[0], "dmg": def_medal[1]}
self.inv_medal = {"id": kwargs.get("inv_medal", 0)[0], "dmg": kwargs.get("inv_medal", 0)[1]} self.inv_medal = {"id": inv_medal[0], "dmg": inv_medal[1]}
@property @property
def id(self): def id(self):
@ -590,7 +711,7 @@ class Battle:
[row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']] [row.get('id') for row in battle.get('def', {}).get('ally_list') if row['deployed']]
) )
self.div = defaultdict(BattleDivision) self.div = {}
for div, data in battle.get('div', {}).items(): for div, data in battle.get('div', {}).items():
div = int(data.get('div')) div = int(data.get('div'))
if data.get('end'): if data.get('end'):
@ -598,15 +719,15 @@ class Battle:
else: else:
end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1)) end = utils.localize_dt(datetime.datetime.max - datetime.timedelta(days=1))
if not data['stats']['def']: if not data.get('stats',{}).get('def'):
def_medal = (0, 0) def_medal = (0, 0)
else: else:
def_medal = (data['stats']['def']['citizenId'], data['stats']['def']['damage']) def_medal = (data['stats']['def']['citizenId'], data['stats']['def']['damage'])
if not data['stats']['inv']: if not data.get('stats').get('inv'):
inv_medal = (0, 0) inv_medal = (0, 0)
else: else:
inv_medal = (data['stats']['inv']['citizenId'], data['stats']['inv']['damage']) inv_medal = (data['stats']['inv']['citizenId'], data['stats']['inv']['damage'])
battle_div = BattleDivision(end=end, epic=data.get('epic_type') in [1, 5], div_id=data.get('id'), battle_div = BattleDivision(div_id=data.get('id'), end=end, epic=data.get('epic_type') in [1, 5],
inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"), inv_pts=data.get('dom_pts').get("inv"), def_pts=data.get('dom_pts').get("def"),
wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom"), wall_for=data.get('wall').get("for"), wall_dom=data.get('wall').get("dom"),
def_medal=def_medal, inv_medal=inv_medal) def_medal=def_medal, inv_medal=inv_medal)

View File

@ -9,7 +9,7 @@ import traceback
import unicodedata import unicodedata
from decimal import Decimal from decimal import Decimal
from pathlib import Path from pathlib import Path
from typing import Any, List, Mapping, Optional, Union, Dict from typing import Any, Dict, List, Mapping, Optional, Union
import pytz import pytz
import requests import requests
@ -323,8 +323,8 @@ def send_email(name: str, content: List[Any], player=None, local_vars: Mapping[A
if "state_thread" in local_vars: if "state_thread" in local_vars:
local_vars.pop('state_thread', None) local_vars.pop('state_thread', None)
from erepublik.classes import MyJSONEncoder from erepublik.classes import MyJSONEncoder
files.append(('file', ("local_vars.json", json.dumps(local_vars, indent=2, files.append(('file', ("local_vars.json", json.dumps(local_vars, cls=MyJSONEncoder, sort_keys=True),
cls=MyJSONEncoder, sort_keys=True), "application/json"))) "application/json")))
if isinstance(player, Citizen): if isinstance(player, Citizen):
files.append(('file', ("instance.json", player.to_json(indent=True), "application/json"))) files.append(('file', ("instance.json", player.to_json(indent=True), "application/json")))
requests.post('https://pasts.72.lv', data=data, files=files) requests.post('https://pasts.72.lv', data=data, files=files)

View File

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

View File

@ -1,10 +1,9 @@
[bumpversion] [bumpversion]
current_version = 0.20.0 current_version = 0.20.1.7
commit = True commit = True
tag = True tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)? parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\.?(?P<dev>\d+)?
serialize = serialize = {major}.{minor}.{patch}.{dev}
{major}.{minor}.{patch}.{dev}
{major}.{minor}.{patch} {major}.{minor}.{patch}
[bumpversion:file:setup.py] [bumpversion:file:setup.py]
@ -19,9 +18,24 @@ replace = __version__ = '{new_version}'
universal = 1 universal = 1
[flake8] [flake8]
exclude = docs exclude = docs,.tox,.git,log,debug,venv
max-line-length = 120 max-line-length = 120
ignore = E722 F401 ignore = D100,D101,D102,D103
[aliases] [pycodestyle]
max-line-length = 120
exclude = .tox,.git,log,debug,venv, build
[mypy]
python_version = 3.7
check_untyped_defs = True
ignore_missing_imports = False
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
plugins = mypy_django_plugin.main
[isort]
multi_line_output = 2
line_length = 120
not_skip = __init__.py

View File

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

View File

@ -3,10 +3,10 @@
"""Tests for `erepublik` package.""" """Tests for `erepublik` package."""
import unittest
from erepublik import Citizen from erepublik import Citizen
import unittest
class TestErepublik(unittest.TestCase): class TestErepublik(unittest.TestCase):
"""Tests for `erepublik` package.""" """Tests for `erepublik` package."""