From 434b82de4b060ef5e72029f6154c04c0242e72d1 Mon Sep 17 00:00:00 2001 From: Jonathan Harris Date: Wed, 3 Jun 2015 14:25:22 +0100 Subject: [PATCH] Add support for saving in Trade Dangerous .prices format. --- EDMarketConnector.py | 13 +++++++++++-- README.md | 4 ++-- bpc.py | 42 +++++++++++----------------------------- companion.py | 21 ++++++++++++++++++++ config.py | 3 ++- eddn.py | 37 ++++++++++++++++------------------- prefs.py | 25 ++++++++++++------------ td.py | 46 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 123 insertions(+), 68 deletions(-) create mode 100644 td.py diff --git a/EDMarketConnector.py b/EDMarketConnector.py index 632f28eb..53ad63ff 100755 --- a/EDMarketConnector.py +++ b/EDMarketConnector.py @@ -15,6 +15,7 @@ if __debug__: import companion import bpc +import td import eddn import prefs from config import appname, applongname, config @@ -136,11 +137,19 @@ class AppWindow: config.write('querytime', querytime) self.holdofftime = querytime + companion.holdoff - if not data.get('commander') or not data.get('commander').get('docked'): + # Validation + if not data.get('commander') or not data['commander'].get('name','').strip(): + raise Exception("Who are you?!") # Shouldn't happen + elif not data['commander'].get('docked'): raise Exception("You're not docked at a station!") - elif not data.get('lastStarport') or not data.get('lastStarport').get('commodities'): + elif not data.get('lastSystem') or not data['lastSystem'].get('name','').strip(): + raise Exception("Where are you?!") # Shouldn't happen + elif not data.get('lastStarport') or not data['lastStarport'].get('commodities'): raise Exception("Station doesn't have a market!") + if config.read('output') & config.OUT_TD: + td.export(data) + if config.read('output') & config.OUT_BPC: bpc.export(data) diff --git a/README.md b/README.md index eefacd3e..4c78daaa 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Elite: Dangerous Market Connector This app downloads commodity market data from the game [Elite: Dangerous](https://www.elitedangerous.com/) and, at your choice, either: * transmits the data to the Elite Dangerous Data Network ("EDDN") from where you and others can use it via online trading tools such as [eddb](http://eddb.io/). -* saves the data to files on your disk which you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081). +* saves the data to files on your disk which you can load into trading tools such as [Slopey's BPC Market Tool](https://forums.frontier.co.uk/showthread.php?t=76081) and [Trade Dangerous](https://bitbucket.org/kfsone/tradedangerous/wiki/Home). -The user-interface is deliberately minimal - just press the "Update" button to download and automatically transmit/save the commodity market data for the station where you're currently docked in-game: +The user-interface is deliberately minimal - just press the "Update" button when you land at a station to automatically download and transmit or save the station's commodity market data: ![Windows screenshot](img/win.png) ![Mac screenshot](img/mac.png) diff --git a/bpc.py b/bpc.py index fcdcdc1a..d2ccae99 100644 --- a/bpc.py +++ b/bpc.py @@ -3,54 +3,34 @@ from os.path import join import codecs -import datetime -import hashlib import time from config import config - -commoditymap = { 'Agricultural Medicines': 'Agri-Medicines', - 'Atmospheric Extractors': 'Atmospheric Processors', - 'Auto Fabricators': 'Auto-Fabricators', - 'Basic Narcotics': 'Narcotics', - 'Bio Reducing Lichen': 'Bioreducing Lichen', - 'Hazardous Environment Suits': 'H.E. Suits', - 'Heliostatic Furnaces': 'Microbial Furnaces', - 'Marine Supplies': 'Marine Equipment', - 'Non Lethal Weapons': 'Non-Lethal Weapons', - 'Terrain Enrichment Systems': 'Land Enrichment Systems' } - -bracketmap = { 0: '', - 1: 'Low', - 2: 'Med', - 3: 'High' } - +from companion import commoditymap, bracketmap def export(data): querytime = config.read('querytime') or int(time.time()) - filename = join(config.read('outdir'), '%s.%s.%s.bpc' % (data.get('lastSystem').get('name').strip(), data.get('lastStarport').get('name').strip(), time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)))) + filename = join(config.read('outdir'), '%s.%s.%s.bpc' % (data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip(), time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)))) - timestamp = datetime.datetime.utcfromtimestamp(querytime).isoformat() - rowheader = '%s;%s;%s' % (data.get('commander').get('name').replace(';',':'), data.get('lastSystem').get('name').strip(), data.get('lastStarport').get('name').strip()) + timestamp = time.strftime('%Y-%m-%dT%H.%M.%S', time.gmtime(querytime)) + rowheader = '%s;%s;%s' % (data['commander']['name'].replace(';',':').strip(), data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip()) h = codecs.open(filename, 'w', 'utf-8') h.write('userID;System;Station;Commodity;Sell;Buy;Demand;;Supply;;Date;\r\n') - for commodity in data.get('lastStarport').get('commodities'): - if commodity.get('categoryname') and commodity.get('categoryname') != 'NonMarketable': + for commodity in data['lastStarport']['commodities']: + if commodity.get('categoryname') and commodity['categoryname'] != 'NonMarketable': h.write('%s;%s;%s;%s;%s;%s;%s;%s;%s;\r\n' % ( rowheader, - commoditymap.get(commodity.get('name').strip(), commodity.get('name').strip()), - commodity.get('sellPrice') and int(commodity.get('sellPrice')) or '', - commodity.get('buyPrice') and int(commodity.get('buyPrice')) or '', - commodity.get('demandBracket') and int(commodity.get('demand')) or '', + commoditymap.get(commodity['name'].strip(), commodity['name'].strip()), + commodity.get('sellPrice') and int(commodity['sellPrice']) or '', + commodity.get('buyPrice') and int(commodity['buyPrice']) or '', + int(commodity['demand']) if commodity.get('demandBracket') else '', bracketmap.get(commodity.get('demandBracket'), ''), - commodity.get('stockBracket') and int(commodity.get('stock')) or '', + int(commodity['stock']) if commodity.get('stockBracket') else '', bracketmap.get(commodity.get('stockBracket'), ''), timestamp)) - elif __debug__: - print 'Skipping %s : %s' % (commodity.get('name'), commodity.get('categoryname')) h.close() diff --git a/companion.py b/companion.py index 87ecd3d9..bfb328a9 100644 --- a/companion.py +++ b/companion.py @@ -19,6 +19,27 @@ from config import config holdoff = 120 # be nice +# Map values reported by the Companion interface to names displayed in-game and recognized by trade tools + +categorymap = { 'Narcotics': 'Legal Drugs', + 'Slaves': 'Slavery', } + +commoditymap= { 'Agricultural Medicines': 'Agri-Medicines', + 'Atmospheric Extractors': 'Atmospheric Processors', + 'Auto Fabricators': 'Auto-Fabricators', + 'Basic Narcotics': 'Narcotics', + 'Bio Reducing Lichen': 'Bioreducing Lichen', + 'Hazardous Environment Suits': 'H.E. Suits', + 'Heliostatic Furnaces': 'Microbial Furnaces', + 'Marine Supplies': 'Marine Equipment', + 'Non Lethal Weapons': 'Non-Lethal Weapons', + 'Terrain Enrichment Systems': 'Land Enrichment Systems', } + +bracketmap = { 1: 'Low', + 2: 'Med', + 3: 'High', } + + class CredentialsError(Exception): def __str__(self): return 'Error: Invalid Credentials' diff --git a/config.py b/config.py index a740cb06..5cc5ac59 100644 --- a/config.py +++ b/config.py @@ -16,7 +16,8 @@ appversion = '1.0.0.0' class Config: OUT_EDDN = 1 - OUT_BPC = 2 + OUT_BPC = 2 + OUT_TD = 4 if platform=='darwin': diff --git a/eddn.py b/eddn.py index cc512cab..79d23ea9 100644 --- a/eddn.py +++ b/eddn.py @@ -1,8 +1,6 @@ # Export to EDDN # -*- coding: utf-8 -*- -import datetime -import hashlib import json import requests from platform import system @@ -10,7 +8,7 @@ from sys import platform import time from config import applongname, appversion, config -from bpc import commoditymap, bracketmap +from companion import commoditymap, bracketmap upload = 'http://eddn-gateway.elite-markets.net:8080/upload/' schema = 'http://schemas.elite-markets.net/eddn/commodity/1' @@ -19,44 +17,43 @@ def export(data, callback): callback('Sending data to EDDN...') + querytime = config.read('querytime') or int(time.time()) + header = { 'softwareName': '%s [%s]' % (applongname, platform=='darwin' and "Mac OS" or system()), 'softwareVersion': appversion, - 'uploaderID': data.get('commander').get('name') } # was hashlib.md5(config.read('username')).hexdigest() } - systemName = data.get('lastSystem').get('name').strip() - stationName = data.get('lastStarport').get('name').strip() - timestamp = datetime.datetime.utcfromtimestamp(config.read('querytime') or int(time.time())).isoformat() + 'uploaderID': data['commander']['name'].strip() } + systemName = data['lastSystem']['name'].strip() + stationName = data['lastStarport']['name'].strip() + timestamp = time.strftime('%Y-%m-%dT%H.%M.%S', time.gmtime(querytime)) # route all requests through a session in the hope of using keep-alive session = requests.Session() session.headers['connection'] = 'keep-alive' # can help through a proxy? - commodities = data.get('lastStarport').get('commodities') + commodities = data['lastStarport']['commodities'] i=0 for commodity in commodities: i = i+1 callback('Sending %d/%d' % (i, len(commodities))) - if commodity.get('categoryname') and commodity.get('categoryname') != 'NonMarketable': + if commodity.get('categoryname') and commodity['categoryname'] != 'NonMarketable': msg = { '$schemaRef': schema, 'header': header, 'message': { 'systemName': systemName, 'stationName': stationName, - 'itemName': commoditymap.get(commodity.get('name').strip(), commodity.get('name').strip()), - 'buyPrice': int(commodity.get('buyPrice')), - 'stationStock': int(commodity.get('stock')), - 'sellPrice': int(commodity.get('sellPrice')), - 'demand': int(commodity.get('demand')), + 'itemName': commoditymap.get(commodity['name'].strip(), commodity['name'].strip()), + 'buyPrice': int(commodity.get('buyPrice', 0)), + 'stationStock': int(commodity.get('stock', 0)), + 'sellPrice': int(commodity.get('sellPrice', 0)), + 'demand': int(commodity.get('demand', 0)), 'timestamp': timestamp, } } if commodity.get('stockBracket'): - msg['message']['supplyLevel'] = bracketmap.get(commodity.get('stockBracket')) + msg['message']['supplyLevel'] = bracketmap.get(commodity['stockBracket']) if commodity.get('demandBracket'): - msg['message']['demandLevel'] = bracketmap.get(commodity.get('demandBracket')) + msg['message']['demandLevel'] = bracketmap.get(commodity['demandBracket']) - r = requests.post(upload, data=json.dumps(msg), verify=True) - - elif __debug__: - print 'Skipping %s : %s' % (commodity.get('name'), commodity.get('categoryname')) + r = requests.post(upload, data=json.dumps(msg)) session.close() diff --git a/prefs.py b/prefs.py index 56221b9f..832c706a 100644 --- a/prefs.py +++ b/prefs.py @@ -41,7 +41,7 @@ class PreferencesDialog(tk.Toplevel): credframe.grid(padx=10, pady=10, sticky=tk.NSEW) credframe.columnconfigure(1, weight=1) - ttk.Label(credframe, text="Please log in with your Elite:Dangerous account details.").grid(row=0, columnspan=2, sticky=tk.W) + ttk.Label(credframe, text="Please log in with your Elite:Dangerous account details").grid(row=0, columnspan=2, sticky=tk.W) ttk.Label(credframe, text="Username (Email)").grid(row=1, sticky=tk.W) ttk.Label(credframe, text="Password").grid(row=2, sticky=tk.W) @@ -53,26 +53,27 @@ class PreferencesDialog(tk.Toplevel): self.password.insert(0, config.read('password') or '') self.password.grid(row=2, column=1, sticky=tk.NSEW) + for child in credframe.winfo_children(): + child.grid_configure(padx=5, pady=3) + outframe = ttk.LabelFrame(frame, text='Output') outframe.grid(padx=10, pady=10, sticky=tk.NSEW) - outframe.columnconfigure(1, weight=1) + outframe.columnconfigure(0, weight=1) self.outvar = tk.IntVar() self.outvar.set(config.read('output') or config.OUT_EDDN) - ttk.Label(outframe, text="Please choose where you want the market data saved.").grid(row=0, columnspan=3, sticky=tk.W) - ttk.Radiobutton(outframe, text="Online to the Elite Dangerous Data Network (EDDN)", variable=self.outvar, value=config.OUT_EDDN, command=self.outvarchanged).grid(row=1, columnspan=3, sticky=tk.W) - ttk.Radiobutton(outframe, text="Offline to Slopey's BPC files in folder:", variable=self.outvar, value=config.OUT_BPC, command=self.outvarchanged).grid(row=2, columnspan=3, sticky=tk.W) - ttk.Label(outframe, width=-1).grid(row=3, column=0) + ttk.Label(outframe, text="Please choose where you want the market data saved").grid(row=0, columnspan=2, padx=5, pady=3, sticky=tk.W) + ttk.Radiobutton(outframe, text="Online to the Elite Dangerous Data Network (EDDN)", variable=self.outvar, value=config.OUT_EDDN, command=self.outvarchanged).grid(row=1, columnspan=2, padx=5, sticky=tk.W) + ttk.Radiobutton(outframe, text="Offline in Slopey's BPC format", variable=self.outvar, value=config.OUT_BPC, command=self.outvarchanged).grid(row=2, columnspan=2, padx=5, sticky=tk.W) + ttk.Radiobutton(outframe, text="Offline in Trade Dangerous format", variable=self.outvar, value=config.OUT_TD, command=self.outvarchanged).grid(row=3, columnspan=2, padx=5, sticky=tk.W) + ttk.Label(outframe, text=(platform=='darwin' and 'Where:' or 'File location:')).grid(row=4, padx=5, pady=(5,0), sticky=tk.NSEW) self.outbutton = ttk.Button(outframe, text=(platform=='darwin' and 'Browse...' or 'Choose...'), command=self.outbrowse) - self.outbutton.grid(row=2, column=2, sticky=tk.E) + self.outbutton.grid(row=4, column=1, padx=5, pady=(5,0), sticky=tk.NSEW) self.outdir = ttk.Entry(outframe) self.outdir.insert(0, config.read('outdir')) - self.outdir.grid(row=3, column=1, columnspan=2, sticky=tk.NSEW) + self.outdir.grid(row=5, columnspan=2, padx=5, pady=5, sticky=tk.EW) self.outvarchanged() - for child in credframe.winfo_children() + outframe.winfo_children(): - child.grid_configure(padx=5, pady=3) - if platform=='darwin': self.protocol("WM_DELETE_WINDOW", self.apply) # close button applies changes else: @@ -149,7 +150,7 @@ class AuthenticationDialog(tk.Toplevel): self.button.grid(row=1, column=3, sticky=tk.E) for child in frame.winfo_children(): - child.grid_configure(padx=5, pady=3) + child.grid_configure(padx=5, pady=5) # wait for window to appear on screen before calling grab_set self.wait_visibility() diff --git a/td.py b/td.py new file mode 100644 index 00000000..bc97828d --- /dev/null +++ b/td.py @@ -0,0 +1,46 @@ +# Export to Trade Dangerous + +from os.path import join +from collections import defaultdict +import codecs +from platform import system +from sys import platform +import time + +from config import applongname, appversion, config +from companion import categorymap, commoditymap, bracketmap + + +def export(data): + + querytime = config.read('querytime') or int(time.time()) + + filename = join(config.read('outdir'), '%s.%s.%s.prices' % (data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip(), time.strftime('%Y-%m-%dT%H.%M.%S', time.localtime(querytime)))) + + timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(querytime)) + + # Format described here: https://bitbucket.org/kfsone/tradedangerous/wiki/Price%20Data + h = open(filename, 'wt') # codecs can't automatically handle line endings, so encode manually where required + h.write(('#! trade.py import -\n# Created by %s %s on %s for Cmdr %s.\n#\n# \n\n@ %s/%s\n' % (applongname, appversion, platform=='darwin' and "Mac OS" or system(), data['commander']['name'].strip(), data['lastSystem']['name'].strip(), data['lastStarport']['name'].strip())).encode('utf-8')) + + # sort commodities by category + bycategory = defaultdict(list) + for commodity in data['lastStarport']['commodities']: + if commodity.get('categoryname') and commodity.get('categoryname') != 'NonMarketable': + bycategory[categorymap.get(commodity['categoryname'], commodity['categoryname'])].append(commodity) + + for category in sorted(bycategory): + h.write(' + %s\n' % category) + # corrections to commodity names can change the sort order + for commodity in sorted(bycategory[category], key=lambda x:commoditymap.get(x['name'].strip(),x['name'])): + h.write(' %-23s %7d %7d %9s%c %8s%c %s\n' % ( + commoditymap.get(commodity['name'].strip(), commodity['name'].strip()), + commodity.get('sellPrice', 0), + commodity.get('buyPrice', 0), + int(commodity.get('demand')) if commodity.get('demandBracket') else '', + bracketmap.get(commodity.get('demandBracket'), '?')[0], + int(commodity.get('stock')) if commodity.get('stockBracket') else '', + bracketmap.get(commodity.get('stockBracket'), '-')[0], + timestamp)) + + h.close()