Last active
May 31, 2022 20:31
-
-
Save arav97531/48b166e1941057bcf35d0b3c129e4195 to your computer and use it in GitHub Desktop.
Elite: Dangerous Journal Extractor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Elite: Dangerous Journal Extractor | |
Is a tool for extracting information from journal files. | |
Features: | |
- extract your discoveries into a CSV table; | |
- make an ImportStars.txt file. | |
Changelog: | |
Version 1.0.1 - 19th October 2018 | |
- sorting discoveries by scan timestamp; | |
- edit comments, minor fixes. | |
Version 1.0.0 - 15th October 2018 | |
- extracting discoveries made by commander into a CSV table; | |
- extracting visited stars into an ImportStars.txt file; | |
- automatically place an ImportStars.txt file in a proper directory. | |
Optional. | |
""" | |
__version__ = "1.0.1" | |
__author__ = "Arav" | |
__email__ = "me@arav.icu" | |
__copyright__ = f"Copyright © 2018 {__author__} <{__email__}>" | |
__license__ = """ | |
This program is free software. It comes without any warranty, to | |
the extent permitted by applicable law. You can redistribute it | |
and/or modify it under the terms of the Do What The Fuck You Want | |
To Public License, Version 2, as published by Sam Hocevar. See | |
http://www.wtfpl.net/ for more details. | |
""" | |
from argparse import ArgumentParser | |
from json import loads | |
from os import getenv, listdir, path | |
ED_JOURNAL_PATH = path.join(getenv("HOMEPATH"), \ | |
"Saved Games\\Frontier Developments\\Elite Dangerous") | |
ED_LOCAL_PATH = path.join(getenv("LOCALAPPDATA"), \ | |
"Frontier Developments\\Elite Dangerous") | |
TABLE_HEADER = [ | |
"Scan timestamp (GMT)", | |
"Sell timestamp (GMT)", | |
"Body name", | |
"Body type", | |
"Body class", | |
"Distance from arrival (Ls)"] | |
def sort_a_list_by_tuple_element(list_of_tuples, element_idx=0): | |
"""Sort a list of tuples by tuple's element of given index. | |
``list_of_tuples`` is a list of tuples to be sorted. | |
``element_idx`` is an index of tuple's element. | |
""" | |
return sorted(list_of_tuples, key=lambda x: x[element_idx]) | |
def get_visited_stars_cache_dir(): | |
"""Return a path to the directory contains a VistedStarsCache.dat file. | |
Returns None if this directory wasn't finded. | |
""" | |
for entry in listdir(ED_LOCAL_PATH): | |
entry = path.join(ED_LOCAL_PATH, entry) | |
if path.isdir(entry): | |
for file_ in listdir(path.join(ED_LOCAL_PATH, entry)): | |
if file_ == "VisitedStarsCache.dat": | |
return path.join(ED_LOCAL_PATH, entry) | |
return None | |
def csv_write(data, file_name, header=None, delimeter=";"): | |
"""Write a list of tuples into a file in CSV format. | |
``data`` is a list of tuples to be written. | |
``file_name`` is a name of an output file. | |
``header`` is an optional header for a table. | |
``delimeter`` is a symbol or a string used as a separator for table elements. | |
""" | |
if header and len(header) != len(data[0]): | |
raise ValueError("Lenght of a header (%i) and data row (%i) mismatch." \ | |
% (len(header), len(data[0]))) | |
with open(file_name, "w") as csv_f: | |
if header: | |
csv_f.write(delimeter.join(header) + "\n") | |
for entry in data: | |
csv_f.write(delimeter.join(map(str, entry)) + "\n") | |
def list_file_write(data, file_name): | |
"""Write a list into a file. | |
``data`` is a list to be written. | |
``file_name`` is a name of an output file. | |
""" | |
with open(file_name, "w") as f: | |
for entry in data: | |
f.write(entry + "\n") | |
def journal_entries(journal_path): | |
"""Return entries from journal files. The entries are deserialised from JSON | |
to Python object. | |
``path`` is the path to the directory that contains the journal files. Must | |
be absolute. | |
""" | |
for journal in listdir(journal_path): | |
if not journal.endswith('log'): | |
continue | |
journal = path.join(journal_path, journal) | |
for entry in open(journal): | |
yield loads(entry) | |
def extract_discoveries(journal_path): | |
"""Extract only bodies that were discovered by you. In form of list of | |
tuples. | |
Each tuple contains following data in this order: | |
scan timestamp | |
sell timestamp | |
body's name | |
body's type | |
body's class | |
Its distance from arrive in Ls (light seconds) | |
``journal_path`` is a path to the place where your E:D journal logs are. | |
""" | |
scans = dict() | |
discoveries = list() | |
for entry in journal_entries(journal_path): | |
if entry["event"] == "Scan": | |
scans[entry["BodyName"]] = ( | |
# scan timestamp #0 | |
entry["timestamp"].replace("T", " ").replace("Z", ""), | |
# body type #1 | |
"Planet" if "PlanetClass" in entry else "Star" \ | |
if "StarType" in entry else "Asteroid Belt", | |
# body class #2 | |
entry["PlanetClass"] if "PlanetClass" in entry \ | |
else entry["StarType"] if "StarType" in entry else "", | |
# body's distance from center of star system #3 | |
entry["DistanceFromArrivalLS"] | |
) | |
elif entry["event"] == "SellExplorationData": | |
for discovery in entry["Discovered"]: | |
discoveries.append(( | |
scans[discovery][0], | |
entry["timestamp"].replace("T", " ").replace("Z", ""), | |
discovery, | |
scans[discovery][1], | |
scans[discovery][2], | |
scans[discovery][3] | |
)) | |
del scans | |
return sort_a_list_by_tuple_element(discoveries) | |
def extract_visited_stars(journal_path): | |
"""Return a list of your visited stars. | |
``journal_path`` is a path to the place where your E:D journal logs are. | |
""" | |
visited_stars = list() | |
for entry in journal_entries(journal_path): | |
if entry["event"] == "FSDJump": | |
if not entry["StarSystem"] in visited_stars: | |
visited_stars.append(entry["StarSystem"]) | |
return visited_stars | |
def main(): | |
args_parser = ArgumentParser( | |
prog="ed-journal-extractor", | |
description="Tool for extraction information from E:D journal files. " \ | |
"This tool can extract your discoveries into a CSV table and " \ | |
"make an ImportStars.txt file with your visited stars. ", | |
epilog="A file ImportStars.txt goes into a numbered " \ | |
"directory in '%%localappdata%%\\Frontier Developments\\Elite " \ | |
"Dangerous\\' where a file VisitedStarsCache.dat is. The game will " \ | |
"import this list on the next restart.") | |
args_parser.add_argument("-v", "--version", action="store_true", | |
help="show version of the program") | |
args_parser.add_argument("-J", "--journal-dir", | |
help="specifies different directory to look for journals") | |
args_parser.add_argument("-D", "--discoveries-file", | |
help="specifies different name for discoveries output file") | |
args_parser.add_argument("-d", "--discoveries", action="store_true", | |
help="extract your discoveries into a CSV table. Default action.") | |
args_parser.add_argument("-s", "--visited-stars", action="store_true", | |
help="make an ImportStars.txt file") | |
args_parser.add_argument("-S", "--visited-stars-auto", action="store_true", | |
help="make an ImportStars.txt and place it into a proper directory") | |
args = args_parser.parse_args() | |
if args.version: | |
print("Elite: Dangerous Journal Extractor\n" \ | |
f"Version: {__version__}\n{__copyright__}\n{__license__}") | |
exit() | |
journal_path = args.journal_dir or ED_JOURNAL_PATH | |
discoveries_file = args.discoveries_file or "discoveries.csv" | |
if args.visited_stars: | |
try: | |
visited_stars = extract_visited_stars(journal_path) | |
list_file_write(visited_stars, "ImportStars.txt") | |
except Exception as ex: | |
print(f"Cannot make a list of visited stars. Error: {ex}") | |
elif args.visited_stars_auto: | |
path = get_visited_stars_cache_dir() | |
if not path: | |
print("VisitedStarsCache.dat's location not found. Saving in " \ | |
"working directory.") | |
path = "ImportStars.txt" | |
try: | |
visited_stars = extract_visited_stars(journal_path) | |
list_file_write(visited_stars, path) | |
except Exception as ex: | |
print(f"Cannot make a list of visited stars. Error: {ex}") | |
if args.discoveries or not args.visited_stars \ | |
or not args.visited_stars_auto: | |
try: | |
discoveries = extract_discoveries(journal_path) | |
csv_write(discoveries, discoveries_file, TABLE_HEADER) | |
except Exception as ex: | |
print(f"Cannot retrieve a list of your discoveries. Error: {ex}") | |
print(ex.with_traceback()) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment