Skip to content

Commit 61d34eb

Browse files
committed
v0.4 commit
1 parent a617f99 commit 61d34eb

16 files changed

+351
-73
lines changed

README.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,29 @@ a great [EVE Online](https://www.eveonline.com/) third-party tool for gathering
2121
PySpy connects to [CCP's ESI API](https://esi.evetech.net/ui/) and the
2222
[zKillboard API](https://github.com/zKillboard/zKillboard/wiki) and is available on Windows, macOS and Linux.
2323

24+
In addition, PySpy uses a proprietary database which creates summary statistics for approximately 2.4 million EVE Online pilots, based on some 50 million killmails dating back to December 2007. This database is updated daily, shortly after CCP server downtime.
25+
26+
If you enjoy using PySpy and would like to show your appreciation, please feel free to send ISK in-game to White Russsian (with 3 's'). Thank you.
27+
2428
## How to use PySpy
2529

2630
1. Open PySpy.
2731
2. In your EVE client, select a list of characters and copy them to the clipboard (`CTRL+C` on Windows *or* `⌘+C` on macOS).
2832
3. Wait until PySpy is done and inspect the results.
2933
4. Double-click a name to open the respective zKillboard in your browser.
3034

31-
**Note**: PySpy will save its window location, size, column sizes, sorting order and transparency (slider on bottom right) and any other settings automatically and restore them the next time you launch it. If selected in the _View Menu_, PySpy will stay on top of the EVE client so long as the game runs in *window mode*.
35+
**Note**: PySpy will save its window location, size, column sizes, sorting order and transparency (slider on bottom right) and any other settings automatically and restore them the next time you launch it (settings will be reset whenever you update to a new version). If selected in the _View Menu_, PySpy will stay on top of the EVE client so long as the game runs in *window mode*.
3236

3337
## Information Provided by PySpy
3438

3539
### New Dark Mode
3640
<p align="center">
37-
<img alt="PySpy in action" src="https://github.com/WhiteRusssian/PySpy/blob/master/assets/v0.3_dark_screenshot.png?raw=true">
41+
<img alt="PySpy in action" src="https://github.com/WhiteRusssian/PySpy/blob/master/assets/v0.4_dark_screenshot.png?raw=true">
3842
</p>
3943

4044
### Traditional Normal Mode
4145
<p align="center">
42-
<img alt="PySpy in action" src="https://github.com/WhiteRusssian/PySpy/blob/master/assets/v0.3_light_screenshot.png?raw=true">
46+
<img alt="PySpy in action" src="https://github.com/WhiteRusssian/PySpy/blob/master/assets/v0.4_light_screenshot.png?raw=true">
4347
</p>
4448

4549
* **Character**: Character name.
@@ -53,8 +57,16 @@ PySpy connects to [CCP's ESI API](https://esi.evetech.net/ui/) and the
5357
* **Solo**: Ratio of solo kills over total kills.
5458
* **BLOPS**: Number of Black Ops Battleships (BLOPS) killed.
5559
* **HICs**: Number of lost Heavy Interdiction Cruisers (HIC).
60+
* **Last Loss**: Days since last loss.
61+
* **Last Kill**: Days since last kill.
62+
* **Avg. Attackers**: Average number of attackers per kill.
63+
* **Covert Cyno**: Ratio of losses where a covert cyno was fitted to total losses.
64+
* **Regular Cyno**: Ratio of losses where a regular cyno was fitted to total losses.
65+
* **Last Covert Cyno**: Ship type of most recent loss where covert cyno was fitted.
66+
* **Last Regular Cyno**: Ship type of most recent loss where regular cyno was fitted.
67+
* **Abyssal Losses**: Number of ship losses in Abyssal space.
5668

57-
**Current Limitations**: To avoid undue strain on zKillboard's API, PySpy will run the *Kills*, *Losses*, *Last Wk*, *Solo*, *BLOPS* and *HICs* analyses only for the first 30 characters in the list.
69+
**Current Limitations**: To avoid undue strain on zKillboard's API, PySpy will run the *Kills*, *Losses*, *Last Wk*, *Solo*, *BLOPS* and *HICs* analyses only for the first 100 characters in the list.
5870

5971
## Ignore Certain Entities
6072

@@ -86,9 +98,6 @@ Delete the PySpy executable and remove the following files manually:
8698

8799
Below is a non-exhaustive list of additional features I plan to add to PySpy as and when the ESI and zKillboard APIs support them:
88100

89-
* **Cynos**: Indicate if a character has in the past lost ships fitted with regular or covert cynos. I am currently putting together the underlying database of killmails and expect to add this feature in August.
90-
* **Average Attackers**: Average number of attackers across all kills a character has been involved in.
91-
* **Abyssal Losses**: Show number of losses in abyssal sites.
92101
* **Custom Highlighting**: Choose a list of characters, corproations or alliances to highlight.
93102
* **Standings**: Only show characters that are non-blue, i.e. neutral or hostile.
94103
* **Highlight New Pilots**: Highlight any pilots that have entered system since last PySpy run.
@@ -98,7 +107,7 @@ Please feel free to add a [feature request](https://github.com/WhiteRusssian/PyS
98107

99108
## Bug Reporting
100109

101-
Despite PySpy's simplicity and extensive testing, you may encounter the odd bug. If so, please check if an existing [issue](https://github.com/WhiteRusssian/PySpy/issues) already describes your bug. If not, feel free to [create a new issue](https://github.com/WhiteRusssian/PySpy/issues/new?template=pyspy-bug-report.md) for your bug.
110+
Despite extensive testing, you may encounter the odd bug. If so, please check if an existing [issue](https://github.com/WhiteRusssian/PySpy/issues) already describes your bug. If not, feel free to [create a new issue](https://github.com/WhiteRusssian/PySpy/issues/new?template=pyspy-bug-report.md) for your bug.
102111

103112
## Dependencies & Acknowledgements
104113

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v0.3.1
1+
v0.4

__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,13 @@ def analyze_chars(char_names):
8989
background_thread = threading.Thread(
9090
target=watch_clpbd,
9191
daemon=True
92-
)
92+
)
9393
background_thread.start()
9494

9595
update_checker = threading.Thread(
9696
target=chkversion.chk_github_update,
9797
daemon=True
98-
)
98+
)
9999
update_checker.start()
100100

101101
app.MainLoop()

aboutdialog.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ def showAboutBox(parent, event=None):
2525
# __main__.app.PySpy.ToggleWindowStyle(wx.STAY_ON_TOP)
2626

2727
description = """
28-
PySpy is a a simple EVE Online character intel tool
29-
using CCP's ESI API.
28+
PySpy is an EVE Online character intel tool
29+
using CCP's ESI API and a daily updated proprietary
30+
database containing key statistics on approximately
31+
2.4 million pilots.
3032
3133
If you enjoy PySpy and want to show your appreciation
3234
to its author, you are welcome to send an ISK donation
@@ -43,7 +45,7 @@ def showAboutBox(parent, event=None):
4345
info = wx.adv.AboutDialogInfo()
4446

4547
info.SetIcon(wx.Icon(config.ABOUT_ICON, wx.BITMAP_TYPE_PNG))
46-
info.SetName("AboutDialog")
48+
info.SetName("PySpy")
4749
info.SetVersion(config.CURRENT_VER)
4850
info.SetDescription(description)
4951
info.SetCopyright('(C) 2018 White Russsian')

analyze.py

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import statusmsg
2020
# cSpell Checker - Correct Words****************************************
2121
# // cSpell:words affil, zkill, blops, qsize, numid, russsian, ccp's
22+
# // cSpell:words records_added
2223
# **********************************************************************
2324
Logger = logging.getLogger(__name__)
2425
# Example call: Logger.info("Something badhappened", exc_info=True) ****
@@ -28,22 +29,36 @@ def main(char_names):
2829
conn, cur = db.connect_db()
2930
chars_found = get_char_ids(conn, cur, char_names)
3031
if chars_found > 0:
32+
# Run Pyspy remote database query in seprate thread
33+
tp = threading.Thread(
34+
target=get_character_intel(conn, cur),
35+
daemon=True
36+
)
37+
tp.start()
38+
39+
# Run zKill query in seprate thread
3140
char_ids = cur.execute(
3241
"SELECT char_id FROM characters ORDER BY char_name"
3342
).fetchall()
3443
q_main = queue.Queue()
35-
t = zKillStats(char_ids, q_main)
36-
t.start()
44+
tz = zKillStats(char_ids, q_main)
45+
tz.start()
46+
3747
get_char_affiliations(conn, cur)
3848
get_affil_names(conn, cur)
39-
t.join()
49+
50+
# Join zKill thread
51+
tz.join()
4052
zkill_stats = q_main.get()
4153
query_string = (
4254
'''UPDATE characters SET kills=?, blops_kills=?, hic_losses=?,
4355
week_kills=?, losses=?, solo_ratio=?, sec_status=?
4456
WHERE char_id=?'''
4557
)
4658
db.write_many_to_db(conn, cur, query_string, zkill_stats)
59+
60+
# Join Pyspy remote database thread
61+
tp.join()
4762
output = output_list(cur)
4863
conn.close()
4964
return output
@@ -91,7 +106,7 @@ def get_char_affiliations(conn, cur):
91106
'''UPDATE characters SET corp_id=?, alliance_id=?, faction_id=?
92107
WHERE char_id=?'''
93108
)
94-
records_added = db.write_many_to_db(conn, cur, query_string, records)
109+
db.write_many_to_db(conn, cur, query_string, records)
95110

96111

97112
def get_affil_names(conn, cur):
@@ -172,18 +187,59 @@ def run(self):
172187
return
173188

174189

190+
def get_character_intel(conn, cur):
191+
'''
192+
Adds certain character killboard statistics derived from PySpy's
193+
proprietary database to the local SQLite3 database.
194+
195+
:param `conn`: SQLite3 connection object.
196+
:param `cur`: SQLite3 cursor object.
197+
'''
198+
char_ids = cur.execute("SELECT char_id FROM characters").fetchall()
199+
char_intel = apis.post_proprietary_db(char_ids)
200+
records = ()
201+
for r in char_intel:
202+
char_id = r["character_id"]
203+
last_loss_date = r["last_loss_date"] if r["last_loss_date"] is not None else 0
204+
last_kill_date = r["last_kill_date"] if r["last_kill_date"] is not None else 0
205+
avg_attackers = r["avg_attackers"] if r["avg_attackers"] is not None else 0
206+
covert_prob = r["covert_prob"] if r["covert_prob"] is not None else 0
207+
normal_prob = r["normal_prob"] if r["normal_prob"] is not None else 0
208+
last_cov_ship = r["last_cov_ship"] if r["last_cov_ship"] is not None else 0
209+
last_norm_ship = r["last_norm_ship"] if r["last_norm_ship"] is not None else 0
210+
abyssal_losses = r["abyssal_losses"] if r["abyssal_losses"] is not None else 0
211+
212+
records = records + ((
213+
last_loss_date, last_kill_date, avg_attackers, covert_prob,
214+
normal_prob, last_cov_ship, last_norm_ship, abyssal_losses, char_id
215+
), )
216+
217+
query_string = (
218+
'''UPDATE characters SET last_loss_date=?, last_kill_date=?,
219+
avg_attackers=?, covert_prob=?, normal_prob=?,
220+
last_cov_ship=?, last_norm_ship=?, abyssal_losses=?
221+
WHERE char_id=?'''
222+
)
223+
db.write_many_to_db(conn, cur, query_string, records)
224+
225+
175226
def output_list(cur):
176227
query_string = (
177228
'''SELECT
178229
ch.char_id, ch.faction_id, ch.char_name, co.id, co.name, al.id,
179230
al.name, fa.name, ac.numid, ch.week_kills, ch.kills, ch.blops_kills,
180-
ch.hic_losses, ch.losses, ch.solo_ratio, ch.sec_status
231+
ch.hic_losses, ch.losses, ch.solo_ratio, ch.sec_status,
232+
ch.last_loss_date, ch.last_kill_date,
233+
ch.avg_attackers, ch.covert_prob, ch.normal_prob,
234+
IFNULL(cs.name,'-'), IFNULL(ns.name,'-'), ch.abyssal_losses
181235
FROM characters AS ch
182236
LEFT JOIN alliances AS al ON ch.alliance_id = al.id
183237
LEFT JOIN corporations AS co ON ch.corp_id = co.id
184238
LEFT JOIN factions AS fa ON ch.faction_id = fa.id
185-
LEFT JOIN (SELECT alliance_id, COUNT(alliance_id) AS numid FROM characters GROUP BY alliance_id) AS ac ON
186-
ch.alliance_id = ac.alliance_id
239+
LEFT JOIN (SELECT alliance_id, COUNT(alliance_id) AS numid FROM characters GROUP BY alliance_id)
240+
AS ac ON ch.alliance_id = ac.alliance_id
241+
LEFT JOIN ships AS cs ON ch.last_cov_ship = cs.id
242+
LEFT JOIN ships AS ns ON ch.last_norm_ship = ns.id
187243
ORDER BY ch.char_name'''
188244
)
189245
return cur.execute(query_string).fetchall()

apis.py

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# MIT licensed
33
# Copyright (c) 2018 White Russsian
44
# Github: <https://github.com/WhiteRusssian/PySpy>**********************
5-
''' This module provides connectivity to CCP's ESI API and to zKillboard's
6-
API.
5+
''' This module provides connectivity to CCP's ESI API, to zKillboard's
6+
API and to PySpy's own proprietary RESTful API.
77
'''
88
# **********************************************************************
99
import json
@@ -23,6 +23,9 @@
2323
# Example call: Logger.info("Something badhappened", exc_info=True) ****
2424

2525

26+
# ESI Status
27+
# https://esi.evetech.net/ui/?version=meta#/Meta/get_status
28+
2629
def post_req_ccp(esi_path, json_data):
2730
url = "https://esi.evetech.net/latest/" + esi_path + \
2831
"?datasource=tranquility"
@@ -143,3 +146,98 @@ def run(self):
143146
sec_status, self._char_id]
144147
)
145148
return
149+
150+
151+
def post_proprietary_db(character_ids):
152+
'''
153+
Query PySpy's proprietary kill database for the character ids
154+
provided as a list or tuple of integers. Returns a JSON containing
155+
one line per character id.
156+
157+
:param `character_ids`: List or tuple of character ids as integers.
158+
:return: JSON dictionary containing certain statistics for each id.
159+
'''
160+
url = "http://pyspy.pythonanywhere.com" + "/character_intel/" + "v1/"
161+
headers = {
162+
"Accept-Encoding": "gzip",
163+
"User-Agent": "PySpy, Author: White Russsian, https://github.com/WhiteRusssian/PySpy"
164+
}
165+
# Character_ids is a list of tuples, which needs to be converted to dict
166+
# with list as value.
167+
character_ids = {"character_ids": character_ids}
168+
try:
169+
r = requests.post(url, headers=headers, json=character_ids)
170+
except requests.exceptions.ConnectionError:
171+
Logger.info("No network connection.", exc_info=True)
172+
statusmsg.push_status(
173+
"NETWORK ERROR: Check your internet connection and firewall settings."
174+
)
175+
time.sleep(5)
176+
return "network_error"
177+
if r.status_code != 200:
178+
server_msg = json.loads(r.text)["error"]
179+
Logger.info(
180+
"PySpy server returned error code: " +
181+
str(r.status_code) + ", saying: " + server_msg, exc_info=True
182+
)
183+
statusmsg.push_status(
184+
"PYSPY SERVER ERROR: " + str(r.status_code) + " (" + server_msg + ")"
185+
)
186+
return "server_error"
187+
return r.json()
188+
189+
190+
def get_ship_data():
191+
'''
192+
Produces a list of ship id and ship name pairs for each ship in EVE
193+
Online, using ESI's universe/names endpoint.
194+
195+
:return: List of lists containing ship ids and related ship names.
196+
'''
197+
all_ship_ids = get_all_ship_ids()
198+
if not isinstance(all_ship_ids, (list, tuple)) or len(all_ship_ids) < 1:
199+
Logger.error("[get_ship_data] No valid ship ids provided.", exc_info=True)
200+
return
201+
202+
url = "https://esi.evetech.net/v2/universe/names/?datasource=tranquility"
203+
json_data = json.dumps(all_ship_ids)
204+
try:
205+
r = requests.post(url, json_data)
206+
except requests.exceptions.ConnectionError:
207+
Logger.error("[get_ship_data] No network connection.", exc_info=True)
208+
return "network_error"
209+
if r.status_code != 200:
210+
server_msg = json.loads(r.text)["error"]
211+
Logger.error(
212+
"[get_ship_data] CCP Servers returned error code: " +
213+
str(r.status_code) + ", saying: " + server_msg, exc_info=True
214+
)
215+
return "server_error"
216+
ship_data = list(map(lambda r: [r['id'], r['name']], r.json()))
217+
return ship_data
218+
219+
220+
def get_all_ship_ids():
221+
'''
222+
Uses ESI's insurance/prices endpoint to get all available ship ids.
223+
224+
:return: List of ship ids as integers.
225+
'''
226+
url = "https://esi.evetech.net/v1/insurance/prices/?datasource=tranquility"
227+
228+
try:
229+
r = requests.get(url)
230+
except requests.exceptions.ConnectionError:
231+
Logger.error("[get_ship_ids] No network connection.", exc_info=True)
232+
return "network_error"
233+
if r.status_code != 200:
234+
server_msg = json.loads(r.text)["error"]
235+
Logger.error(
236+
"[get_ship_ids] CCP Servers at returned error code: " +
237+
str(r.status_code) + ", saying: " + server_msg, exc_info=True
238+
)
239+
return "server_error"
240+
241+
ship_ids = list(map(lambda r: str(r['type_id']), r.json()))
242+
Logger.info("[get_ship_ids] Number of ship ids found: " + str(len(ship_ids)))
243+
return ship_ids

assets/cov_cyno_64.png

7.63 KB
Loading

assets/hic_64.png

5.82 KB
Loading

assets/norm_cyno_64.png

6.47 KB
Loading

assets/v0.4_dark_screenshot.png

178 KB
Loading

0 commit comments

Comments
 (0)