From a61829eaf724c2e5d27c06ec97c828fbc473b915 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Tue, 30 Aug 2022 17:17:00 +0100 Subject: [PATCH 1/5] EDDN: Implement Journal-based `fcmaterials/1` schema The EDDN schema is still a work in progress, unsure if we'll use the same one for both Journal and CAPI sourced data, or separate schemas. But, so far, this works. --- plugins/eddn.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index b369e415..5ffb216d 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -88,6 +88,8 @@ class This: self.commodities: Optional[List[OrderedDictT[str, Any]]] = None self.outfitting: Optional[Tuple[bool, List[str]]] = None self.shipyard: Optional[Tuple[bool, List[Mapping[str, Any]]]] = None + self.fcmaterials_marketid: int = 0 + self.fcmaterials: Optional[List[OrderedDictT[str, Any]]] = None # For the tkinter parent window, so we can call update_idletasks() self.parent: tk.Tk @@ -1079,7 +1081,6 @@ class EDDN: Send a NavRoute to EDDN on the correct schema. :param cmdr: the commander under which this upload is made - :param system_starpos: Coordinates of current star system :param is_beta: whether or not we are in beta mode :param entry: the journal entry to send """ @@ -1146,6 +1147,87 @@ class EDDN: this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_journal_fcmaterials( + self, cmdr: str, is_beta: bool, entry: MutableMapping[str, Any] + ) -> Optional[str]: + """ + Send an FCMaterials message to EDDN on the correct schema. + + :param cmdr: the commander under which this upload is made + :param is_beta: whether or not we are in beta mode + :param entry: the journal entry to send + """ + # { + # "timestamp":"2022-06-08T12:44:19Z", + # "event":"FCMaterials", + # "MarketID":3700710912, + # "CarrierName":"PSI RORSCHACH", + # "CarrierID":"K4X-33F", + # "Items":[ + # { + # "id":128961533, + # "Name":"$encryptedmemorychip_name;", + # "Name_Localised":"Encrypted Memory Chip", + # "Price":500, + # "Stock":0, + # "Demand":5 + # }, + # + # { "id":128961537, + # "Name":"$memorychip_name;", + # "Name_Localised":"Memory Chip", + # "Price":600, + # "Stock":0, + # "Demand":5 + # }, + # + # { "id":128972290, + # "Name":"$campaignplans_name;", + # "Name_Localised":"Campaign Plans", + # "Price":600, + # "Stock":5, + # "Demand":0 + # } + # ] + # } + + # Sanity check + if 'Items' not in entry: + logger.warning(f"FCMaterials didn't contain an Items array!\n{entry!r}") + # This can happen if first-load of the file failed, and we're simply + # passing through the bare Journal event, so no need to alert + # the user. + return None + + if this.fcmaterials_marketid == entry['MarketID']: + if this.fcmaterials == entry['Items']: + # Same FC, no change in Stock/Demand/Prices, so don't send + return None + + this.fcmaterials_marketid = entry['MarketID'] + this.fcmaterials = entry['Items'] + + ####################################################################### + # Elisions + ####################################################################### + # There are Name_Localised key/values in the Items array members + entry = filter_localised(entry) + ####################################################################### + + ####################################################################### + # Augmentations + ####################################################################### + # None + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/fcmaterials/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(cmdr, entry, msg) + return None + def export_journal_approachsettlement( self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] ) -> Optional[str]: @@ -1776,6 +1858,9 @@ def journal_entry( # noqa: C901, CCR001 elif event_name == 'navroute': return this.eddn.export_journal_navroute(cmdr, is_beta, entry) + elif event_name == 'fcmaterials': + return this.eddn.export_journal_fcmaterials(cmdr, is_beta, entry) + elif event_name == 'approachsettlement': # An `ApproachSettlement` can appear *before* `Location` if you # logged at one. We won't have necessary augmentation data From 37553d6bc7fe5988d4cfbd7742e4fac5d5f6b2c1 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 31 Aug 2022 15:19:54 +0100 Subject: [PATCH 2/5] EDDN: Add 'data-source' to Journal FCMaterials export --- plugins/eddn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 5ffb216d..8b71c425 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1217,7 +1217,8 @@ class EDDN: ####################################################################### # Augmentations ####################################################################### - # None + # Mandatory data-source + entry['data-source'] = 'Journal' ####################################################################### msg = { From c77576f3d467ce03997e4e711b93dee41349763a Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 31 Aug 2022 15:59:34 +0100 Subject: [PATCH 3/5] EDDN: `fcmaterials/1` from CAPI `/market` data --- plugins/eddn.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index 8b71c425..7dbdb9d2 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -90,6 +90,8 @@ class This: self.shipyard: Optional[Tuple[bool, List[Mapping[str, Any]]]] = None self.fcmaterials_marketid: int = 0 self.fcmaterials: Optional[List[OrderedDictT[str, Any]]] = None + self.fcmaterials_capi_marketid: int = 0 + self.fcmaterials_capi: Optional[List[OrderedDictT[str, Any]]] = None # For the tkinter parent window, so we can call update_idletasks() self.parent: tk.Tk @@ -147,6 +149,7 @@ class EDDN: r"https://eddn.edcd.io/schemas/(?P.+)/(?P[0-9]+) is unknown, " r"unable to validate.',\)\]$" ) + CAPI_LOCALISATION_RE = re.compile(r'^loc[A-Z].+') def __init__(self, parent: tk.Tk): self.parent: tk.Tk = parent @@ -490,6 +493,9 @@ class EDDN: this.commodities = commodities + # Send any FCMaterials.json-equivalent 'orders' as well + self.export_capi_fcmaterials(data, is_beta, horizons) + def safe_modules_and_ships(self, data: Mapping[str, Any]) -> Tuple[Dict, Dict]: """ Produce a sanity-checked version of ships and modules from CAPI data. @@ -1229,6 +1235,63 @@ class EDDN: this.eddn.export_journal_entry(cmdr, entry, msg) return None + def export_capi_fcmaterials( + self, data: Mapping[str, Any], is_beta: bool, horizons: bool + ) -> Optional[str]: + """ + Send CAPI-sourced 'onfootmicroresources' data on `fcmaterials/1` schema. + + :param data: the CAPI `/market` data + :param is_beta: whether, or not we are in beta mode + :param horizons: whether player is in Horizons + """ + # Sanity check + if 'lastStarport' not in data: + return None + + if 'orders' not in data['lastStarport']: + return None + + if 'onfootmicroresources' not in data['lastStarport']['orders']: + return None + + items = data['lastStarport']['orders']['onfootmicroresources'] + if this.fcmaterials_capi_marketid == data['lastStarport']['id']: + if this.fcmaterials_capi == items: + # Same FC, no change in orders, so don't send + return None + + this.fcmaterials_capi_marketid = data['lastStarport']['id'] + this.fcmaterials_capi = items + + ####################################################################### + # Elisions + ####################################################################### + # There are localised key names for the resources + items = capi_filter_localised(items) + ####################################################################### + + ####################################################################### + # EDDN `'message'` creation, and augmentations + ####################################################################### + entry = { + 'timestamp': data['timestamp'], + 'event': 'FCMaterials', + 'data-source': 'CAPI', # Mandatory indication of data source + 'MarketID': data['lastStarport']['id'], + 'CarrierID': data['lastStarport']['name'], + 'Items': items, + } + ####################################################################### + + msg = { + '$schemaRef': f'https://eddn.edcd.io/schemas/fcmaterials/1{"/test" if is_beta else ""}', + 'message': entry + } + + this.eddn.export_journal_entry(data['commander']['name'], entry, msg) + return None + def export_journal_approachsettlement( self, cmdr: str, system_name: str, system_starpos: list, is_beta: bool, entry: MutableMapping[str, Any] ) -> Optional[str]: @@ -1707,10 +1770,9 @@ def plugin_stop() -> None: logger.debug('Done.') -# Recursively filter '*_Localised' keys from dict def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: """ - Remove any dict keys with names ending `_Localised` from a dict. + Recursively remove any dict keys with names ending `_Localised` from a dict. :param d: Dict to filter keys of. :return: The filtered dict. @@ -1732,6 +1794,30 @@ def filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: return filtered +def capi_filter_localised(d: Mapping[str, Any]) -> OrderedDictT[str, Any]: + """ + Recursively remove any dict keys for known CAPI 'localised' names. + + :param d: Dict to filter keys of. + :return: The filtered dict. + """ + filtered: OrderedDictT[str, Any] = OrderedDict() + for k, v in d.items(): + if EDDN.CAPI_LOCALISATION_RE.search(k): + pass + + elif hasattr(v, 'items'): # dict -> recurse + filtered[k] = capi_filter_localised(v) + + elif isinstance(v, list): # list of dicts -> recurse + filtered[k] = [capi_filter_localised(x) if hasattr(x, 'items') else x for x in v] + + else: + filtered[k] = v + + return filtered + + def journal_entry( # noqa: C901, CCR001 cmdr: str, is_beta: bool, From 69ef3b7d399a191ebc0c963ca1464d5e02ef71fc Mon Sep 17 00:00:00 2001 From: Athanasius Date: Wed, 31 Aug 2022 16:34:21 +0100 Subject: [PATCH 4/5] eddn: fcmaterials: Actually send horizons and odyssey flags for CAPI data --- plugins/eddn.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/eddn.py b/plugins/eddn.py index 7dbdb9d2..f74ef9c1 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1278,6 +1278,8 @@ class EDDN: 'timestamp': data['timestamp'], 'event': 'FCMaterials', 'data-source': 'CAPI', # Mandatory indication of data source + 'horizons': horizons, + 'odyssey': this.odyssey, 'MarketID': data['lastStarport']['id'], 'CarrierID': data['lastStarport']['name'], 'Items': items, From e530544ac5d34c08e1100ed61afcc21e53c19f46 Mon Sep 17 00:00:00 2001 From: Athanasius Date: Fri, 2 Sep 2022 17:27:16 +0100 Subject: [PATCH 5/5] eddn: fcmaterials: Now working for the two separate schemas --- plugins/eddn.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/eddn.py b/plugins/eddn.py index f74ef9c1..cd1d53ff 100644 --- a/plugins/eddn.py +++ b/plugins/eddn.py @@ -1223,12 +1223,11 @@ class EDDN: ####################################################################### # Augmentations ####################################################################### - # Mandatory data-source - entry['data-source'] = 'Journal' + # None ####################################################################### msg = { - '$schemaRef': f'https://eddn.edcd.io/schemas/fcmaterials/1{"/test" if is_beta else ""}', + '$schemaRef': f'https://eddn.edcd.io/schemas/fcmaterials_journal/1{"/test" if is_beta else ""}', 'message': entry } @@ -1277,7 +1276,6 @@ class EDDN: entry = { 'timestamp': data['timestamp'], 'event': 'FCMaterials', - 'data-source': 'CAPI', # Mandatory indication of data source 'horizons': horizons, 'odyssey': this.odyssey, 'MarketID': data['lastStarport']['id'], @@ -1287,7 +1285,7 @@ class EDDN: ####################################################################### msg = { - '$schemaRef': f'https://eddn.edcd.io/schemas/fcmaterials/1{"/test" if is_beta else ""}', + '$schemaRef': f'https://eddn.edcd.io/schemas/fcmaterials_capi/1{"/test" if is_beta else ""}', 'message': entry }