1
0
mirror of https://github.com/EDCD/EDMarketConnector.git synced 2025-04-08 05:20:03 +03:00

Add support for saving in Trade Dangerous .prices format.

This commit is contained in:
Jonathan Harris 2015-06-03 14:25:22 +01:00
parent 41958801af
commit 434b82de4b
8 changed files with 123 additions and 68 deletions

View File

@ -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)

View File

@ -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)

42
bpc.py
View File

@ -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()

View File

@ -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'

View File

@ -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':

37
eddn.py
View File

@ -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()

View File

@ -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()

46
td.py Normal file
View File

@ -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# <item name> <sellCR> <buyCR> <demand> <stock> <timestamp>\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()