Compare commits

...

6 Commits

Author SHA1 Message Date
35c471eef4 Fixed PathEvents filter
All checks were successful
Build / Build NPM Project (push) Successful in 1m12s
Build / Tag Version (push) Successful in 12s
Build / Publish Documentation (push) Successful in 47s
2025-02-04 21:35:31 -05:00
fe9fdb9384 Fixed blackorwhite invalid argument crash
All checks were successful
Build / Build NPM Project (push) Successful in 38s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 40s
2025-02-03 14:20:18 -05:00
a5d7a35fdc Fixed formatDate
All checks were successful
Build / Build NPM Project (push) Successful in 42s
Build / Tag Version (push) Successful in 9s
Build / Publish Documentation (push) Successful in 38s
2025-01-08 14:21:14 -05:00
cb117273a7 Fixed path-event filter
All checks were successful
Build / Build NPM Project (push) Successful in 39s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 36s
2024-12-27 22:20:27 -05:00
3bda688b1e added blackOrWhite color contrast function
All checks were successful
Build / Build NPM Project (push) Successful in 41s
Build / Tag Version (push) Successful in 8s
Build / Publish Documentation (push) Successful in 36s
2024-12-26 10:40:39 -05:00
446b1aa9db Updated arg-parser help output
All checks were successful
Build / Build NPM Project (push) Successful in 1m2s
Build / Tag Version (push) Successful in 16s
Build / Publish Documentation (push) Successful in 57s
2024-11-29 18:26:09 -05:00
6 changed files with 66 additions and 109 deletions

View File

@ -6,31 +6,9 @@
</head> </head>
<body> <body>
<script type="module"> <script type="module">
import {ArgParser} from './dist/index.mjs'; import {PathEvent} from './dist/index.mjs';
const hostname = 'test'; console.log(PathEvent.filter(['payments/ztimson:cr', 'logs/momentum:c', 'data/Testing:r'], 'data'));
const argParser = new ArgParser('botnet-manager.js', 'Connect & manage a network of servers to launch distributed attacks.', [
new ArgParser('copy', 'Copy file & dependencies to botnet', [
{name: 'file', desc: 'File to copy', default: false},
{name: 'control', desc: 'Copy to master server', flags: ['-c', '--control'], default: false},
{name: 'noDeps', desc: 'Skip copying dependencies', flags: ['-d', '--no-deps'], default: false},
{name: 'slave', desc: 'Copy to slave servers', flags: ['-s', '--slave'], default: false},
]),
new ArgParser('join', 'Connect server as a botnet slave', [
{name: 'device', desc: 'Device to connect, defaults to the current machine', optional: true, default: hostname}
]),
new ArgParser('kill', 'Kill any scripts running on the botnet'),
new ArgParser('leave', 'Disconnect worker node from swarm', [
{name: 'device', desc: 'Device to disconnect, defaults to the current machine', optional: true, default: hostname}
]),
new ArgParser('run', 'Copy & run script on the botnet', [
{name: 'script', desc: 'Script to copy & execute', type: 'string'},
{name: 'args', desc: 'Arguments for script. Forward the current target with: {{TARGET}}', optional: true, extras: true},
]),
new ArgParser('start', 'Start this server as the botnet manager'),
{name: 'silent', desc: 'Suppress program output', flags: ['-s', '--silent'], default: false},
]);
console.log(argParser.help({}));
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
{ {
"name": "@ztimson/utils", "name": "@ztimson/utils",
"version": "0.23.4", "version": "0.23.10",
"description": "Utility library", "description": "Utility library",
"author": "Zak Timson", "author": "Zak Timson",
"license": "MIT", "license": "MIT",

View File

@ -14,8 +14,6 @@ export type Arg<T = any> = {
} }
export class ArgParser { export class ArgParser {
static readonly helpArg: Arg = {name: 'help', desc: 'Display command\'s help message', flags: ['-h', '--help'], default: false};
commands: ArgParser[] = []; commands: ArgParser[] = [];
args: Arg[] = []; args: Arg[] = [];
flags: Arg[] = []; flags: Arg[] = [];
@ -37,7 +35,8 @@ export class ArgParser {
// Arguments // Arguments
this.commands = argList.filter(arg => arg instanceof ArgParser); this.commands = argList.filter(arg => arg instanceof ArgParser);
this.args = <Arg[]>argList.filter(arg => !(arg instanceof ArgParser) && !arg.flags?.length); this.args = <Arg[]>argList.filter(arg => !(arg instanceof ArgParser) && !arg.flags?.length);
this.flags = <Arg[]>[...argList.filter(arg => !(arg instanceof ArgParser) && arg.flags && arg.flags.length), ...this.flags]; this.flags = <Arg[]>[...argList.filter(arg => !(arg instanceof ArgParser) && arg.flags && arg.flags.length),
{name: 'help', desc: 'Display command\'s help message', flags: ['-h', '--help'], default: false}];
this.defaults = argList.reduce((acc, arg: any) => ({...acc, [arg.name]: arg['extras'] ? [] : (arg.default ?? null)}), {}); this.defaults = argList.reduce((acc, arg: any) => ({...acc, [arg.name]: arg['extras'] ? [] : (arg.default ?? null)}), {});
// Examples // Examples
@ -125,18 +124,18 @@ export class ArgParser {
// Description // Description
let msg = `\n\n${opts.message || this.desc}`; let msg = `\n\n${opts.message || this.desc}`;
// Examples // Examples
msg += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t\t'); msg += '\n\nUsage:\t' + this.examples.map(ex => `${this.name} ${ex}`).join('\n\t');
// Arguments // Arguments
if(this.args.length) msg += '\n\n\t\t' + this.args.map(arg => if(this.args.length) msg += '\n\n\t' + this.args.map(arg =>
`${arg.name.toUpperCase()}${spacer(arg.name)}${arg.desc}`).join('\n\t\t'); `${arg.name.toUpperCase()}${spacer(arg.name)}${arg.desc}`).join('\n\t');
// Flags // Flags
msg += '\n\nOptions:\n\t\t' + this.flags.map(flag => { msg += '\n\nOptions:\n\t' + this.flags.map(flag => {
const flags = flag.flags?.join(', ') || ''; const flags = flag.flags?.join(', ') || '';
return `${flags}${spacer(flags)}${flag.desc}`; return `${flags}${spacer(flags)}${flag.desc}`;
}).join('\n\t\t'); }).join('\n\t');
// Commands // Commands
if(this.commands.length) msg += '\n\nCommands:\n\t\t' + this.commands.map(command => if(this.commands.length) msg += '\n\nCommands:\n\t' + this.commands.map(command =>
`${command.name}${spacer(command.name)}${command.desc}`).join('\n\t\t'); `${command.name}${spacer(command.name)}${command.desc}`).join('\n\t');
return `${msg}\n\n`; return `${msg}\n\n`;
} }
} }

12
src/color.ts Normal file
View File

@ -0,0 +1,12 @@
/**
* Determine if either black or white provides more contrast to the provided color
* @param {string} background Color to compare against
* @return {"white" | "black"} Color with the most contrast
*/
export function blackOrWhite(background: string): 'white' | 'black' {
const exploded = background?.match(background.length >= 6 ? /\w\w/g : /\w/g);
if(!exploded) return 'black';
const [r, g, b] = exploded.map(hex => parseInt(hex, 16));
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
return luminance > 0.5 ? 'black' : 'white';
}

View File

@ -2,6 +2,7 @@ export * from './arg-parser';
export * from './array'; export * from './array';
export * from './aset'; export * from './aset';
export * from './cache'; export * from './cache';
export * from './color';
export * from './csv'; export * from './csv';
export * from './files'; export * from './files';
export * from './emitter'; export * from './emitter';

View File

@ -23,12 +23,12 @@ export function adjustedInterval(cb: Function, ms: number) {
/** /**
* Format date * Format date
* *
* @param {Date | number | string} date Date or timestamp to convert to string
* @param {string} format How date string will be formatted, default: `YYYY-MM-DD H:mm A` * @param {string} format How date string will be formatted, default: `YYYY-MM-DD H:mm A`
* @param tz Override timezone, can be either string or number * @param {Date | number | string} date Date or timestamp, defaults to now
* @param tz Set timezone offset
* @return {string} Formated date * @return {string} Formated date
*/ */
export function formatDate(date: Date | number | string, format = 'YYYY-MM-DD H:mm', tz?: string | number): string { export function formatDate(format = 'YYYY-MM-DD H:mm', date: Date | number | string = new Date(), tz?: string | number): string {
const timezones = [ const timezones = [
['IDLW', -12], ['IDLW', -12],
['SST', -11], ['SST', -11],
@ -67,16 +67,7 @@ export function formatDate(date: Date | number | string, format = 'YYYY-MM-DD H:
} }
function day(num: number): string { function day(num: number): string {
switch(num) { return ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][num] || 'Unknown';
case(0): return 'Sunday';
case(1): return 'Monday';
case(2): return 'Tuesday';
case(3): return 'Wednesday';
case(4): return 'Thursday';
case(5): return 'Friday';
case(6): return 'Saturday';
default: return 'Unknown';
}
} }
function doy(date: Date) { function doy(date: Date) {
@ -85,30 +76,16 @@ export function formatDate(date: Date | number | string, format = 'YYYY-MM-DD H:
} }
function month(num: number): string { function month(num: number): string {
switch(num) { return ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][num] || 'Unknown';
case(0): return 'January';
case(1): return 'February';
case(2): return 'March';
case(3): return 'April';
case(4): return 'May';
case(5): return 'June';
case(6): return 'July';
case(7): return 'August';
case(8): return 'September';
case(9): return 'October';
case(10): return 'November';
case(11): return 'December';
default: return 'Unknown';
}
} }
function suffix(num: number) { function suffix(num: number) {
let n = num.toString(); if (num % 100 >= 11 && num % 100 <= 13) return `${num}th`;
switch(n.at(-1)) { switch (num % 10) {
case('1'): return num + 'st'; case 1: return `${num}st`;
case('2'): return num + 'nd'; case 2: return `${num}nd`;
case('3'): return num + 'rd'; case 3: return `${num}rd`;
default: return num + 'th'; default: return `${num}th`;
} }
} }
@ -127,46 +104,36 @@ export function formatDate(date: Date | number | string, format = 'YYYY-MM-DD H:
if(!t) throw new Error(`Unknown timezone: ${tz}`); if(!t) throw new Error(`Unknown timezone: ${tz}`);
date = adjustTz(date, t[1]); date = adjustTz(date, t[1]);
return format // Token mapping
// Year const tokens: Record<string, string> = {
.replaceAll('YYYY', date.getFullYear().toString()) 'YYYY': date.getFullYear().toString(),
.replaceAll('YY', date.getFullYear().toString().slice(2)) 'YY': date.getFullYear().toString().slice(2),
// Month 'MMMM': month(date.getMonth()),
.replaceAll('MMMM', month(date.getMonth())) 'MMM': month(date.getMonth()).slice(0, 3),
.replaceAll('MMM', month(date.getMonth()).slice(0, 3)) 'MM': (date.getMonth() + 1).toString().padStart(2, '0'),
.replaceAll('MM', (date.getMonth() + 1).toString().padStart(2, '0')) 'M': (date.getMonth() + 1).toString(),
.replaceAll('M', (date.getMonth() + 1).toString()) 'DDD': doy(date).toString(),
// Day 'DD': date.getDate().toString().padStart(2, '0'),
.replaceAll('DDD', doy(date).toString()) 'Do': suffix(date.getDate()),
.replaceAll('DD', date.getDate().toString().padStart(2, '0')) 'D': date.getDate().toString(),
.replaceAll('Do', suffix(date.getDate())) 'dddd': day(date.getDay()),
.replaceAll('D', date.getDate().toString()) 'ddd': day(date.getDay()).slice(0, 3),
.replaceAll('dddd', day(date.getDay())) 'HH': date.getHours().toString().padStart(2, '0'),
.replaceAll('ddd', day(date.getDay()).slice(0, 2)) 'H': date.getHours().toString(),
.replaceAll('dd', date.getDate().toString().padStart(2, '0')) 'hh': (date.getHours() % 12 || 12).toString().padStart(2, '0'),
.replaceAll('d', date.getDay().toString()) 'h': (date.getHours() % 12 || 12).toString(),
// Hour 'mm': date.getMinutes().toString().padStart(2, '0'),
.replaceAll('HH', date.getHours().toString().padStart(2, '0')) 'm': date.getMinutes().toString(),
.replaceAll('H', date.getHours().toString()) 'ss': date.getSeconds().toString().padStart(2, '0'),
.replaceAll('hh', (date.getHours() > 12 ? date.getHours() - 12 : date.getHours()).toString().padStart(2, '0')) 's': date.getSeconds().toString(),
.replaceAll('h', (date.getHours() > 12 ? date.getHours() - 12 : date.getHours()).toString()) 'SSS': date.getMilliseconds().toString().padStart(3, '0'),
// Minute 'A': date.getHours() >= 12 ? 'PM' : 'AM',
.replaceAll('mm', date.getMinutes().toString().padStart(2, '0')) 'a': date.getHours() >= 12 ? 'pm' : 'am',
.replaceAll('m', date.getMinutes().toString()) 'ZZ': tzOffset(t[1] * 60).replace(':', ''),
// Second 'Z': tzOffset(t[1] * 60),
.replaceAll('ss', date.getSeconds().toString().padStart(2, '0')) 'z': typeof tz == 'string' ? tz : (<any>t)[0]
.replaceAll('s', date.getSeconds().toString()) };
// Millisecond return format.replace(/YYYY|YY|MMMM|MMM|MM|M|DDD|DD|Do|D|dddd|ddd|HH|H|hh|h|mm|m|ss|s|SSS|A|a|ZZ|Z|z/g, token => tokens[token]);
.replaceAll('SSS', date.getMilliseconds().toString().padEnd(3, '0'))
.replaceAll('SS', date.getMilliseconds().toString().slice(0, 1).padEnd(2, '0'))
.replaceAll('S', date.getMilliseconds().toString()[0])
// Period/Meridian (AM/PM)
.replaceAll('A', date.getHours() >= 12 ? 'PM' : 'AM')
.replaceAll('a', date.getHours() >= 12 ? 'pm' : 'am')
// Timezone
.replaceAll('ZZ', tzOffset(t[1] * 60).replace(':', ''))
.replaceAll('Z', tzOffset(t[1] * 60))
.replaceAll('z', typeof tz == 'string' ? tz : (<any>t)[0]);
} }
/** /**