tradedangerous 11.5.2__py3-none-any.whl → 12.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tradedangerous might be problematic. Click here for more details.
- tradedangerous/cache.py +567 -395
- tradedangerous/cli.py +2 -2
- tradedangerous/commands/TEMPLATE.py +25 -26
- tradedangerous/commands/__init__.py +8 -16
- tradedangerous/commands/buildcache_cmd.py +40 -10
- tradedangerous/commands/buy_cmd.py +57 -46
- tradedangerous/commands/commandenv.py +0 -2
- tradedangerous/commands/export_cmd.py +78 -50
- tradedangerous/commands/import_cmd.py +70 -34
- tradedangerous/commands/market_cmd.py +52 -19
- tradedangerous/commands/olddata_cmd.py +120 -107
- tradedangerous/commands/rares_cmd.py +122 -110
- tradedangerous/commands/run_cmd.py +118 -66
- tradedangerous/commands/sell_cmd.py +52 -45
- tradedangerous/commands/shipvendor_cmd.py +49 -234
- tradedangerous/commands/station_cmd.py +55 -485
- tradedangerous/commands/update_cmd.py +56 -420
- tradedangerous/csvexport.py +173 -162
- tradedangerous/gui.py +2 -2
- tradedangerous/plugins/eddblink_plug.py +389 -252
- tradedangerous/plugins/spansh_plug.py +2488 -821
- tradedangerous/prices.py +124 -142
- tradedangerous/templates/TradeDangerous.sql +6 -6
- tradedangerous/tradecalc.py +1227 -1109
- tradedangerous/tradedb.py +533 -384
- tradedangerous/tradeenv.py +12 -1
- tradedangerous/version.py +1 -1
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/METADATA +17 -4
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/RECORD +33 -39
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/WHEEL +1 -1
- tradedangerous/commands/update_gui.py +0 -721
- tradedangerous/jsonprices.py +0 -254
- tradedangerous/plugins/edapi_plug.py +0 -1071
- tradedangerous/plugins/journal_plug.py +0 -537
- tradedangerous/plugins/netlog_plug.py +0 -316
- tradedangerous/templates/database_changes.json +0 -6
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/entry_points.txt +0 -0
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info/licenses}/LICENSE +0 -0
- {tradedangerous-11.5.2.dist-info → tradedangerous-12.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1071 +0,0 @@
|
|
|
1
|
-
# ----------------------------------------------------------------
|
|
2
|
-
# Import plugin that downloads market and ship vendor data from the
|
|
3
|
-
# Elite Dangerous mobile API.
|
|
4
|
-
# ----------------------------------------------------------------
|
|
5
|
-
|
|
6
|
-
import hashlib
|
|
7
|
-
import json
|
|
8
|
-
import pathlib
|
|
9
|
-
import random
|
|
10
|
-
import requests
|
|
11
|
-
import time
|
|
12
|
-
import base64
|
|
13
|
-
import webbrowser
|
|
14
|
-
import configparser
|
|
15
|
-
|
|
16
|
-
from datetime import datetime, timezone
|
|
17
|
-
from collections import namedtuple
|
|
18
|
-
from http import HTTPStatus
|
|
19
|
-
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
20
|
-
from urllib.parse import urlsplit, parse_qs
|
|
21
|
-
|
|
22
|
-
from .. import cache, csvexport, plugins, mapping, fs
|
|
23
|
-
|
|
24
|
-
import secrets
|
|
25
|
-
|
|
26
|
-
__version_info__ = (5, 0, 2)
|
|
27
|
-
__version__ = '.'.join(map(str, __version_info__))
|
|
28
|
-
|
|
29
|
-
# ----------------------------------------------------------------
|
|
30
|
-
# Deal with some differences in names between TD, ED and the API.
|
|
31
|
-
# ----------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
bracket_levels = ('?', 'L', 'M', 'H')
|
|
34
|
-
|
|
35
|
-
# Categories to ignore. Drones end up here. No idea what they are.
|
|
36
|
-
cat_ignore = [
|
|
37
|
-
'NonMarketable',
|
|
38
|
-
]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class OAuthCallbackHandler(BaseHTTPRequestHandler):
|
|
42
|
-
|
|
43
|
-
def do_GET(self):
|
|
44
|
-
split_url = urlsplit(self.path)
|
|
45
|
-
if split_url.path == "/callback":
|
|
46
|
-
parsed_url = parse_qs(split_url.query)
|
|
47
|
-
self.server.callback_code = parsed_url.get("code", [None])[0]
|
|
48
|
-
self.server.callback_state = parsed_url.get("state", [None])[0]
|
|
49
|
-
self.send_response(HTTPStatus.OK)
|
|
50
|
-
body_text = b"<p>You can close me now.</p>"
|
|
51
|
-
else:
|
|
52
|
-
self.send_response(HTTPStatus.NOT_IMPLEMENTED)
|
|
53
|
-
body_text = b"<p>Something went wrong.</p>"
|
|
54
|
-
self.end_headers()
|
|
55
|
-
self.wfile.write(b"<html><head><title>EDAPI Frontier Login</title></head>")
|
|
56
|
-
self.wfile.write(b"<body><h1>AUTHENTICATION</h1>")
|
|
57
|
-
self.wfile.write(body_text)
|
|
58
|
-
self.wfile.write(b"</body></html>")
|
|
59
|
-
|
|
60
|
-
def log_message(self, format, *args):
|
|
61
|
-
pass
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class OAuthCallbackServer:
|
|
65
|
-
def __init__(self, hostname, port, handler):
|
|
66
|
-
myServer = HTTPServer
|
|
67
|
-
myServer.callback_code = None
|
|
68
|
-
myServer.callback_state = None
|
|
69
|
-
self.httpd = myServer((hostname, port), handler)
|
|
70
|
-
self.httpd.handle_request()
|
|
71
|
-
self.httpd.server_close()
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class EDAPI:
|
|
75
|
-
'''
|
|
76
|
-
A class that handles the Frontier ED API.
|
|
77
|
-
'''
|
|
78
|
-
|
|
79
|
-
_agent = "EDCD-TradeDangerousPluginEDAPI-%s" % __version__
|
|
80
|
-
_basename = 'edapi'
|
|
81
|
-
_configfile = _basename + '.config'
|
|
82
|
-
|
|
83
|
-
def __init__(
|
|
84
|
-
self,
|
|
85
|
-
basename = 'edapi',
|
|
86
|
-
debug = False,
|
|
87
|
-
configfile = None,
|
|
88
|
-
json_file = None,
|
|
89
|
-
login = False
|
|
90
|
-
):
|
|
91
|
-
'''
|
|
92
|
-
Initialize
|
|
93
|
-
'''
|
|
94
|
-
|
|
95
|
-
# Build common file names from basename.
|
|
96
|
-
self._basename = basename
|
|
97
|
-
if configfile:
|
|
98
|
-
self._configfile = configfile
|
|
99
|
-
|
|
100
|
-
self.debug = debug
|
|
101
|
-
self.login = login
|
|
102
|
-
|
|
103
|
-
# If json_file was given, just load that instead.
|
|
104
|
-
if json_file:
|
|
105
|
-
with open(json_file) as file:
|
|
106
|
-
self.profile = json.load(file)
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
# Setup the session.
|
|
110
|
-
self.opener = requests.Session()
|
|
111
|
-
|
|
112
|
-
# Setup config
|
|
113
|
-
self.config = configparser.ConfigParser()
|
|
114
|
-
self.config.read_dict({
|
|
115
|
-
"frontier": {
|
|
116
|
-
"AUTH_URL": "https://auth.frontierstore.net",
|
|
117
|
-
"AUTH_URL_AUTH": "https://auth.frontierstore.net/auth",
|
|
118
|
-
"AUTH_URL_TOKEN": "https://auth.frontierstore.net/token",
|
|
119
|
-
},
|
|
120
|
-
"companion": {
|
|
121
|
-
"CAPI_LIVE_URL": "https://companion.orerve.net",
|
|
122
|
-
"CAPI_BETA_URL": "https://pts-companion.orerve.net",
|
|
123
|
-
"CLIENT_ID": "0d60c9fe-1ae3-4849-91e9-250db5de9d79",
|
|
124
|
-
"REDIRECT_URI": "http://127.0.0.1:2989/callback",
|
|
125
|
-
},
|
|
126
|
-
"authorization": {}
|
|
127
|
-
})
|
|
128
|
-
self._authorization_set_config({})
|
|
129
|
-
self.config.read(self._configfile)
|
|
130
|
-
|
|
131
|
-
# If force login, kill the authorization
|
|
132
|
-
if self.login:
|
|
133
|
-
self._authorization_set_config({})
|
|
134
|
-
|
|
135
|
-
# Grab the commander profile
|
|
136
|
-
self.text = []
|
|
137
|
-
self.profile = self.query_capi("/profile")
|
|
138
|
-
|
|
139
|
-
# kfsone: not sure if there was a reason to query these even tho we didn't
|
|
140
|
-
# use the resulting data.
|
|
141
|
-
# market = self.query_capi("/market")
|
|
142
|
-
# shipyard = self.query_capi("/shipyard")
|
|
143
|
-
|
|
144
|
-
# Grab the market, outfitting and shipyard data if needed
|
|
145
|
-
portServices = self.profile['lastStarport'].get('services')
|
|
146
|
-
if self.profile['commander']['docked'] and portServices:
|
|
147
|
-
if portServices.get('commodities'):
|
|
148
|
-
res = self.query_capi("/market")
|
|
149
|
-
if int(res["id"]) == int(self.profile["lastStarport"]["id"]):
|
|
150
|
-
self.profile["lastStarport"].update(res)
|
|
151
|
-
hasShipyard = portServices.get('shipyard')
|
|
152
|
-
if hasShipyard or portServices.get('outfitting'):
|
|
153
|
-
# the ships for the shipyard are not always returned the first time
|
|
154
|
-
for attempt in range(3):
|
|
155
|
-
# try up to 3 times
|
|
156
|
-
res = self.query_capi("/shipyard")
|
|
157
|
-
if not hasShipyard or res.get('ships'):
|
|
158
|
-
break
|
|
159
|
-
if self.debug:
|
|
160
|
-
print("No shipyard in response, I'll try again in 5s")
|
|
161
|
-
time.sleep(5)
|
|
162
|
-
if int(res["id"]) == int(self.profile["lastStarport"]["id"]):
|
|
163
|
-
self.profile["lastStarport"].update(res)
|
|
164
|
-
|
|
165
|
-
def query_capi(self, capi_endpoint):
|
|
166
|
-
self._authorization_check()
|
|
167
|
-
response = self.opener.get(self.config["companion"]["CAPI_LIVE_URL"] + capi_endpoint)
|
|
168
|
-
try:
|
|
169
|
-
print(response.text)
|
|
170
|
-
data = response.json()
|
|
171
|
-
self.text.append(response.text)
|
|
172
|
-
except:
|
|
173
|
-
if self.debug:
|
|
174
|
-
print(' URL:', response.url)
|
|
175
|
-
print('status:', response.status_code)
|
|
176
|
-
print(' text:', response.text)
|
|
177
|
-
txtDebug = ""
|
|
178
|
-
else:
|
|
179
|
-
txtDebug = "\nTry with --debug and report this."
|
|
180
|
-
raise plugins.PluginException(
|
|
181
|
-
"Unable to parse JSON response for {}!"
|
|
182
|
-
"\nTry to relogin with the 'login' option."
|
|
183
|
-
"{}".format(capi_endpoint, txtDebug)
|
|
184
|
-
)
|
|
185
|
-
return data
|
|
186
|
-
|
|
187
|
-
def _authorization_check(self):
|
|
188
|
-
status_ok = True
|
|
189
|
-
expires_at = self.config.getint("authorization", "expires_at")
|
|
190
|
-
if self.debug:
|
|
191
|
-
print("auth expires_at", time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(expires_at)))
|
|
192
|
-
if (expires_at - time.time()) < 60:
|
|
193
|
-
if self.debug:
|
|
194
|
-
print("authorization expired")
|
|
195
|
-
status_ok = False
|
|
196
|
-
if self.config["authorization"]["refresh_token"]:
|
|
197
|
-
status_ok = self._authorization_refresh()
|
|
198
|
-
if not status_ok:
|
|
199
|
-
status_ok = self._authorization_login()
|
|
200
|
-
with open(self._configfile, "w") as c:
|
|
201
|
-
self.config.write(c)
|
|
202
|
-
|
|
203
|
-
if not status_ok:
|
|
204
|
-
# Something terrible happend
|
|
205
|
-
raise plugins.PluginException("Couldn't get frontier authorization.")
|
|
206
|
-
|
|
207
|
-
# Setup session authorization
|
|
208
|
-
self.opener.headers = {
|
|
209
|
-
'User-Agent': self._agent,
|
|
210
|
-
'Authorization': "%s %s" % (
|
|
211
|
-
self.config['authorization']['token_type'],
|
|
212
|
-
self.config['authorization']['access_token'],
|
|
213
|
-
),
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
def _authorization_set_config(self, auth_data):
|
|
217
|
-
self.config.set("authorization", "access_token", auth_data.get("access_token", ""))
|
|
218
|
-
self.config.set("authorization", "token_type", auth_data.get("token_type", ""))
|
|
219
|
-
self.config.set("authorization", "expires_at", str(auth_data.get("expires_at", 0)))
|
|
220
|
-
self.config.set("authorization", "refresh_token", auth_data.get("refresh_token", ""))
|
|
221
|
-
|
|
222
|
-
def _authorization_token(self, data):
|
|
223
|
-
expires_at = int(time.time())
|
|
224
|
-
res = requests.post(self.config["frontier"]["AUTH_URL_TOKEN"], data = data)
|
|
225
|
-
if self.debug:
|
|
226
|
-
print(res, res.url)
|
|
227
|
-
print(res.text)
|
|
228
|
-
if res.status_code == requests.codes.ok: # pylint: disable=no-member
|
|
229
|
-
auth_data = res.json()
|
|
230
|
-
auth_data['expires_at'] = expires_at + int(auth_data.get('expires_in', 0))
|
|
231
|
-
self._authorization_set_config(auth_data)
|
|
232
|
-
return True
|
|
233
|
-
self._authorization_set_config({})
|
|
234
|
-
return False
|
|
235
|
-
|
|
236
|
-
def _authorization_refresh(self):
|
|
237
|
-
data = {
|
|
238
|
-
"grant_type": "refresh_token",
|
|
239
|
-
"refresh_token": self.config["authorization"]["refresh_token"],
|
|
240
|
-
"client_id": self.config["companion"]["CLIENT_ID"],
|
|
241
|
-
}
|
|
242
|
-
return self._authorization_token(data)
|
|
243
|
-
|
|
244
|
-
def _authorization_login(self):
|
|
245
|
-
session_state = secrets.token_urlsafe(36)
|
|
246
|
-
code_verifier = secrets.token_urlsafe(36)
|
|
247
|
-
code_digest = hashlib.sha256(code_verifier.encode()).digest()
|
|
248
|
-
code_challenge = base64.urlsafe_b64encode(code_digest).decode().rstrip("=")
|
|
249
|
-
data = {
|
|
250
|
-
'response_type': 'code',
|
|
251
|
-
'redirect_uri': self.config["companion"]["REDIRECT_URI"],
|
|
252
|
-
'client_id': self.config["companion"]["CLIENT_ID"],
|
|
253
|
-
'code_challenge': code_challenge,
|
|
254
|
-
'code_challenge_method': 'S256',
|
|
255
|
-
'state': session_state,
|
|
256
|
-
}
|
|
257
|
-
req = requests.Request("GET", self.config["frontier"]["AUTH_URL_AUTH"], params = data)
|
|
258
|
-
pre = req.prepare()
|
|
259
|
-
webbrowser.open_new_tab(pre.url)
|
|
260
|
-
|
|
261
|
-
redirect_uri = urlsplit(self.config["companion"]["REDIRECT_URI"])
|
|
262
|
-
oauth = OAuthCallbackServer(redirect_uri.hostname, redirect_uri.port, OAuthCallbackHandler)
|
|
263
|
-
if oauth.httpd.callback_code and oauth.httpd.callback_state == session_state:
|
|
264
|
-
data = {
|
|
265
|
-
"grant_type": "authorization_code",
|
|
266
|
-
"code": oauth.httpd.callback_code,
|
|
267
|
-
"code_verifier": code_verifier,
|
|
268
|
-
"client_id": self.config["companion"]["CLIENT_ID"],
|
|
269
|
-
"redirect_uri": self.config["companion"]["REDIRECT_URI"],
|
|
270
|
-
}
|
|
271
|
-
return self._authorization_token(data)
|
|
272
|
-
return False
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
class EDDN:
|
|
276
|
-
_gateways = (
|
|
277
|
-
'https://eddn.edcd.io:4430/upload/',
|
|
278
|
-
# 'http://eddn-gateway.ed-td.space:8080/upload/',
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
_commodity_schemas = {
|
|
282
|
-
'production': 'https://eddn.edcd.io/schemas/commodity/3',
|
|
283
|
-
'test': 'https://eddn.edcd.io/schemas/commodity/3/test',
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
_shipyard_schemas = {
|
|
287
|
-
'production': 'https://eddn.edcd.io/schemas/shipyard/2',
|
|
288
|
-
'test': 'https://eddn.edcd.io/schemas/shipyard/2/test',
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
_outfitting_schemas = {
|
|
292
|
-
'production': 'https://eddn.edcd.io/schemas/outfitting/2',
|
|
293
|
-
'test': 'https://eddn.edcd.io/schemas/outfitting/2/test',
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
_debug = True
|
|
297
|
-
|
|
298
|
-
# As of 1.3, ED reports four levels.
|
|
299
|
-
_levels = (
|
|
300
|
-
'Low',
|
|
301
|
-
'Low',
|
|
302
|
-
'Med',
|
|
303
|
-
'High',
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
def __init__(
|
|
307
|
-
self,
|
|
308
|
-
uploaderID,
|
|
309
|
-
noHash,
|
|
310
|
-
softwareName,
|
|
311
|
-
softwareVersion
|
|
312
|
-
):
|
|
313
|
-
# Obfuscate uploaderID
|
|
314
|
-
if noHash:
|
|
315
|
-
self.uploaderID = uploaderID
|
|
316
|
-
else:
|
|
317
|
-
self.uploaderID = hashlib.sha1(uploaderID.encode('utf-8')).hexdigest()
|
|
318
|
-
self.softwareName = softwareName
|
|
319
|
-
self.softwareVersion = softwareVersion
|
|
320
|
-
|
|
321
|
-
def postMessage(
|
|
322
|
-
self,
|
|
323
|
-
message,
|
|
324
|
-
timestamp = 0
|
|
325
|
-
):
|
|
326
|
-
if timestamp:
|
|
327
|
-
timestamp = datetime.fromtimestamp(timestamp).isoformat()
|
|
328
|
-
else:
|
|
329
|
-
timestamp = datetime.now(timezone.utc).astimezone().isoformat()
|
|
330
|
-
|
|
331
|
-
message['message']['timestamp'] = timestamp
|
|
332
|
-
|
|
333
|
-
url = random.choice(self._gateways)
|
|
334
|
-
|
|
335
|
-
headers = {
|
|
336
|
-
'content-type': 'application/json; charset=utf8'
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if self._debug:
|
|
340
|
-
print(
|
|
341
|
-
json.dumps(
|
|
342
|
-
message,
|
|
343
|
-
sort_keys = True,
|
|
344
|
-
indent = 4
|
|
345
|
-
)
|
|
346
|
-
)
|
|
347
|
-
|
|
348
|
-
r = requests.post(
|
|
349
|
-
url,
|
|
350
|
-
headers = headers,
|
|
351
|
-
data = json.dumps(
|
|
352
|
-
message,
|
|
353
|
-
ensure_ascii = False
|
|
354
|
-
).encode('utf8'),
|
|
355
|
-
verify = True
|
|
356
|
-
)
|
|
357
|
-
|
|
358
|
-
r.raise_for_status()
|
|
359
|
-
|
|
360
|
-
def publishCommodities(
|
|
361
|
-
self,
|
|
362
|
-
systemName,
|
|
363
|
-
stationName,
|
|
364
|
-
marketId,
|
|
365
|
-
commodities,
|
|
366
|
-
additional = None,
|
|
367
|
-
timestamp = 0
|
|
368
|
-
):
|
|
369
|
-
message = {}
|
|
370
|
-
|
|
371
|
-
message['$schemaRef'] = self._commodity_schemas[('test' if self._debug else 'production')] # NOQA
|
|
372
|
-
|
|
373
|
-
message['header'] = {
|
|
374
|
-
'uploaderID': self.uploaderID,
|
|
375
|
-
'softwareName': self.softwareName,
|
|
376
|
-
'softwareVersion': self.softwareVersion
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
message['message'] = {
|
|
380
|
-
'systemName': systemName,
|
|
381
|
-
'stationName': stationName,
|
|
382
|
-
'marketId': marketId,
|
|
383
|
-
'commodities': commodities,
|
|
384
|
-
}
|
|
385
|
-
if additional:
|
|
386
|
-
message['message'].update(additional)
|
|
387
|
-
|
|
388
|
-
self.postMessage(message, timestamp)
|
|
389
|
-
|
|
390
|
-
def publishShipyard(
|
|
391
|
-
self,
|
|
392
|
-
systemName,
|
|
393
|
-
stationName,
|
|
394
|
-
marketId,
|
|
395
|
-
ships,
|
|
396
|
-
timestamp = 0
|
|
397
|
-
):
|
|
398
|
-
message = {}
|
|
399
|
-
|
|
400
|
-
message['$schemaRef'] = self._shipyard_schemas[('test' if self._debug else 'production')] # NOQA
|
|
401
|
-
|
|
402
|
-
message['header'] = {
|
|
403
|
-
'uploaderID': self.uploaderID,
|
|
404
|
-
'softwareName': self.softwareName,
|
|
405
|
-
'softwareVersion': self.softwareVersion
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
message['message'] = {
|
|
409
|
-
'systemName': systemName,
|
|
410
|
-
'stationName': stationName,
|
|
411
|
-
'marketId': marketId,
|
|
412
|
-
'ships': ships,
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
self.postMessage(message, timestamp)
|
|
416
|
-
|
|
417
|
-
def publishOutfitting(
|
|
418
|
-
self,
|
|
419
|
-
systemName,
|
|
420
|
-
stationName,
|
|
421
|
-
marketId,
|
|
422
|
-
modules,
|
|
423
|
-
timestamp = 0
|
|
424
|
-
):
|
|
425
|
-
message = {}
|
|
426
|
-
|
|
427
|
-
message['$schemaRef'] = self._outfitting_schemas[('test' if self._debug else 'production')] # NOQA
|
|
428
|
-
|
|
429
|
-
message['header'] = {
|
|
430
|
-
'uploaderID': self.uploaderID,
|
|
431
|
-
'softwareName': self.softwareName,
|
|
432
|
-
'softwareVersion': self.softwareVersion
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
message['message'] = {
|
|
436
|
-
'systemName': systemName,
|
|
437
|
-
'stationName': stationName,
|
|
438
|
-
'marketId': marketId,
|
|
439
|
-
'modules': modules,
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
self.postMessage(message, timestamp)
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
class ImportPlugin(plugins.ImportPluginBase):
|
|
446
|
-
"""
|
|
447
|
-
Plugin that downloads market and ship vendor data from the Elite Dangerous
|
|
448
|
-
mobile API.
|
|
449
|
-
"""
|
|
450
|
-
|
|
451
|
-
pluginOptions = {
|
|
452
|
-
'csvs': 'Merge shipyards into ShipVendor.csv.',
|
|
453
|
-
'edcd': 'Call the EDCD plugin first.',
|
|
454
|
-
'eddn': 'Post market, shipyard and outfitting to EDDN.',
|
|
455
|
-
'name': 'Do not obfuscate commander name for EDDN submit.',
|
|
456
|
-
'save': 'Save the API response (tmp/profile.YYYYMMDD_HHMMSS.json).',
|
|
457
|
-
'tdh': 'Save the API response for TDH (tmp/tdh_profile.json).',
|
|
458
|
-
'test': 'Test the plugin with a json file (test=[FILENAME]).',
|
|
459
|
-
'warn': 'Ask for station update if a API<->DB diff is encountered.',
|
|
460
|
-
'login': 'Ask for login credentials.',
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
configFile = "edapi.config"
|
|
464
|
-
|
|
465
|
-
def __init__(self, tdb, tdenv):
|
|
466
|
-
super().__init__(tdb, tdenv)
|
|
467
|
-
|
|
468
|
-
self.filename = self.defaultImportFile
|
|
469
|
-
self.configPath = tdb.dataPath / pathlib.Path(ImportPlugin.configFile)
|
|
470
|
-
|
|
471
|
-
def askForStationData(self, system, stnName = None, station = None):
|
|
472
|
-
"""
|
|
473
|
-
Ask for new or updated station data
|
|
474
|
-
"""
|
|
475
|
-
tdb, tdenv = self.tdb, self.tdenv
|
|
476
|
-
askForData = False
|
|
477
|
-
|
|
478
|
-
stnDefault = namedtuple(
|
|
479
|
-
'stnDefault', [
|
|
480
|
-
'lsFromStar', 'market', 'blackMarket', 'shipyard', 'maxPadSize',
|
|
481
|
-
'outfitting', 'rearm', 'refuel', 'repair', 'planetary',
|
|
482
|
-
]
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
def tellUserAPIResponse(defName, defValue):
|
|
486
|
-
if defValue == "Y":
|
|
487
|
-
tdenv.NOTE("{:>12} in API response", defName)
|
|
488
|
-
else:
|
|
489
|
-
tdenv.NOTE("{:>12} NOT in API response", defName)
|
|
490
|
-
|
|
491
|
-
def getYNfromObject(obj, key, val = None):
|
|
492
|
-
if val:
|
|
493
|
-
return "Y" if obj.get(key) == val else "N"
|
|
494
|
-
else:
|
|
495
|
-
return "Y" if key in obj else "N"
|
|
496
|
-
|
|
497
|
-
# defaults from API response are not reliable!
|
|
498
|
-
checkStarport = self.edAPI.profile['lastStarport']
|
|
499
|
-
defMarket = getYNfromObject(checkStarport, 'commodities')
|
|
500
|
-
defShipyard = getYNfromObject(checkStarport, 'ships')
|
|
501
|
-
defOutfitting = getYNfromObject(checkStarport, 'modules')
|
|
502
|
-
tellUserAPIResponse("'Outfitting'", defOutfitting)
|
|
503
|
-
tellUserAPIResponse("'ShipYard'", defShipyard)
|
|
504
|
-
tellUserAPIResponse("'Market'", defMarket)
|
|
505
|
-
|
|
506
|
-
def warnAPIResponse(checkName, checkYN):
|
|
507
|
-
# no warning if unknown
|
|
508
|
-
if checkYN == "?":
|
|
509
|
-
return False
|
|
510
|
-
warnText = (
|
|
511
|
-
"The station should{s} have a {what}, "
|
|
512
|
-
"but the API did{d} return one."
|
|
513
|
-
)
|
|
514
|
-
if checkYN == "Y":
|
|
515
|
-
s, d = "", "n't"
|
|
516
|
-
else:
|
|
517
|
-
s, d = "n't", ""
|
|
518
|
-
|
|
519
|
-
tdenv.WARN(warnText, what = checkName, s = s, d = d)
|
|
520
|
-
return True if self.getOption('warn') else False
|
|
521
|
-
|
|
522
|
-
# station services since ED update 2.4
|
|
523
|
-
checkServices = checkStarport.get('services', None)
|
|
524
|
-
if checkServices:
|
|
525
|
-
if station:
|
|
526
|
-
tdenv.NOTE('Station known.')
|
|
527
|
-
stnlsFromStar = station.lsFromStar
|
|
528
|
-
stnmaxPadSize = station.maxPadSize
|
|
529
|
-
stnplanetary = station.planetary
|
|
530
|
-
else:
|
|
531
|
-
tdenv.NOTE('Station unknown.')
|
|
532
|
-
stnlsFromStar = 0
|
|
533
|
-
stnmaxPadSize = "?"
|
|
534
|
-
stnplanetary = "?"
|
|
535
|
-
tdenv.NOTE("Found station services.")
|
|
536
|
-
if checkStarport.get('outpostType', None) == 'starport':
|
|
537
|
-
# only the big one can be detected
|
|
538
|
-
stnmaxPadSize = "L"
|
|
539
|
-
stnplanetary = "N"
|
|
540
|
-
defStation = stnDefault(
|
|
541
|
-
lsFromStar = stnlsFromStar,
|
|
542
|
-
market = getYNfromObject(checkServices, 'commodities', val = 'ok'),
|
|
543
|
-
blackMarket = getYNfromObject(checkServices, 'blackmarket', val = 'ok'),
|
|
544
|
-
shipyard = getYNfromObject(checkServices, 'shipyard', val = 'ok'),
|
|
545
|
-
maxPadSize = stnmaxPadSize,
|
|
546
|
-
outfitting = getYNfromObject(checkServices, 'outfitting', val = 'ok'),
|
|
547
|
-
rearm = getYNfromObject(checkServices, 'rearm', val = 'ok'),
|
|
548
|
-
refuel = getYNfromObject(checkServices, 'refuel', val = 'ok'),
|
|
549
|
-
repair = getYNfromObject(checkServices, 'repair', val = 'ok'),
|
|
550
|
-
planetary = stnplanetary,
|
|
551
|
-
)
|
|
552
|
-
elif station:
|
|
553
|
-
tdenv.NOTE('Station known.')
|
|
554
|
-
defStation = stnDefault(
|
|
555
|
-
lsFromStar = station.lsFromStar,
|
|
556
|
-
market = defMarket if station.market == "?" else station.market,
|
|
557
|
-
blackMarket = station.blackMarket,
|
|
558
|
-
shipyard = defShipyard if station.shipyard == "?" else station.shipyard,
|
|
559
|
-
maxPadSize = station.maxPadSize,
|
|
560
|
-
outfitting = defOutfitting if station.outfitting == "?" else station.outfitting,
|
|
561
|
-
rearm = station.rearm,
|
|
562
|
-
refuel = station.refuel,
|
|
563
|
-
repair = station.repair,
|
|
564
|
-
planetary = station.planetary,
|
|
565
|
-
)
|
|
566
|
-
else:
|
|
567
|
-
tdenv.NOTE('Station unknown.')
|
|
568
|
-
defStation = stnDefault(
|
|
569
|
-
lsFromStar = 0, market = defMarket,
|
|
570
|
-
blackMarket = "?", shipyard = defShipyard,
|
|
571
|
-
maxPadSize = "?", outfitting = defOutfitting,
|
|
572
|
-
rearm = "?", refuel = "?",
|
|
573
|
-
repair = "?", planetary = "?",
|
|
574
|
-
)
|
|
575
|
-
|
|
576
|
-
warning = False
|
|
577
|
-
if defStation.outfitting != defOutfitting:
|
|
578
|
-
warning |= warnAPIResponse('outfitting', defStation.outfitting)
|
|
579
|
-
if defStation.shipyard != defShipyard:
|
|
580
|
-
warning |= warnAPIResponse('shipyard', defStation.shipyard)
|
|
581
|
-
if defStation.market != defMarket:
|
|
582
|
-
warning |= warnAPIResponse('market', defStation.market)
|
|
583
|
-
if warning:
|
|
584
|
-
tdenv.WARN("Please update station data with correct values.")
|
|
585
|
-
tdenv.WARN("(Fields will be marked with an leading asterisk '*')")
|
|
586
|
-
askForData = True
|
|
587
|
-
if ((defStation.lsFromStar == 0) or ("?" in defStation)):
|
|
588
|
-
askForData = True
|
|
589
|
-
|
|
590
|
-
newStation = {}
|
|
591
|
-
for key in defStation._fields:
|
|
592
|
-
newStation[key] = getattr(defStation, key)
|
|
593
|
-
|
|
594
|
-
if askForData:
|
|
595
|
-
tdenv.NOTE("Values in brackets are the default.")
|
|
596
|
-
lsFromStar = input(
|
|
597
|
-
" Stn/Ls..............[{}]: ".format(defStation.lsFromStar)
|
|
598
|
-
) or defStation.lsFromStar
|
|
599
|
-
try:
|
|
600
|
-
lsFromStar = int(float(lsFromStar) + 0.5)
|
|
601
|
-
except:
|
|
602
|
-
print("That doesn't seem to be a number. Defaulting to zero.")
|
|
603
|
-
lsFromStar = defStation.lsFromStar
|
|
604
|
-
newStation['lsFromStar'] = lsFromStar
|
|
605
|
-
|
|
606
|
-
for askText, askField, markValue in [
|
|
607
|
-
('Pad Size....(s,m,l) ', 'maxPadSize', defStation.maxPadSize),
|
|
608
|
-
('Planetary.....(y,n) ', 'planetary', defStation.planetary),
|
|
609
|
-
('B/Market......(y,n) ', 'blackMarket', defStation.blackMarket),
|
|
610
|
-
('Refuel........(y,n) ', 'refuel', defStation.refuel),
|
|
611
|
-
('Repair........(y,n) ', 'repair', defStation.repair),
|
|
612
|
-
('Restock.......(y,n) ', 'rearm', defStation.rearm),
|
|
613
|
-
('Outfitting....(y,n) ', 'outfitting', defOutfitting),
|
|
614
|
-
('Shipyard......(y,n) ', 'shipyard', defShipyard),
|
|
615
|
-
('Market........(y,n) ', 'market', defMarket),
|
|
616
|
-
]:
|
|
617
|
-
defValue = getattr(defStation, askField)
|
|
618
|
-
if defValue != markValue:
|
|
619
|
-
mark = "*"
|
|
620
|
-
else:
|
|
621
|
-
mark = " "
|
|
622
|
-
askValue = input(
|
|
623
|
-
"{}{}[{}]: ".format(mark, askText, defValue)
|
|
624
|
-
) or defValue
|
|
625
|
-
newStation[askField] = askValue
|
|
626
|
-
|
|
627
|
-
else:
|
|
628
|
-
|
|
629
|
-
def _detail(value, source):
|
|
630
|
-
detail = source[value]
|
|
631
|
-
if detail == '?':
|
|
632
|
-
detail += ' [unknown]'
|
|
633
|
-
return detail
|
|
634
|
-
|
|
635
|
-
ls = newStation['lsFromStar']
|
|
636
|
-
print(" Stn/Ls....:", ls, '[unknown]' if ls == 0 else '')
|
|
637
|
-
print(" Pad Size..:", _detail(newStation['maxPadSize'], tdb.padSizes))
|
|
638
|
-
print(" Planetary.:", _detail(newStation['planetary'], tdb.planetStates))
|
|
639
|
-
print(" B/Market..:", _detail(newStation['blackMarket'], tdb.marketStates))
|
|
640
|
-
print(" Refuel....:", _detail(newStation['refuel'], tdb.marketStates))
|
|
641
|
-
print(" Repair....:", _detail(newStation['repair'], tdb.marketStates))
|
|
642
|
-
print(" Restock...:", _detail(newStation['rearm'], tdb.marketStates))
|
|
643
|
-
print(" Outfitting:", _detail(newStation['outfitting'], tdb.marketStates))
|
|
644
|
-
print(" Shipyard..:", _detail(newStation['shipyard'], tdb.marketStates))
|
|
645
|
-
print(" Market....:", _detail(newStation['market'], tdb.marketStates))
|
|
646
|
-
|
|
647
|
-
exportCSV = False
|
|
648
|
-
if not station:
|
|
649
|
-
station = tdb.addLocalStation(
|
|
650
|
-
system = system,
|
|
651
|
-
name = stnName,
|
|
652
|
-
lsFromStar = newStation['lsFromStar'],
|
|
653
|
-
blackMarket = newStation['blackMarket'],
|
|
654
|
-
maxPadSize = newStation['maxPadSize'],
|
|
655
|
-
market = newStation['market'],
|
|
656
|
-
shipyard = newStation['shipyard'],
|
|
657
|
-
outfitting = newStation['outfitting'],
|
|
658
|
-
rearm = newStation['rearm'],
|
|
659
|
-
refuel = newStation['refuel'],
|
|
660
|
-
repair = newStation['repair'],
|
|
661
|
-
planetary = newStation['planetary'],
|
|
662
|
-
)
|
|
663
|
-
exportCSV = True
|
|
664
|
-
else:
|
|
665
|
-
# let the function check for changes
|
|
666
|
-
if tdb.updateLocalStation(
|
|
667
|
-
station = station,
|
|
668
|
-
lsFromStar = newStation['lsFromStar'],
|
|
669
|
-
blackMarket = newStation['blackMarket'],
|
|
670
|
-
maxPadSize = newStation['maxPadSize'],
|
|
671
|
-
market = newStation['market'],
|
|
672
|
-
shipyard = newStation['shipyard'],
|
|
673
|
-
outfitting = newStation['outfitting'],
|
|
674
|
-
rearm = newStation['rearm'],
|
|
675
|
-
refuel = newStation['refuel'],
|
|
676
|
-
repair = newStation['repair'],
|
|
677
|
-
planetary = newStation['planetary'],
|
|
678
|
-
):
|
|
679
|
-
exportCSV = True
|
|
680
|
-
|
|
681
|
-
if exportCSV:
|
|
682
|
-
lines, csvPath = csvexport.exportTableToFile(
|
|
683
|
-
tdb,
|
|
684
|
-
tdenv,
|
|
685
|
-
"Station",
|
|
686
|
-
)
|
|
687
|
-
tdenv.DEBUG0("{} updated.", csvPath)
|
|
688
|
-
return station
|
|
689
|
-
|
|
690
|
-
def run(self):
|
|
691
|
-
tdb, tdenv = self.tdb, self.tdenv
|
|
692
|
-
|
|
693
|
-
# first check for EDCD
|
|
694
|
-
if self.getOption("edcd"):
|
|
695
|
-
# Call the EDCD plugin
|
|
696
|
-
try:
|
|
697
|
-
import plugins.edcd_plug as EDCD # @UnresolvedImport
|
|
698
|
-
except:
|
|
699
|
-
raise plugins.PluginException("EDCD plugin not found.")
|
|
700
|
-
tdenv.NOTE("Calling the EDCD plugin.")
|
|
701
|
-
edcdPlugin = EDCD.ImportPlugin(tdb, tdenv)
|
|
702
|
-
edcdPlugin.options["csvs"] = True
|
|
703
|
-
edcdPlugin.run()
|
|
704
|
-
tdenv.NOTE("Going back to EDAPI.\n")
|
|
705
|
-
|
|
706
|
-
# now load the mapping tables
|
|
707
|
-
itemMap = mapping.FDEVMappingItems(tdb, tdenv)
|
|
708
|
-
shipMap = mapping.FDEVMappingShips(tdb, tdenv)
|
|
709
|
-
|
|
710
|
-
# Connect to the API, authenticate, and pull down the commander
|
|
711
|
-
# /profile.
|
|
712
|
-
if self.getOption("test"):
|
|
713
|
-
tdenv.WARN("#############################")
|
|
714
|
-
tdenv.WARN("### EDAPI in test mode. ###")
|
|
715
|
-
tdenv.WARN("#############################")
|
|
716
|
-
apiED = namedtuple('EDAPI', ['profile', 'text'])
|
|
717
|
-
try:
|
|
718
|
-
proPath = pathlib.Path(self.getOption("test"))
|
|
719
|
-
except TypeError:
|
|
720
|
-
raise plugins.PluginException(
|
|
721
|
-
"Option 'test' must be a file name"
|
|
722
|
-
)
|
|
723
|
-
if proPath.exists():
|
|
724
|
-
with proPath.open() as proFile:
|
|
725
|
-
proData = json.load(proFile)
|
|
726
|
-
if isinstance(proData, list):
|
|
727
|
-
# since 4.3.0: list(profile, market, shipyard)
|
|
728
|
-
testProfile = proData[0]
|
|
729
|
-
for data in proData[1:]:
|
|
730
|
-
if int(data["id"]) == int(testProfile["lastStarport"]["id"]):
|
|
731
|
-
testProfile["lastStarport"].update(data)
|
|
732
|
-
else:
|
|
733
|
-
testProfile = proData
|
|
734
|
-
api = apiED(
|
|
735
|
-
profile = testProfile,
|
|
736
|
-
text = '{{"mode":"test","file":"{}"}}'.format(str(proPath))
|
|
737
|
-
)
|
|
738
|
-
else:
|
|
739
|
-
raise plugins.PluginException(
|
|
740
|
-
"JSON-file '{}' not found.".format(str(proPath))
|
|
741
|
-
)
|
|
742
|
-
else:
|
|
743
|
-
api = EDAPI(
|
|
744
|
-
configfile = str(self.configPath),
|
|
745
|
-
login = self.getOption('login'),
|
|
746
|
-
debug = tdenv.debug,
|
|
747
|
-
)
|
|
748
|
-
self.edAPI = api
|
|
749
|
-
|
|
750
|
-
fs.ensurefolder(tdenv.tmpDir)
|
|
751
|
-
|
|
752
|
-
if self.getOption("tdh"):
|
|
753
|
-
self.options["save"] = True
|
|
754
|
-
|
|
755
|
-
# save profile if requested
|
|
756
|
-
if self.getOption("save"):
|
|
757
|
-
saveName = 'tdh_profile.json' if self.getOption("tdh") else \
|
|
758
|
-
'profile.' + time.strftime('%Y%m%d_%H%M%S') + '.json'
|
|
759
|
-
savePath = tdenv.tmpDir / pathlib.Path(saveName)
|
|
760
|
-
if savePath.exists():
|
|
761
|
-
savePath.unlink()
|
|
762
|
-
with open(savePath, 'w', encoding = "utf-8") as saveFile:
|
|
763
|
-
if isinstance(api.text, list):
|
|
764
|
-
# since 4.3.0: list(profile, market, shipyard)
|
|
765
|
-
tdenv.DEBUG0("{}", api.text)
|
|
766
|
-
saveFile.write('{{"profile":{}}}'.format(api.text[0]))
|
|
767
|
-
else:
|
|
768
|
-
saveFile.write(api.text)
|
|
769
|
-
print('API response saved to: {}'.format(savePath))
|
|
770
|
-
|
|
771
|
-
# If TDH is calling the plugin, nothing else needs to be done
|
|
772
|
-
# now that the file has been created.
|
|
773
|
-
if self.getOption("tdh"):
|
|
774
|
-
return False
|
|
775
|
-
|
|
776
|
-
# Sanity check that the commander is docked. Otherwise we will get a
|
|
777
|
-
# mismatch between the last system and last station.
|
|
778
|
-
if not api.profile['commander']['docked']:
|
|
779
|
-
print('Commander not docked. Aborting!')
|
|
780
|
-
return False
|
|
781
|
-
|
|
782
|
-
# Figure out where we are.
|
|
783
|
-
sysName = api.profile['lastSystem']['name']
|
|
784
|
-
stnName = api.profile['lastStarport']['name']
|
|
785
|
-
marketId = int(api.profile['lastStarport']['id'])
|
|
786
|
-
print('@{}/{} (ID: {})'.format(sysName.upper(), stnName, marketId))
|
|
787
|
-
|
|
788
|
-
# Reload the cache.
|
|
789
|
-
tdenv.DEBUG0("Checking the cache")
|
|
790
|
-
tdb.close()
|
|
791
|
-
tdb.reloadCache()
|
|
792
|
-
tdb.load(
|
|
793
|
-
maxSystemLinkLy = tdenv.maxSystemLinkLy,
|
|
794
|
-
)
|
|
795
|
-
tdb.close()
|
|
796
|
-
|
|
797
|
-
# Check to see if this system is in the database
|
|
798
|
-
try:
|
|
799
|
-
system = tdb.lookupSystem(sysName)
|
|
800
|
-
except LookupError:
|
|
801
|
-
raise plugins.PluginException(
|
|
802
|
-
"System '{}' unknown.".format(sysName)
|
|
803
|
-
)
|
|
804
|
-
|
|
805
|
-
# Check to see if this station is in the database
|
|
806
|
-
try:
|
|
807
|
-
station = tdb.lookupStation(stnName, system)
|
|
808
|
-
except LookupError:
|
|
809
|
-
station = None
|
|
810
|
-
|
|
811
|
-
# New or update station data
|
|
812
|
-
station = self.askForStationData(system, stnName = stnName, station = station)
|
|
813
|
-
|
|
814
|
-
# If a shipyard exists, make the ship lists
|
|
815
|
-
shipCost = {}
|
|
816
|
-
shipList = []
|
|
817
|
-
eddn_ships = []
|
|
818
|
-
if ((station.shipyard == "Y") and ('ships' in api.profile['lastStarport'])):
|
|
819
|
-
if 'shipyard_list' in api.profile['lastStarport']['ships']:
|
|
820
|
-
if len(api.profile['lastStarport']['ships']['shipyard_list']):
|
|
821
|
-
for ship in api.profile['lastStarport']['ships']['shipyard_list'].values():
|
|
822
|
-
shipName = shipMap.mapID(ship['id'], ship['name'])
|
|
823
|
-
shipCost[shipName] = ship['basevalue']
|
|
824
|
-
shipList.append(shipName)
|
|
825
|
-
eddn_ships.append(ship['name'])
|
|
826
|
-
|
|
827
|
-
if 'unavailable_list' in api.profile['lastStarport']['ships']:
|
|
828
|
-
for ship in api.profile['lastStarport']['ships']['unavailable_list']:
|
|
829
|
-
shipName = shipMap.mapID(ship['id'], ship['name'])
|
|
830
|
-
shipCost[shipName] = ship['basevalue']
|
|
831
|
-
shipList.append(shipName)
|
|
832
|
-
eddn_ships.append(ship['name'])
|
|
833
|
-
|
|
834
|
-
if self.getOption("csvs"):
|
|
835
|
-
addShipList = set()
|
|
836
|
-
delShipList = set()
|
|
837
|
-
addRows = delRows = 0
|
|
838
|
-
db = tdb.getDB()
|
|
839
|
-
if station.shipyard == "N":
|
|
840
|
-
# delete all ships if there is no shipyard
|
|
841
|
-
delRows = db.execute(
|
|
842
|
-
"""
|
|
843
|
-
DELETE FROM ShipVendor
|
|
844
|
-
WHERE station_id = ?
|
|
845
|
-
""",
|
|
846
|
-
[station.ID]
|
|
847
|
-
).rowcount
|
|
848
|
-
|
|
849
|
-
if len(shipList):
|
|
850
|
-
# and now update the shipyard list
|
|
851
|
-
# we go through all ships to decide if a ship needs to be
|
|
852
|
-
# added or deleted from the shipyard
|
|
853
|
-
for shipID in tdb.shipByID:
|
|
854
|
-
shipName = tdb.shipByID[shipID].dbname
|
|
855
|
-
if shipName in shipList:
|
|
856
|
-
# check for ship discount, costTD = 100%
|
|
857
|
-
# python builtin round() uses "Round half to even"
|
|
858
|
-
# but we need commercial rounding, so we do it ourself
|
|
859
|
-
costTD = tdb.shipByID[shipID].cost
|
|
860
|
-
costED = int((shipCost[shipName] + 5) / 10) * 10
|
|
861
|
-
if costTD != costED:
|
|
862
|
-
prozED = int(shipCost[shipName] * 100 / costTD + 0.5) - 100
|
|
863
|
-
tdenv.NOTE(
|
|
864
|
-
"CostDiff {}: {} != {} ({}%)",
|
|
865
|
-
shipName, costTD, costED, prozED
|
|
866
|
-
)
|
|
867
|
-
# add the ship to the shipyard
|
|
868
|
-
shipSQL = (
|
|
869
|
-
"INSERT OR IGNORE"
|
|
870
|
-
" INTO ShipVendor(station_id, ship_id)"
|
|
871
|
-
" VALUES(?, ?)"
|
|
872
|
-
)
|
|
873
|
-
tdenv.DEBUG0(shipSQL.replace("?", "{}"), station.ID, shipID)
|
|
874
|
-
rc = db.execute(shipSQL, [station.ID, shipID]).rowcount
|
|
875
|
-
if rc:
|
|
876
|
-
addRows += rc
|
|
877
|
-
addShipList.add(shipName)
|
|
878
|
-
# remove ship from the list
|
|
879
|
-
shipList.remove(shipName)
|
|
880
|
-
else:
|
|
881
|
-
# delete the ship from the shipyard
|
|
882
|
-
shipSQL = (
|
|
883
|
-
"DELETE FROM ShipVendor"
|
|
884
|
-
" WHERE station_id = ?"
|
|
885
|
-
" AND ship_id = ?"
|
|
886
|
-
)
|
|
887
|
-
tdenv.DEBUG0(shipSQL.replace("?", "{}"), station.ID, shipID)
|
|
888
|
-
rc = db.execute(shipSQL, [station.ID, shipID]).rowcount
|
|
889
|
-
if rc:
|
|
890
|
-
delRows += rc
|
|
891
|
-
delShipList.add(shipName)
|
|
892
|
-
|
|
893
|
-
if len(shipList):
|
|
894
|
-
tdenv.WARN("unknown Ship(s): {}", ",".join(shipList))
|
|
895
|
-
|
|
896
|
-
db.commit()
|
|
897
|
-
if (addRows + delRows) > 0:
|
|
898
|
-
if addRows > 0:
|
|
899
|
-
tdenv.NOTE(
|
|
900
|
-
"Added {} ({}) ships in '{}' shipyard.",
|
|
901
|
-
addRows, ", ".join(sorted(addShipList)), station.name()
|
|
902
|
-
)
|
|
903
|
-
if delRows > 0:
|
|
904
|
-
tdenv.NOTE(
|
|
905
|
-
"Deleted {} ({}) ships in '{}' shipyard.",
|
|
906
|
-
delRows, ", ".join(sorted(delShipList)), station.name()
|
|
907
|
-
)
|
|
908
|
-
lines, csvPath = csvexport.exportTableToFile(
|
|
909
|
-
tdb,
|
|
910
|
-
tdenv,
|
|
911
|
-
"ShipVendor",
|
|
912
|
-
)
|
|
913
|
-
tdenv.DEBUG0("{} updated.", csvPath)
|
|
914
|
-
|
|
915
|
-
# If a market exists, make the item lists
|
|
916
|
-
itemList = []
|
|
917
|
-
eddn_market = []
|
|
918
|
-
if ((station.market == "Y") and ('commodities' in api.profile['lastStarport'])):
|
|
919
|
-
for commodity in api.profile['lastStarport']['commodities']:
|
|
920
|
-
if commodity['categoryname'] in cat_ignore:
|
|
921
|
-
continue
|
|
922
|
-
|
|
923
|
-
if commodity.get('legality', '') != '':
|
|
924
|
-
# ignore if present and not empty
|
|
925
|
-
continue
|
|
926
|
-
|
|
927
|
-
locName = commodity.get('locName', commodity['name'])
|
|
928
|
-
itmName = itemMap.mapID(commodity['id'], locName)
|
|
929
|
-
|
|
930
|
-
def commodity_int(key):
|
|
931
|
-
try:
|
|
932
|
-
ret = int(float(commodity[key]) + 0.5)
|
|
933
|
-
except (ValueError, KeyError):
|
|
934
|
-
ret = 0
|
|
935
|
-
return ret
|
|
936
|
-
|
|
937
|
-
itmSupply = commodity_int('stock')
|
|
938
|
-
itmDemand = commodity_int('demand')
|
|
939
|
-
itmSupplyLevel = commodity_int('stockBracket')
|
|
940
|
-
itmDemandLevel = commodity_int('demandBracket')
|
|
941
|
-
itmBuyPrice = commodity_int('buyPrice')
|
|
942
|
-
itmSellPrice = commodity_int('sellPrice')
|
|
943
|
-
|
|
944
|
-
if itmSupplyLevel == 0 or itmBuyPrice == 0:
|
|
945
|
-
# If there is not stockBracket or buyPrice, ignore stock
|
|
946
|
-
itmBuyPrice = 0
|
|
947
|
-
itmSupply = 0
|
|
948
|
-
itmSupplyLevel = 0
|
|
949
|
-
tdSupply = "-"
|
|
950
|
-
tdDemand = "{}{}".format(
|
|
951
|
-
itmDemand,
|
|
952
|
-
bracket_levels[itmDemandLevel]
|
|
953
|
-
)
|
|
954
|
-
else:
|
|
955
|
-
# otherwise don't care about demand
|
|
956
|
-
itmDemand = 0
|
|
957
|
-
itmDemandLevel = 0
|
|
958
|
-
tdDemand = "?"
|
|
959
|
-
tdSupply = "{}{}".format(
|
|
960
|
-
itmSupply,
|
|
961
|
-
bracket_levels[itmSupplyLevel]
|
|
962
|
-
)
|
|
963
|
-
|
|
964
|
-
# ignore items without supply or demand bracket (TD only)
|
|
965
|
-
if itmSupplyLevel > 0 or itmDemandLevel > 0:
|
|
966
|
-
itemTD = (
|
|
967
|
-
itmName,
|
|
968
|
-
itmSellPrice, itmBuyPrice,
|
|
969
|
-
tdDemand, tdSupply,
|
|
970
|
-
)
|
|
971
|
-
itemList.append(itemTD)
|
|
972
|
-
|
|
973
|
-
# Populate EDDN
|
|
974
|
-
if self.getOption("eddn"):
|
|
975
|
-
itemEDDN = {
|
|
976
|
-
"name": commodity['name'],
|
|
977
|
-
"meanPrice": commodity_int('meanPrice'),
|
|
978
|
-
"buyPrice": commodity_int('buyPrice'),
|
|
979
|
-
"stock": commodity_int('stock'),
|
|
980
|
-
"stockBracket": commodity['stockBracket'],
|
|
981
|
-
"sellPrice": commodity_int('sellPrice'),
|
|
982
|
-
"demand": commodity_int('demand'),
|
|
983
|
-
"demandBracket": commodity['demandBracket'],
|
|
984
|
-
}
|
|
985
|
-
if len(commodity['statusFlags']) > 0:
|
|
986
|
-
itemEDDN["statusFlags"] = commodity['statusFlags']
|
|
987
|
-
eddn_market.append(itemEDDN)
|
|
988
|
-
|
|
989
|
-
if itemList:
|
|
990
|
-
# Create the import file.
|
|
991
|
-
with open(self.filename, 'w', encoding = "utf-8") as f:
|
|
992
|
-
# write System/Station line
|
|
993
|
-
f.write("@ {}/{}\n".format(sysName, stnName))
|
|
994
|
-
|
|
995
|
-
# write Item lines (category lines are not needed)
|
|
996
|
-
for itemTD in itemList:
|
|
997
|
-
f.write("\t\t%s %s %s %s %s\n" % itemTD)
|
|
998
|
-
|
|
999
|
-
tdenv.ignoreUnknown = True
|
|
1000
|
-
cache.importDataFromFile(
|
|
1001
|
-
tdb,
|
|
1002
|
-
tdenv,
|
|
1003
|
-
pathlib.Path(self.filename),
|
|
1004
|
-
)
|
|
1005
|
-
|
|
1006
|
-
# Import EDDN
|
|
1007
|
-
if self.getOption("eddn"):
|
|
1008
|
-
con = EDDN(
|
|
1009
|
-
api.profile['commander']['name'],
|
|
1010
|
-
self.getOption("name"),
|
|
1011
|
-
'EDAPI Trade Dangerous Plugin',
|
|
1012
|
-
__version__
|
|
1013
|
-
)
|
|
1014
|
-
if self.getOption("test"):
|
|
1015
|
-
con._debug = True
|
|
1016
|
-
else:
|
|
1017
|
-
con._debug = False
|
|
1018
|
-
|
|
1019
|
-
if eddn_market:
|
|
1020
|
-
print('Posting commodities to EDDN...')
|
|
1021
|
-
eddn_additional = {}
|
|
1022
|
-
if 'economies' in api.profile['lastStarport']:
|
|
1023
|
-
eddn_additional['economies'] = []
|
|
1024
|
-
for economy in api.profile['lastStarport']['economies'].values():
|
|
1025
|
-
eddn_additional['economies'].append(economy)
|
|
1026
|
-
if 'prohibited' in api.profile['lastStarport']:
|
|
1027
|
-
eddn_additional['prohibited'] = []
|
|
1028
|
-
for item in api.profile['lastStarport']['prohibited'].values():
|
|
1029
|
-
eddn_additional['prohibited'].append(item)
|
|
1030
|
-
|
|
1031
|
-
con.publishCommodities(
|
|
1032
|
-
sysName,
|
|
1033
|
-
stnName,
|
|
1034
|
-
marketId,
|
|
1035
|
-
eddn_market,
|
|
1036
|
-
additional = eddn_additional
|
|
1037
|
-
)
|
|
1038
|
-
|
|
1039
|
-
if eddn_ships:
|
|
1040
|
-
print('Posting shipyard to EDDN...')
|
|
1041
|
-
con.publishShipyard(
|
|
1042
|
-
sysName,
|
|
1043
|
-
stnName,
|
|
1044
|
-
marketId,
|
|
1045
|
-
eddn_ships
|
|
1046
|
-
)
|
|
1047
|
-
|
|
1048
|
-
if station.outfitting == "Y" and 'modules' in api.profile['lastStarport'] and len(api.profile['lastStarport']['modules']):
|
|
1049
|
-
eddn_modules = []
|
|
1050
|
-
for module in api.profile['lastStarport']['modules'].values():
|
|
1051
|
-
# see: https://github.com/EDSM-NET/EDDN/wiki
|
|
1052
|
-
addModule = False
|
|
1053
|
-
if module['name'].startswith(('Hpt_', 'Int_')) or module['name'].find('_Armour_') > 0:
|
|
1054
|
-
if module.get('sku', None) in (None, 'ELITE_HORIZONS_V_PLANETARY_LANDINGS'):
|
|
1055
|
-
if module['name'] != 'Int_PlanetApproachSuite':
|
|
1056
|
-
addModule = True
|
|
1057
|
-
if addModule:
|
|
1058
|
-
eddn_modules.append(module['name'])
|
|
1059
|
-
elif self.getOption("test"):
|
|
1060
|
-
tdenv.NOTE("Ignored module ID: {}, name: {}", module['id'], module['name'])
|
|
1061
|
-
if eddn_modules:
|
|
1062
|
-
print('Posting outfitting to EDDN...')
|
|
1063
|
-
con.publishOutfitting(
|
|
1064
|
-
sysName,
|
|
1065
|
-
stnName,
|
|
1066
|
-
marketId,
|
|
1067
|
-
sorted(eddn_modules)
|
|
1068
|
-
)
|
|
1069
|
-
|
|
1070
|
-
# We did all the work
|
|
1071
|
-
return False
|