spacex-cli/spacex-cli.js

405 lines
17 KiB
JavaScript
Raw Normal View History

2021-01-08 03:11:49 +08:00
#!/usr/bin/env node
2021-01-06 03:13:05 +08:00
const clc = require('cli-color');
const notifier = require('node-notifier');
2021-01-08 03:11:49 +08:00
const Diff = require('diff');
2021-01-06 03:13:05 +08:00
const path = require('path');
const isWsl = require('is-wsl');
2021-01-06 03:13:05 +08:00
const arguments = require('./tools/process-args').arguments;
2021-01-08 03:11:49 +08:00
const cli_elem = require('./tools/cli-elements'); //Idk js package structure/conventions...
2021-01-06 03:13:05 +08:00
const date_tools = require('./tools/date-tools');
const net_tools = require('./tools/net-tools');
const format_tools = require('./tools/format-tools');
const constants = require('./constants');
const COLOR = constants.GETCOLORS(clc);
const STRING = constants.GETSTRING(COLOR);
const CONSTANT_VALUES = constants.CONSTANT_VALUES;
var screen = cli_elem.screen;
var table_element = cli_elem.table_element;
2021-01-08 03:11:49 +08:00
var diff_table_element = cli_elem.diff_table_element;
var diff_json_element = cli_elem.diff_json_element;
2021-01-06 03:13:05 +08:00
var status_element = cli_elem.status_element;
var countdown_element = cli_elem.countdown_element;
var controls_element = cli_elem.controls_element;
var information_element = cli_elem.information_element;
const secondsHumanReadable = date_tools.secondsHumanReadable;
const getData = net_tools.getData;
const formatErr = net_tools.formatErr;
const prettyPrintData = format_tools.prettyPrintData;
// =====================================
//TODO: better viewer (than json and the crappy one I made using only string manipulation.)
process.env.FORCE_COLOR = true;
// const SCREEN_REFRESH = 1000; //ms
// const API_REFRESH = 10*60*1000; //ms
const SCREEN_REFRESH = arguments.screen_refresh; //ms
const API_REFRESH = arguments.api_refresh; //ms
const API_REFRESH_CYCLES = API_REFRESH/SCREEN_REFRESH;
const NONINTERACTIVE = arguments.dump;
2021-01-08 03:11:49 +08:00
const BLINK = arguments.blink;
const ARCHIVE = arguments.archive;
2021-01-06 03:13:05 +08:00
//
var notified_1h = [];
var notifying_1h = [];
var ICON_PATH = path.join(__dirname, 'spacex.ico');
if (isWsl && ICON_PATH.startsWith("/mnt/",0)) {
ICON_PATH = ICON_PATH.substr(5).replace("/",":/");
}
2021-01-08 03:11:49 +08:00
function selectData(arr) {
2021-01-06 03:13:05 +08:00
const LUNCHPADS_RESP = arr[0];
const LUNCHES_RESP = arr[1];
const ROCKETS_RESP = arr[2];
const PAYLOADS_RESP = arr[3];
const CORES_RESP = arr[4];
var ROCKETS = {};
ROCKETS_RESP.forEach(rocket => {
ROCKETS[rocket.id] = rocket.name
});
var LUNCHPADS = {};
LUNCHPADS_RESP.forEach(lunchpad => {
LUNCHPADS[lunchpad.id] = {
name: lunchpad.name,
region: lunchpad.region
}
});
var PAYLOADS = {}
PAYLOADS_RESP.forEach(payload => {
PAYLOADS[payload.id] = {
name: payload.name,
customers: payload.customers.join(', ')
}
});
var CORES = {};
CORES_RESP.forEach(core => {
CORES[core.id] = {
serial: core.serial,
reuse_count: core.reuse_count
}
});
var LUNCHES = LUNCHES_RESP.map(lunch => {
const lunchpad = LUNCHPADS[lunch.launchpad];
var precision = lunch.date_precision;
const t_s = lunch.date_unix;
const date_arr = new Date(t_s*1000).toIsoArr(precision);
var payload_names_str = '';
var payload_customers_str = '';
lunch.payloads.forEach(id => {
payload_names_str += (PAYLOADS[id].name + ' | ')
payload_customers_str += (PAYLOADS[id].customers + ' | ')
});
payload_names_str = payload_names_str.substr(0, payload_names_str.length-3)
payload_customers_str = payload_customers_str.substr(0, payload_customers_str.length-3)
var cores_str = "";
2021-01-08 03:11:49 +08:00
var cores_str_ = "";
2021-01-06 03:13:05 +08:00
lunch.cores.forEach(core_ => {
cores_str += CORES[core_["core"]] ? (CORES[core_["core"]]["serial"] + '(' + CORES[core_["core"]]["reuse_count"] + ') ') : STRING.CORE_UNASSIGNED + ' ';
2021-01-08 03:11:49 +08:00
cores_str_ += CORES[core_["core"]] ? (CORES[core_["core"]]["serial"] + '(' + CORES[core_["core"]]["reuse_count"] + ') ') : STRING.CORE_UNASSIGNED_ + ' ';
2021-01-06 03:13:05 +08:00
});
2021-01-08 03:11:49 +08:00
cores_str = cores_str.substr(0, cores_str.length-1);
cores_str_ = cores_str_.substr(0, cores_str_.length-1);
2021-01-06 03:13:05 +08:00
var time_style = {
hour: { p:"minutes", c: COLOR.TIME.LT1 },
day: { p:"hours", c: COLOR.TIME.LTD },
month: { p:"hours", c: COLOR.TIME.LTM },
quarter: { p:"days", c: COLOR.TIME.LTQ }, // use this symbol? ◔ ◑
half: { p:"days", c: COLOR.TIME.LTH }
}[precision]
const dt_s = t_s - new Date().getTime() / 1000;
2021-01-08 03:11:49 +08:00
const dt_str_ = secondsHumanReadable(dt_s,time_style.p);
var dt_str;
if (dt_s <= CONSTANT_VALUES.TIME_NOTIFY && precision === "hour" && !lunch.tbd && !lunch.net && !notified_1h.includes(lunch.flight_number)) { //Notify user.
2021-01-06 03:13:05 +08:00
notified_1h.push(lunch.flight_number)
notifying_1h.push(lunch.name)
}
2021-01-08 03:11:49 +08:00
if (dt_s <= CONSTANT_VALUES.TIME_BLINK && precision === "hour" && !lunch.tbd && !lunch.net) {
dt_str = COLOR.HUGE_SUCCESS("! "+(BLINK ? COLOR.BLINK(dt_str_) : dt_str_))+COLOR.HUGE_SUCCESS(" !");
2021-01-06 03:13:05 +08:00
date_h = COLOR.HUGE_SUCCESS(date_arr.bw.join(''));
} else if (dt_s < 0) {
2021-01-08 03:11:49 +08:00
dt_str = COLOR.INVALID(dt_str_);
2021-01-06 03:13:05 +08:00
date_h = COLOR.INVALID(date_arr.bw.join(''))+'{/}';
} else {
2021-01-08 03:11:49 +08:00
dt_str = time_style.c(dt_str_)
2021-01-06 03:13:05 +08:00
date_h = date_arr.col.join('');
}
2021-01-08 03:11:49 +08:00
const precision_ = precision;
2021-01-06 03:13:05 +08:00
precision = time_style.c(precision)
return {
name: lunch.name,
2021-01-08 03:11:49 +08:00
flight_number: String(lunch.flight_number),
2021-01-06 03:13:05 +08:00
date_precision: precision,
2021-01-08 03:11:49 +08:00
date_precision_:precision_,
2021-01-06 03:13:05 +08:00
tbd: lunch.tbd,
net: lunch.net,
date_h: date_h,
2021-01-08 03:11:49 +08:00
date_h_: date_arr.bw.join(''),
2021-01-06 03:13:05 +08:00
rocket: ROCKETS[lunch.rocket],
cores: cores_str,
2021-01-08 03:11:49 +08:00
cores_: cores_str_,
2021-01-06 03:13:05 +08:00
launchpad: lunchpad.name,
launchpad_reg: lunchpad.region,
dt: dt_str,
2021-01-08 03:11:49 +08:00
dt_: dt_str_,
2021-01-06 03:13:05 +08:00
payloads: {
names_str: payload_names_str,
customers_str: payload_customers_str
}
}
});
2021-01-08 03:11:49 +08:00
return LUNCHES;
};
2021-01-06 03:13:05 +08:00
2021-01-08 03:11:49 +08:00
async function formatData(arr) {
return format_tools.tabularizeData(selectData(arr));
};
2021-01-06 03:13:05 +08:00
2021-01-08 03:11:49 +08:00
var diff_table_content;
var diff_json_content;
2021-01-06 03:13:05 +08:00
2021-01-08 03:11:49 +08:00
var newData = false;
2021-01-06 03:13:05 +08:00
var json_view = false;
//Keep these keybind listeners in main // Quit on Escape, q, or Control-C.
screen.key(['escape', 'q', '', 'C-c', 'left'], (ch, key) => {
2021-01-08 03:11:49 +08:00
const name = screen.focused.name;
if (name === "table" && key.name !== 'left') {
2021-01-06 03:13:05 +08:00
console.log('\033[?25h');
return process.exit(0);
2021-01-08 03:11:49 +08:00
} else if (name === "detailed_information" || name === "diff_table" || name === "diff_json") {
if (name === "diff_table" || name === "diff_json") {
newData = false;
};
2021-01-06 03:13:05 +08:00
table_element.setFront();
table_element.focus();
controls_element.setContent(STRING.CONTROLS.TABLE);
screen.render();
};
});
screen.key(['j'], (ch, key) => {
2021-01-08 03:11:49 +08:00
const name = screen.focused.name;
if (name === "detailed_information") {
json_view = (json_view ? false : true);
information_element.setData(prettyPrintData(data_cache, idx, json_view));
} else if (name === "diff_table") {
json_view = true;
diff_json_element.setFront();
diff_json_element.focus();
diff_json_element.enableInput();
} else if (name === "diff_json") {
json_view = false;
diff_table_element.setFront();
diff_table_element.focus();
diff_table_element.enableInput();
};
screen.render();
});
screen.key(['d'], (ch, key) => {
const name = screen.focused.name;
if (name !== "diff_table" && name !== "diff_json") {
// console.log(diff_table_content);
// process.kill(process.pid)
controls_element.setContent(STRING.CONTROLS.INFORMATION);
if (json_view) {
diff_json_element.setFront();
diff_json_element.focus();
diff_json_element.enableInput();
} else {
diff_table_element.setFront();
diff_table_element.focus();
diff_table_element.enableInput();
};
} else {
table_element.setFront();
table_element.focus();
controls_element.setContent(STRING.CONTROLS.TABLE);
};
2021-01-06 03:13:05 +08:00
screen.render();
});
screen.key(['enter', 'right'], (ch, key) => {
2021-01-08 03:11:49 +08:00
const name = screen.focused.name;
if (name === "table") {
2021-01-06 03:13:05 +08:00
idx = table_element.getScroll()-2;
if (idx >= 0) {
status_element.setContent(String(data_cache[1][idx].name));
information_element.setFront();
information_element.focus();
information_element.enableInput();
information_element.setData(prettyPrintData(data_cache, idx, json_view));
controls_element.setContent(STRING.CONTROLS.INFORMATION);
};
2021-01-08 03:11:49 +08:00
} else if (key.name === 'enter' && (name === "detailed_information" || name === "diff_table" || name === "diff_json")) {
if (name === "diff_table" || name === "diff_json") {
newData = false;
};
2021-01-06 03:13:05 +08:00
table_element.setFront();
table_element.focus();
controls_element.setContent(STRING.CONTROLS.TABLE);
};
screen.render();
});
screen.key(['r'], (ch, key) => {
api_counter = 1;
});
var data_cache;
2021-01-08 03:11:49 +08:00
var checkDiff = true;
var firstRun = true;
var lastDelta = '';
2021-01-06 03:13:05 +08:00
var api_counter = 1;
(async function(){
// data_cache = await getData()
// const TABLE = await formatData(await data_cache);
// table_element.setData(TABLE);
table_element.select(1);
status_element.setContent(STRING.DOWNLOADING);
countdown_element.setContent(STRING.DOWNLOADING_STAR);
2021-01-08 03:11:49 +08:00
diff_table_element.setData(diff_table_content ? diff_table_content : [['diff_table_content']]);
2021-01-06 03:13:05 +08:00
screen.render();
})()
async function main() {
api_counter--;
2021-01-08 03:11:49 +08:00
if (api_counter === 0) {
checkDiff = true;
2021-01-06 03:13:05 +08:00
status_element.setContent(STRING.DOWNLOADING);
countdown_element.setContent(STRING.DOWNLOADING_STAR);
screen.render();
api_counter = API_REFRESH_CYCLES;
data_cache = await getData();
}
if (data_cache) {
var err_message = await formatErr(await data_cache);
2021-01-08 03:11:49 +08:00
if (err_message === true) {
2021-01-06 03:13:05 +08:00
const TABLE = await formatData(await data_cache);
const SCROLL = table_element.getScroll();
table_element.setData(TABLE);
table_element.select(SCROLL);
2021-01-08 03:11:49 +08:00
err_message = [( (data_cache[1].length - (table_element.height - 4)) > 0 ? STRING.OK_SCROLL : STRING.OK_GENERIC) + (newData ? ' | '+COLOR.WARNING(STRING.NEW_DATA) : '')+' '+lastDelta];
};
if (notifying_1h.length > 0) {
notifier.notify({
title: STRING.H_WARNING,
message: notifying_1h.join(' │ '),
icon: ICON_PATH,
2021-01-08 03:11:49 +08:00
appID: STRING.APPID
});
notifying_1h = [];
};
2021-01-06 03:13:05 +08:00
if (NONINTERACTIVE) {
screen.remove(status_element);
screen.remove(countdown_element);
screen.remove(controls_element);
const snip = 2; //Add offset incase you want to add in a line in a bash script (Press any key to continue thing.)
table_element.height = screen.height-snip;
screen.render();
const screenshot = screen.screenshot().split('\n');
screen.destroy();
console.log('\x1b[H\x1b[J'+screenshot.slice(0,screenshot.length-snip-1).join('\n'));
process.exit(0);
}
status_element.setContent(err_message[api_counter%err_message.length]);
countdown_element.setContent("Next update in "+secondsHumanReadable(api_counter*SCREEN_REFRESH/1000));
2021-01-08 03:11:49 +08:00
2021-01-06 03:13:05 +08:00
2021-01-08 03:11:49 +08:00
if (checkDiff) {
checkDiff = false;
const path_prev_launches = path.join(CONSTANT_VALUES.DATA_PATH,'previous_data.json');
const path_prev_launches_table = path.join(CONSTANT_VALUES.DATA_PATH,'previous_data_table.json');
2021-01-06 03:13:05 +08:00
2021-01-08 03:11:49 +08:00
const curr_launches = data_cache[1];
var prev_launches = net_tools.readFile(path_prev_launches);
prev_launches = prev_launches !== '' ? JSON.parse(prev_launches) : [];
diff_json_content = [[STRING.HEADERS.JSON],['']];
// jsonDiff.diffString(prev_launches, curr_launches)
// .split('\n')
// .forEach(u => {diff_json_content.push([clc.cyan(u)])});
const json_diff_data = Diff.diffJson(prev_launches, curr_launches)
const changed = json_diff_data.some(chunk => {
return chunk.added || chunk.removed;
});
2021-01-06 03:13:05 +08:00
2021-01-08 03:11:49 +08:00
if (changed || firstRun) {
firstRun = false;
json_diff_data.forEach(chunk => {
if (chunk.added && chunk.removed) {//Not sure if this case is possible.
chunk.value.split('\\n').forEach(line => {[diff_json_content.push([COLOR.WARNING(STRING.DIFF.BOTH_SYMBOL+'│ '+line)])]});
} else if (chunk.added) {
chunk.value.split('\\n').forEach(line => {[diff_json_content.push([COLOR.SUCCESS(STRING.DIFF.CURRENT_SYMBOL+'│ '+line)])]});
} else if (chunk.removed) {
chunk.value.split('\\n').forEach(line => {[diff_json_content.push([COLOR.DANGER(STRING.DIFF.PREVIOUS_SYMBOL+'│ '+line)])]});
} else {
chunk.value.replace(/\n/gm,"_NEWLINE_") //Why the frick doesn't my regexp work when it works in a tester???? Using this workaround.
.replace(/_NEWLINE_\s\s\{(.*?)_NEWLINE_\s\s\},/gm, '_NEWLINE_ [...]')
.replace(/_NEWLINE_\s\s\{(.*?)_NEWLINE_]/m, '_NEWLINE_ [...]_NEWLINE_]')
.split('_NEWLINE_')
.forEach(line => {line !== '' ? [diff_json_content.push([COLOR.GENERIC(STRING.DIFF.UNCHANGED_SYMBOL_+'│ '+line)])] : null}); //Trailing newline results in a empty line - need to filter it out.
};
});
diff_json_content.push([''])
2021-01-06 03:13:05 +08:00
2021-01-08 03:11:49 +08:00
// console.log(json_diff_data);
// process.kill(process.pid)
// JSON.stringify((json_diff_data ? json_diff_data : ''),null,2)
// .split('\n')
// .forEach(u => {diff_json_content.push([clc.cyan(u)])});
var prev_launches_table = net_tools.readFile(path_prev_launches_table);
const curr_launches_table = format_tools.tabularizeDiffData(await selectData(await data_cache));
prev_launches_table = prev_launches_table !== '' ? JSON.parse(prev_launches_table) : Array(curr_launches_table.length).fill(Array(curr_launches_table[0].length).fill(''));
diff_table_content = format_tools.diffTable(prev_launches_table,curr_launches_table)
diff_table_element.setData(diff_table_content ? diff_table_content : [['diff_table_content']]);
diff_json_element.setData(diff_json_content ? diff_json_content : [['diff_json_content']]);
if (changed) {
const dateStr = new Date().toIsoArr().bw.join('');
lastDelta = '| '+STRING.LAST_DELTA+COLOR.GENERIC(dateStr);
newData = true;
notifier.notify({
title: STRING.NEW_DATA,
message: 'Press d to see table diff. Press j to see JSON diff.',
icon: ICON_PATH,
2021-01-08 03:11:49 +08:00
appID: STRING.APPID
});
net_tools.writeFile(path_prev_launches, JSON.stringify(curr_launches));
net_tools.writeFile(path_prev_launches_table, JSON.stringify(curr_launches_table));
if (ARCHIVE) {
net_tools.writeFile(
path.join(CONSTANT_VALUES.DATA_PATH,'/archive/'+dateStr.replace(/:/g,'_').replace(' ','_')+'.json'),
JSON.stringify(curr_launches)
);
net_tools.writeFile(
path.join(CONSTANT_VALUES.DATA_PATH,'/archive/'+dateStr.replace(/:/g,'_').replace(' ','_')+'_table.json'),
JSON.stringify(curr_launches_table)
);
};
};
};
};
};
screen.render();
};
console.log('\033[?25l'); //ANSI code - hide cursor
setInterval(main, SCREEN_REFRESH);