mirror of
https://github.com/peter-tanner/spacex-cli.git
synced 2024-11-30 11:00:15 +08:00
SpaceX-cli version 1.0 :D
This commit is contained in:
parent
b7a3d5b703
commit
9158e60476
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
~/
|
||||
etc/
|
||||
node_modules/
|
||||
#trademark reasons.
|
||||
|
|
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# SpaceX-cli
|
||||
## View upcoming launches in the terminal
|
||||
|
||||
## Write a readme for this later...
|
||||
|
||||
`npm install -g npc-strider/spacex-cli`
|
||||
|
||||
This is an enhanced version of a basic CLI bash script I was using.
|
||||
|
||||
Because the bash script was based on the v3 [spacexdata](https://github.com/r-spacex/SpaceX-API) api, I was forced to upgrade it to use the new v4 api.
|
||||
|
||||
So I decided to not just upgrade the bash script to use the new api, but also add some new features and more interactibility.
|
||||
|
||||
The cli isn't pretty like some others, but I think it's quite dense in relevant information.
|
||||
|
||||
*I'm not including space.ico in my releases to prevent trademark infringement :/ sorry.
|
66
constants.js
66
constants.js
|
@ -1,11 +1,16 @@
|
|||
const os = require('os');
|
||||
const clc = require('cli-color');
|
||||
|
||||
const arguments = require('./tools/process-args').arguments;
|
||||
|
||||
if (!arguments.dump) {
|
||||
if (arguments.color) {
|
||||
this.STYLES = {scrollbar: {bg: 'blue'}}
|
||||
this.STYLES = {scrollbar: {bg: 'blue'}};
|
||||
} else {
|
||||
this.STYLES = {scrollbar: {bg: 'white'}}
|
||||
this.STYLES = {scrollbar: {bg: 'white'}};
|
||||
};
|
||||
} else {
|
||||
this.STYLES = {scrollbar: null};
|
||||
};
|
||||
|
||||
const override_deep = (o,v) => {
|
||||
|
@ -20,14 +25,34 @@ const override_deep = (o,v) => {
|
|||
return o_
|
||||
};
|
||||
|
||||
this.CONTROL_MAX_WIDTH = 0;
|
||||
const registerKeys = ((keys, actions) => {
|
||||
var strArr = [];
|
||||
for (i = 0; i < keys.length; ++i) {
|
||||
if (arguments.color) {
|
||||
strArr.push(keys[i]+' '+clc.underline(actions[i]));
|
||||
} else {
|
||||
strArr.push(keys[i]+' '+actions[i]);
|
||||
};
|
||||
};
|
||||
const str = strArr.join(' | ');
|
||||
const d = arguments.color ? 9 : 0;
|
||||
if (str.length - i*d > this.CONTROL_MAX_WIDTH) {
|
||||
this.CONTROL_MAX_WIDTH = str.length - i*d;
|
||||
};
|
||||
return str;
|
||||
});
|
||||
|
||||
this.CONSTANT_VALUES = {
|
||||
TIME_NOTIFY: 60*60*24*7, //FIXME: TESTING VALUES - Change to 60*90
|
||||
TIME_BLINK: 60*60*24*7 //FIXME: TESTING VALUES - Change to 60*60*24
|
||||
TIME_NOTIFY: arguments.notify_time,//60*60*24*7,
|
||||
TIME_BLINK: arguments.highlight_time,
|
||||
DATA_PATH: arguments.path.replace('~',os.homedir),
|
||||
}
|
||||
|
||||
this.GETCOLORS = ((clc_) => {
|
||||
var COLOR = {
|
||||
NONE: ((t) => {return t}),
|
||||
BLINK: clc_.blink,
|
||||
GENERIC: clc_.cyan,
|
||||
SUCCESS: clc_.green,
|
||||
HUGE_SUCCESS: clc_.green.bold,
|
||||
|
@ -35,14 +60,14 @@ this.GETCOLORS = ((clc_) => {
|
|||
DANGER: clc_.red.bold,
|
||||
INVALID: clc_.blackBright.bold,
|
||||
PROGRESS: clc_.yellow,
|
||||
HEADER: clc_.underline,
|
||||
HEADER: clc_.magentaBright.underline,
|
||||
TIME: {
|
||||
LT1: clc_.green,
|
||||
LTD: clc_.green,
|
||||
LTM: clc_.yellow,
|
||||
LTQ: clc_.blackBright.bold,
|
||||
LTH: clc_.blackBright.bold
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (!arguments.color) {
|
||||
|
@ -62,28 +87,49 @@ this.GETSTRING = ((COLOR_) => {
|
|||
PRECISION: COLOR_.HEADER("Precision"),
|
||||
FLAGS: COLOR_.HEADER("Flags"),
|
||||
ROCKET: COLOR_.HEADER("Rocket type"),
|
||||
CORE: COLOR_.HEADER("Core (№. reused)"),
|
||||
CORE: COLOR_.HEADER("Core(№ of reuses)"),
|
||||
LAUNCHPAD: COLOR_.HEADER("Launchpad"),
|
||||
LAUNCHPAD_REG: COLOR_.HEADER("Launch region"),
|
||||
PAYLOAD_NAME: COLOR_.HEADER("Payloads"),
|
||||
PAYLOAD_CUSTOMERS: COLOR_.HEADER("Payload customers"),
|
||||
|
||||
JSON: COLOR_.HEADER('Key/Value'),
|
||||
KEY: COLOR_.HEADER('Property'),
|
||||
VALUE: COLOR_.HEADER('Value'),
|
||||
},
|
||||
CONTROLS: {
|
||||
TABLE: '↑↓ Scroll and select | ↵ → Select launch | r Refresh | q Quit',
|
||||
INFORMATION: '↑↓ Scroll | q ↵ ← Return to table | j Toggle JSON view'
|
||||
TABLE: registerKeys(['↑↓', '↵ →', 'r', 'q', 'd' ],
|
||||
['Scroll and select', 'Select launch', 'Refresh', 'Quit', 'Diff']),
|
||||
INFORMATION: registerKeys(['↑↓', 'q ↵ ←', 'j' ],
|
||||
['Scroll', 'Return to table', 'Toggle JSON view']),
|
||||
DIFF: registerKeys(['↑↓', 'q ↵ ←' ],
|
||||
['Scroll', 'Return to table'])
|
||||
},
|
||||
|
||||
CORE_UNASSIGNED: COLOR_.INVALID("Unknown"),
|
||||
CORE_UNASSIGNED_: "Unknown",
|
||||
|
||||
DOWNLOADING: COLOR_.PROGRESS("Downloading new data from spacex api . . .",),
|
||||
DOWNLOADING_STAR: COLOR_.PROGRESS("*"),
|
||||
|
||||
OK_GENERIC: COLOR_.SUCCESS("OK."),
|
||||
OK_SCROLL: COLOR_.SUCCESS("OK. [!] Warning: Scroll down to view more"),
|
||||
OK_SCROLL: COLOR_.SUCCESS("OK. [!] Scroll to view more"),
|
||||
LAST_DELTA: COLOR_.GENERIC('Last Δ: '),
|
||||
|
||||
H_WARNING: "SpaceX launches imminent!",
|
||||
NEW_DATA: "New SpaceX data downloaded!",
|
||||
|
||||
APPID: 'spacex-cli',
|
||||
SCREEN_TITLE: 'Spacex - Upcoming lunches',
|
||||
|
||||
DIFF: {
|
||||
PREVIOUS: 'prev',
|
||||
CURRENT: 'curr',
|
||||
PREVIOUS_SYMBOL: '[-]',
|
||||
CURRENT_SYMBOL: '[+]',
|
||||
BOTH_SYMBOL: '[±]',
|
||||
UNCHANGED_SYMBOL: '[=]',
|
||||
UNCHANGED_SYMBOL_: ' '
|
||||
}
|
||||
};
|
||||
})
|
5
package-lock.json
generated
5
package-lock.json
generated
|
@ -31,6 +31,11 @@
|
|||
"type": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
|
||||
"integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w=="
|
||||
},
|
||||
"es5-ext": {
|
||||
"version": "0.10.53",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
{
|
||||
"name": "spacex-cli",
|
||||
"version": "0.1.0",
|
||||
"version": "1.0.0",
|
||||
"description": "SpaceX upcoming launch tracker",
|
||||
"main": "spacex-cli.js",
|
||||
"scripts": {
|
||||
"test": "node spacex-cli"
|
||||
},
|
||||
"bin": {
|
||||
"spacex-cli": "spacex-cli"
|
||||
},
|
||||
"keywords": [
|
||||
"spacex",
|
||||
"cli"
|
||||
|
@ -14,6 +17,7 @@
|
|||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"cli-color": "^2.0.0",
|
||||
"diff": "^5.0.0",
|
||||
"mri": "^1.1.6",
|
||||
"neo-blessed": "^0.2.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
|
231
spacex-cli.js
231
spacex-cli.js
|
@ -1,10 +1,11 @@
|
|||
#!/usr/bin/env node
|
||||
const clc = require('cli-color');
|
||||
const notifier = require('node-notifier');
|
||||
const blessed = require('neo-blessed');
|
||||
const Diff = require('diff');
|
||||
const path = require('path');
|
||||
|
||||
const arguments = require('./tools/process-args').arguments;
|
||||
const cli_elem = require('./tools/cli-elements');
|
||||
const cli_elem = require('./tools/cli-elements'); //Idk js package structure/conventions...
|
||||
const date_tools = require('./tools/date-tools');
|
||||
const net_tools = require('./tools/net-tools');
|
||||
const format_tools = require('./tools/format-tools');
|
||||
|
@ -16,6 +17,8 @@ const CONSTANT_VALUES = constants.CONSTANT_VALUES;
|
|||
|
||||
var screen = cli_elem.screen;
|
||||
var table_element = cli_elem.table_element;
|
||||
var diff_table_element = cli_elem.diff_table_element;
|
||||
var diff_json_element = cli_elem.diff_json_element;
|
||||
var status_element = cli_elem.status_element;
|
||||
var countdown_element = cli_elem.countdown_element;
|
||||
var controls_element = cli_elem.controls_element;
|
||||
|
@ -30,7 +33,6 @@ const prettyPrintData = format_tools.prettyPrintData;
|
|||
|
||||
// =====================================
|
||||
|
||||
//TODO: archive data option
|
||||
//TODO: better viewer (than json and the crappy one I made using only string manipulation.)
|
||||
|
||||
process.env.FORCE_COLOR = true;
|
||||
|
@ -41,12 +43,14 @@ 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;
|
||||
const BLINK = arguments.blink;
|
||||
const ARCHIVE = arguments.archive;
|
||||
//
|
||||
|
||||
var notified_1h = [];
|
||||
var notifying_1h = [];
|
||||
|
||||
async function formatData(arr) {
|
||||
function selectData(arr) {
|
||||
const LUNCHPADS_RESP = arr[0];
|
||||
const LUNCHES_RESP = arr[1];
|
||||
const ROCKETS_RESP = arr[2];
|
||||
|
@ -95,10 +99,13 @@ async function formatData(arr) {
|
|||
payload_customers_str = payload_customers_str.substr(0, payload_customers_str.length-3)
|
||||
|
||||
var cores_str = "";
|
||||
var cores_str_ = "";
|
||||
lunch.cores.forEach(core_ => {
|
||||
cores_str += CORES[core_["core"]] ? (CORES[core_["core"]]["serial"] + '(' + CORES[core_["core"]]["reuse_count"] + ') ') : STRING.CORE_UNASSIGNED + ' ';
|
||||
cores_str_ += CORES[core_["core"]] ? (CORES[core_["core"]]["serial"] + '(' + CORES[core_["core"]]["reuse_count"] + ') ') : STRING.CORE_UNASSIGNED_ + ' ';
|
||||
});
|
||||
cores_str = cores_str.substr(0, cores_str.length-1)
|
||||
cores_str = cores_str.substr(0, cores_str.length-1);
|
||||
cores_str_ = cores_str_.substr(0, cores_str_.length-1);
|
||||
|
||||
var time_style = {
|
||||
hour: { p:"minutes", c: COLOR.TIME.LT1 },
|
||||
|
@ -108,82 +115,70 @@ async function formatData(arr) {
|
|||
half: { p:"days", c: COLOR.TIME.LTH }
|
||||
}[precision]
|
||||
const dt_s = t_s - new Date().getTime() / 1000;
|
||||
var dt_str = secondsHumanReadable(dt_s,time_style.p);
|
||||
if (dt_s <= CONSTANT_VALUES.TIME_NOTIFY && precision == "hour" && !lunch.tbd && !lunch.net && !notified_1h.includes(lunch.flight_number)) { //Notify user.
|
||||
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.
|
||||
notified_1h.push(lunch.flight_number)
|
||||
notifying_1h.push(lunch.name)
|
||||
}
|
||||
|
||||
if (dt_s <= CONSTANT_VALUES.TIME_BLINK && precision == "hour" && !lunch.tbd && !lunch.net) {
|
||||
dt_str = COLOR.HUGE_SUCCESS("! "+clc.blink(dt_str))+COLOR.HUGE_SUCCESS(" !");
|
||||
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(" !");
|
||||
date_h = COLOR.HUGE_SUCCESS(date_arr.bw.join(''));
|
||||
} else if (dt_s < 0) {
|
||||
dt_str = COLOR.INVALID(dt_str);
|
||||
dt_str = COLOR.INVALID(dt_str_);
|
||||
date_h = COLOR.INVALID(date_arr.bw.join(''))+'{/}';
|
||||
} else {
|
||||
dt_str = time_style.c(dt_str)
|
||||
dt_str = time_style.c(dt_str_)
|
||||
date_h = date_arr.col.join('');
|
||||
}
|
||||
const precision_ = precision;
|
||||
precision = time_style.c(precision)
|
||||
|
||||
return {
|
||||
name: lunch.name,
|
||||
flight_number: lunch.flight_number,
|
||||
flight_number: String(lunch.flight_number),
|
||||
date_precision: precision,
|
||||
date_precision_:precision_,
|
||||
tbd: lunch.tbd,
|
||||
net: lunch.net,
|
||||
date_h: date_h,
|
||||
date_h_: date_arr.bw.join(''),
|
||||
rocket: ROCKETS[lunch.rocket],
|
||||
cores: cores_str,
|
||||
cores_: cores_str_,
|
||||
launchpad: lunchpad.name,
|
||||
launchpad_reg: lunchpad.region,
|
||||
dt: dt_str,
|
||||
dt_: dt_str_,
|
||||
payloads: {
|
||||
names_str: payload_names_str,
|
||||
customers_str: payload_customers_str
|
||||
}
|
||||
}
|
||||
});
|
||||
return LUNCHES;
|
||||
};
|
||||
|
||||
async function formatData(arr) {
|
||||
return format_tools.tabularizeData(selectData(arr));
|
||||
};
|
||||
|
||||
LUNCHES = LUNCHES.map(lunch => [
|
||||
COLOR.GENERIC(lunch.flight_number),
|
||||
COLOR.GENERIC(lunch.name),
|
||||
lunch.date_h,
|
||||
lunch.dt,
|
||||
String(lunch.date_precision),
|
||||
COLOR.DANGER((lunch.tbd ? "tbd" : "")+(lunch.net && lunch.tbd ? ", " : "")+(lunch.net ? "net" : "")),
|
||||
COLOR.GENERIC(lunch.rocket),
|
||||
COLOR.GENERIC(lunch.cores),
|
||||
COLOR.GENERIC(lunch.launchpad),
|
||||
// lunch.launchpad_reg
|
||||
COLOR.GENERIC(lunch.payloads.names_str),
|
||||
COLOR.GENERIC(lunch.payloads.customers_str)
|
||||
])
|
||||
LUNCHES.unshift([
|
||||
STRING.HEADERS.FLIGHT_NUMBER,
|
||||
STRING.HEADERS.NAME,
|
||||
STRING.HEADERS.DATE_H,
|
||||
STRING.HEADERS.DT,
|
||||
STRING.HEADERS.PRECISION,
|
||||
STRING.HEADERS.FLAGS,
|
||||
STRING.HEADERS.ROCKET,
|
||||
STRING.HEADERS.CORE,
|
||||
STRING.HEADERS.LAUNCHPAD,
|
||||
// STRING.HEADERS.LAUNCHPAD_REG
|
||||
STRING.HEADERS.PAYLOAD_NAME,
|
||||
STRING.HEADERS.PAYLOAD_CUSTOMERS,
|
||||
],[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' '])
|
||||
return LUNCHES
|
||||
}
|
||||
var diff_table_content;
|
||||
var diff_json_content;
|
||||
|
||||
var newData = false;
|
||||
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) => {
|
||||
if (screen.focused.name == "table" && key.name != 'left') {
|
||||
const name = screen.focused.name;
|
||||
if (name === "table" && key.name !== 'left') {
|
||||
console.log('\033[?25h');
|
||||
return process.exit(0);
|
||||
} else if (screen.focused.name == "detailed_information") {
|
||||
} else if (name === "detailed_information" || name === "diff_table" || name === "diff_json") {
|
||||
if (name === "diff_table" || name === "diff_json") {
|
||||
newData = false;
|
||||
};
|
||||
table_element.setFront();
|
||||
table_element.focus();
|
||||
controls_element.setContent(STRING.CONTROLS.TABLE);
|
||||
|
@ -191,12 +186,48 @@ screen.key(['escape', 'q', '', 'C-c', 'left'], (ch, key) => {
|
|||
};
|
||||
});
|
||||
screen.key(['j'], (ch, key) => {
|
||||
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);
|
||||
};
|
||||
screen.render();
|
||||
});
|
||||
screen.key(['enter', 'right'], (ch, key) => {
|
||||
if (screen.focused.name == "table") {
|
||||
const name = screen.focused.name;
|
||||
if (name === "table") {
|
||||
idx = table_element.getScroll()-2;
|
||||
if (idx >= 0) {
|
||||
status_element.setContent(String(data_cache[1][idx].name));
|
||||
|
@ -206,7 +237,10 @@ screen.key(['enter', 'right'], (ch, key) => {
|
|||
information_element.setData(prettyPrintData(data_cache, idx, json_view));
|
||||
controls_element.setContent(STRING.CONTROLS.INFORMATION);
|
||||
};
|
||||
} else if (screen.focused.name == "detailed_information") {
|
||||
} else if (key.name === 'enter' && (name === "detailed_information" || name === "diff_table" || name === "diff_json")) {
|
||||
if (name === "diff_table" || name === "diff_json") {
|
||||
newData = false;
|
||||
};
|
||||
table_element.setFront();
|
||||
table_element.focus();
|
||||
controls_element.setContent(STRING.CONTROLS.TABLE);
|
||||
|
@ -218,6 +252,9 @@ screen.key(['r'], (ch, key) => {
|
|||
});
|
||||
|
||||
var data_cache;
|
||||
var checkDiff = true;
|
||||
var firstRun = true;
|
||||
var lastDelta = '';
|
||||
var api_counter = 1;
|
||||
|
||||
(async function(){
|
||||
|
@ -227,13 +264,16 @@ var api_counter = 1;
|
|||
table_element.select(1);
|
||||
status_element.setContent(STRING.DOWNLOADING);
|
||||
countdown_element.setContent(STRING.DOWNLOADING_STAR);
|
||||
diff_table_element.setData(diff_table_content ? diff_table_content : [['diff_table_content']]);
|
||||
|
||||
screen.render();
|
||||
})()
|
||||
|
||||
|
||||
async function main() {
|
||||
api_counter--;
|
||||
if (api_counter == 0) {
|
||||
if (api_counter === 0) {
|
||||
checkDiff = true;
|
||||
status_element.setContent(STRING.DOWNLOADING);
|
||||
countdown_element.setContent(STRING.DOWNLOADING_STAR);
|
||||
screen.render();
|
||||
|
@ -242,12 +282,13 @@ async function main() {
|
|||
}
|
||||
if (data_cache) {
|
||||
var err_message = await formatErr(await data_cache);
|
||||
if (err_message == true) {
|
||||
if (err_message === true) {
|
||||
const TABLE = await formatData(await data_cache);
|
||||
const SCROLL = table_element.getScroll();
|
||||
table_element.setData(TABLE);
|
||||
table_element.select(SCROLL);
|
||||
err_message = [( (data_cache[1].length - (table_element.height - 4)) > 0 ? STRING.OK_SCROLL : STRING.OK_GENERIC)];
|
||||
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,
|
||||
|
@ -256,8 +297,7 @@ async function main() {
|
|||
appID: STRING.APPID
|
||||
});
|
||||
notifying_1h = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
if (NONINTERACTIVE) {
|
||||
screen.remove(status_element);
|
||||
screen.remove(countdown_element);
|
||||
|
@ -272,17 +312,88 @@ async function main() {
|
|||
}
|
||||
status_element.setContent(err_message[api_counter%err_message.length]);
|
||||
countdown_element.setContent("Next update in "+secondsHumanReadable(api_counter*SCREEN_REFRESH/1000));
|
||||
}
|
||||
|
||||
|
||||
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');
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
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([''])
|
||||
|
||||
// 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: path.join(__dirname, 'spacex.ico'),
|
||||
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);
|
||||
|
||||
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
|
@ -8,7 +8,7 @@ const COLOR = constants.GETCOLORS(clc);
|
|||
const STRING = constants.GETSTRING(COLOR);
|
||||
const STYLES = constants.STYLES;
|
||||
|
||||
const control_element_width = STRING.CONTROLS.TABLE.length+8;
|
||||
const control_element_width = constants.CONTROL_MAX_WIDTH+8;
|
||||
|
||||
this.screen = blessed.screen({
|
||||
smartCSR: true,
|
||||
|
@ -32,9 +32,9 @@ this.table_element = blessed.listtable({
|
|||
style: {
|
||||
scrollbar: STYLES.scrollbar
|
||||
},
|
||||
scrollbar: true,
|
||||
scrollbar: !arguments.dump,
|
||||
invertSelected: true,
|
||||
})
|
||||
});
|
||||
|
||||
this.status_element = blessed.box({
|
||||
position : {
|
||||
|
@ -102,10 +102,47 @@ this.information_element = blessed.listtable({//blessed.box({
|
|||
name: 'detailed_information'
|
||||
});
|
||||
|
||||
|
||||
this.diff_json_element = blessed.listtable({//blessed.box({
|
||||
align: 'left',
|
||||
position: {
|
||||
width: '100%',
|
||||
height: this.screen.height-2,
|
||||
},
|
||||
style: {
|
||||
scrollbar: STYLES.scrollbar
|
||||
},
|
||||
tags: true,
|
||||
scrollbar: true,
|
||||
scrollable: true,
|
||||
border: 'line',
|
||||
keys: true,
|
||||
parent: this.screen,
|
||||
invertSelected: true,
|
||||
name: 'diff_json'
|
||||
});
|
||||
|
||||
this.diff_table_element = blessed.listtable({
|
||||
parent: this.screen,
|
||||
border: 'line',
|
||||
align: 'center',
|
||||
keys: true,
|
||||
width: "100%",
|
||||
height: this.screen.height-2,
|
||||
name: 'diff_table',
|
||||
tags: true,
|
||||
style: {
|
||||
scrollbar: STYLES.scrollbar
|
||||
},
|
||||
scrollbar: true,
|
||||
invertSelected: true,
|
||||
});
|
||||
|
||||
this.screen.append(this.status_element);
|
||||
this.screen.append(this.countdown_element);
|
||||
this.screen.append(this.controls_element);
|
||||
this.screen.append(this.information_element);
|
||||
this.screen.append(this.diff_table_element);
|
||||
this.screen.append(this.table_element);
|
||||
this.table_element.setFront();
|
||||
this.table_element.focus();
|
||||
|
|
|
@ -27,7 +27,7 @@ Date.prototype.toIsoArr = function(precision) {
|
|||
const color = {
|
||||
month: (['half','quarter'].includes(precision) ? COLOR.WARNING : ( ['month','day','hour'].includes(precision) ? COLOR.SUCCESS : COLOR.INVALID )),
|
||||
day: ['day','hour'].includes(precision) ? COLOR.SUCCESS : COLOR.INVALID,
|
||||
hour: (precision == "hour") ? COLOR.SUCCESS : COLOR.INVALID
|
||||
hour: (precision === "hour") ? COLOR.SUCCESS : COLOR.INVALID
|
||||
};
|
||||
col = [
|
||||
COLOR.SUCCESS(this.getFullYear()),
|
||||
|
@ -51,16 +51,16 @@ Date.prototype.toIsoArr = function(precision) {
|
|||
this.secondsHumanReadable = ((t, precision) => {
|
||||
var seconds = parseInt(t, 10); // Convert float to int
|
||||
var days = Math.floor(seconds / 86400);
|
||||
if (precision == "days") { return days+" days" };
|
||||
if (precision === "days") { return days+" days" };
|
||||
var str = (days != 0 ? days+" days, " : "");
|
||||
seconds -= days*86400;
|
||||
var hours = Math.floor(seconds / 3600);
|
||||
str += (hours+" hours")
|
||||
if (precision == "hours") { return str };
|
||||
if (precision === "hours") { return str };
|
||||
seconds -= hours*3600;
|
||||
var minutes = Math.floor(seconds / 60);
|
||||
str += (", "+pad(minutes, " ")+" minutes")
|
||||
if (precision == "minutes") { return str };
|
||||
if (precision === "minutes") { return str };
|
||||
seconds -= minutes*60;
|
||||
return str+", "+pad(seconds, " ")+" seconds" //Let's not worry about padding the hours.. Doesn't change that much.
|
||||
});
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
const clc = require('cli-color');
|
||||
const constants = require('../constants');
|
||||
|
||||
const COLOR = constants.GETCOLORS(clc);
|
||||
const STRING = constants.GETSTRING(COLOR);
|
||||
|
||||
this.prettyPrintData = ((data_cache, idx, json_view) => {
|
||||
var data = Object.assign({},data_cache[1][idx]);
|
||||
if (json_view) {
|
||||
var output = [[clc.underline('Key/Value')]];
|
||||
var output = [[STRING.HEADERS.JSON]];
|
||||
JSON.stringify(data,null,2)
|
||||
.split('\n')
|
||||
.forEach(u => {output.push([clc.cyan(u)])});
|
||||
return output
|
||||
.forEach(u => {output.push([COLOR.GENERIC(u)])});
|
||||
return output;
|
||||
}
|
||||
// data.rocket = data_cache[2].filter((rocket) => rocket.id == data.rocket) //I'm lazy right now - was going to add in some features to view the 'hashes' (other elements such as cores, rocket type, etc.), but holding off on it.
|
||||
// console.log(JSON.stringify(data,null,2))
|
||||
var output = [[clc.underline('Property'),clc.underline('Value')]];
|
||||
var output = [[STRING.HEADERS.KEY,STRING.HEADERS.VALUE]];
|
||||
JSON.stringify(data,null,2)
|
||||
.split('\n')
|
||||
.forEach(u => {
|
||||
|
@ -24,7 +28,141 @@ this.prettyPrintData = ((data_cache, idx, json_view) => {
|
|||
.replace('}','')
|
||||
.replace('{','')
|
||||
.split(/:(.+)/);
|
||||
output.push([clc.yellow(val[0]),clc.cyan(val[1] ? val[1].replace(/,$/, '') : '')]);
|
||||
output.push([COLOR.WARNING(val[0]),COLOR.GENERIC(val[1] ? val[1].replace(/,$/, '') : '')]);
|
||||
});
|
||||
return output;
|
||||
});
|
||||
|
||||
this.deepCompare = ((A,B) => {
|
||||
if (A.length === B.length) {
|
||||
for ( var i = 0; i < A.length; i++ ) {
|
||||
if (Array.isArray(A[i])) {
|
||||
if (Array.isArray(B[i])) {
|
||||
if (!this.deepCompare(A[i],B[i])) {
|
||||
return false
|
||||
};
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
} else {
|
||||
if (A[i] !== B[i]) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
};
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
this.diffTable = ((A,B) => {
|
||||
var diff_table = [
|
||||
[
|
||||
'',
|
||||
STRING.HEADERS.FLIGHT_NUMBER,
|
||||
STRING.HEADERS.NAME,
|
||||
STRING.HEADERS.DATE_H,
|
||||
STRING.HEADERS.PRECISION,
|
||||
STRING.HEADERS.FLAGS,
|
||||
STRING.HEADERS.ROCKET,
|
||||
STRING.HEADERS.CORE,
|
||||
STRING.HEADERS.LAUNCHPAD,
|
||||
// STRING.HEADERS.LAUNCHPAD_REG
|
||||
STRING.HEADERS.PAYLOAD_NAME,
|
||||
STRING.HEADERS.PAYLOAD_CUSTOMERS,
|
||||
],
|
||||
[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' ']
|
||||
];
|
||||
|
||||
for ( var i = 0; i < B.length; i++ ) {
|
||||
var row = [[''],[''],Array(B[i].length).fill('')];
|
||||
var mod = false;
|
||||
for ( var j = 0; j < B[i].length; j++ ) {
|
||||
var a = A[i][j];
|
||||
var b = B[i][j];
|
||||
if (a !== b) {
|
||||
a = COLOR.DANGER(a);
|
||||
b = COLOR.SUCCESS(b);
|
||||
mod = true;
|
||||
} else {
|
||||
a = COLOR.GENERIC(a);
|
||||
b = COLOR.GENERIC(b);
|
||||
};
|
||||
row[0].push(a);
|
||||
row[1].push(b);
|
||||
};
|
||||
if (mod) {
|
||||
row[0][0] = COLOR.DANGER(STRING.DIFF.PREVIOUS_SYMBOL + STRING.DIFF.PREVIOUS);
|
||||
row[1][0] = COLOR.SUCCESS(STRING.DIFF.CURRENT_SYMBOL + STRING.DIFF.CURRENT);
|
||||
} else {
|
||||
row[0][0] = COLOR.WARNING(STRING.DIFF.UNCHANGED_SYMBOL + STRING.DIFF.PREVIOUS);
|
||||
row[1][0] = COLOR.WARNING(STRING.DIFF.UNCHANGED_SYMBOL + STRING.DIFF.CURRENT);
|
||||
};
|
||||
diff_table = diff_table.concat(row);
|
||||
};
|
||||
return diff_table;
|
||||
});
|
||||
|
||||
this.tabularizeDiffData = (LUNCHES => {
|
||||
LUNCHES = LUNCHES.map(lunch => [
|
||||
String(lunch.flight_number),
|
||||
lunch.name,
|
||||
lunch.date_h_,
|
||||
lunch.date_precision_,
|
||||
(lunch.tbd ? "tbd" : "")+(lunch.net && lunch.tbd ? ", " : "")+(lunch.net ? "net" : ""),
|
||||
lunch.rocket,
|
||||
lunch.cores_,
|
||||
lunch.launchpad,
|
||||
// lunch.launchpad_reg
|
||||
lunch.payloads.names_str,
|
||||
lunch.payloads.customers_str
|
||||
]);
|
||||
// LUNCHES.unshift([
|
||||
// STRING.HEADERS.FLIGHT_NUMBER,
|
||||
// STRING.HEADERS.NAME,
|
||||
// STRING.HEADERS.DATE_H,
|
||||
// STRING.HEADERS.DT,
|
||||
// STRING.HEADERS.PRECISION,
|
||||
// STRING.HEADERS.FLAGS,
|
||||
// STRING.HEADERS.ROCKET,
|
||||
// STRING.HEADERS.CORE,
|
||||
// STRING.HEADERS.LAUNCHPAD,
|
||||
// // STRING.HEADERS.LAUNCHPAD_REG
|
||||
// STRING.HEADERS.PAYLOAD_NAME,
|
||||
// STRING.HEADERS.PAYLOAD_CUSTOMERS,
|
||||
// ],[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' ']);
|
||||
return LUNCHES;
|
||||
})
|
||||
|
||||
this.tabularizeData = (LUNCHES => {
|
||||
LUNCHES = LUNCHES.map(lunch => [
|
||||
COLOR.GENERIC(String(lunch.flight_number)),
|
||||
COLOR.GENERIC(lunch.name),
|
||||
lunch.date_h,
|
||||
lunch.dt,
|
||||
lunch.date_precision,
|
||||
COLOR.DANGER((lunch.tbd ? "tbd" : "")+(lunch.net && lunch.tbd ? ", " : "")+(lunch.net ? "net" : "")),
|
||||
COLOR.GENERIC(lunch.rocket),
|
||||
COLOR.GENERIC(lunch.cores),
|
||||
COLOR.GENERIC(lunch.launchpad),
|
||||
// lunch.launchpad_reg
|
||||
COLOR.GENERIC(lunch.payloads.names_str),
|
||||
COLOR.GENERIC(lunch.payloads.customers_str)
|
||||
]);
|
||||
LUNCHES.unshift([
|
||||
STRING.HEADERS.FLIGHT_NUMBER,
|
||||
STRING.HEADERS.NAME,
|
||||
STRING.HEADERS.DATE_H,
|
||||
STRING.HEADERS.DT,
|
||||
STRING.HEADERS.PRECISION,
|
||||
STRING.HEADERS.FLAGS,
|
||||
STRING.HEADERS.ROCKET,
|
||||
STRING.HEADERS.CORE,
|
||||
STRING.HEADERS.LAUNCHPAD,
|
||||
// STRING.HEADERS.LAUNCHPAD_REG
|
||||
STRING.HEADERS.PAYLOAD_NAME,
|
||||
STRING.HEADERS.PAYLOAD_CUSTOMERS,
|
||||
],[ ' ', ' ', ' ', ' ', ' ', ' ', ' ',' ', ' ', ' ', ' ']);
|
||||
return LUNCHES;
|
||||
});
|
|
@ -1,4 +1,7 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fetch = require('node-fetch');
|
||||
const { CONSTANT_VALUES } = require('../constants');
|
||||
|
||||
async function fetchURLJSON(url) {
|
||||
var resp;
|
||||
|
@ -12,6 +15,36 @@ async function fetchURLJSON(url) {
|
|||
return resp;
|
||||
};
|
||||
|
||||
this.writeFile = ((path_, data) => {
|
||||
const dir = path_.substring(0, path_.lastIndexOf(path.sep));
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true }, (e) => {
|
||||
if (e) {
|
||||
throw e;
|
||||
};
|
||||
});
|
||||
};
|
||||
fs.writeFileSync(path_, data, function(e) {
|
||||
if (e) {
|
||||
throw e;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
this.readFile = (path_ => {
|
||||
if (fs.existsSync(path_)) {
|
||||
return fs.readFileSync(path_, { encoding: 'utf-8' }, (e,data) => {
|
||||
if (e) {
|
||||
throw e;
|
||||
} else {
|
||||
return data;
|
||||
};
|
||||
});
|
||||
} else {
|
||||
return ''; //Fail silently - for first-time run where the file doesn't exist.
|
||||
};
|
||||
});
|
||||
|
||||
this.getData = async function() {
|
||||
const LUNCHPADS_RESP = await fetchURLJSON('https://api.spacexdata.com/v4/launchpads');
|
||||
const LUNCHES_RESP = await fetchURLJSON('https://api.spacexdata.com/v4/launches/upcoming');
|
||||
|
|
|
@ -9,19 +9,34 @@ this.arguments = mri(process.argv, {
|
|||
help: false,
|
||||
screen_refresh: String(1000),
|
||||
color: true,
|
||||
dump: false
|
||||
dump: false,
|
||||
blink: false,
|
||||
archive: false,
|
||||
path: '~/.spacexcli',
|
||||
notify_time: String(5400),
|
||||
highlight_time: String(86400),
|
||||
},
|
||||
alias: {
|
||||
api_refresh: "a",
|
||||
help: "h",
|
||||
screen_refresh: "s",
|
||||
color: "c",
|
||||
dump: "d"
|
||||
dump: "d",
|
||||
blink: "b",
|
||||
archive: "v",
|
||||
path: "p",
|
||||
notify_time: "n",
|
||||
highlight_time: "g",
|
||||
}
|
||||
});
|
||||
|
||||
['s', 'screen_refresh', 'a', 'api_refresh'].forEach(idx => {
|
||||
if (idx == 'a' || idx == 'api_refresh') {
|
||||
[
|
||||
's', 'screen_refresh',
|
||||
'a', 'api_refresh',
|
||||
'n', 'notify_time',
|
||||
'g', 'highlight_time',
|
||||
].forEach(idx => {
|
||||
if (idx === 'a' || idx === 'api_refresh') {
|
||||
if (this.arguments[idx] < 30*1000) {
|
||||
this.arguments[idx] = 30*1000 // No spam pls!
|
||||
}
|
||||
|
@ -33,8 +48,8 @@ if (this.arguments.help) {
|
|||
console.log(`
|
||||
Usage:
|
||||
spacex-cli
|
||||
spacex-cli [-a <polling interval>] | [-h] | [-s <polling interval>] | [-d]
|
||||
spacex-cli [--api_refresh=<polling interval>] | [--help] | [--screen_refresh=<polling interval>] | [--dump]
|
||||
spacex-cli [-a <polling interval>] | [-h] | [-s <polling interval>] | [-d] | [-b]
|
||||
spacex-cli [--api_refresh=<polling interval>] | [--help] | [--screen_refresh=<polling interval>] | [--dump] | [--blink]
|
||||
|
||||
Options:
|
||||
-h, --help Show this help information.
|
||||
|
@ -42,6 +57,11 @@ Options:
|
|||
-a, --api_refresh API refresh interval in milliseconds. How often we poll the api for new/updated information. Please don't use small values! [default: 600000]
|
||||
-c, --color Print with color [default: true]
|
||||
-d, --dump Non-interactive mode - dumps the main launches table [default: false]
|
||||
-b, --blink Blink for close launches. This argument exists because I know some people hate blink [default: false]
|
||||
-v, --archive Archive launch data when changed [default: false]
|
||||
-p, --path Application directory [default: ~/.spacexcli]
|
||||
-n, --notify_time At this amount of seconds remaining until launch, send a notification [default: 5400]
|
||||
-g, --highlight_time At this amount of seconds remaining until launch, highlight the row in the table view [default: 86400]
|
||||
|
||||
Current configuration:`);
|
||||
var arguments_ = this.arguments;
|
||||
|
|
Loading…
Reference in New Issue
Block a user