2020-12-03 19:27:14 +00:00
|
|
|
import path from 'path';
|
|
|
|
import net from 'net';
|
|
|
|
import cprog from 'child_process';
|
2021-10-19 16:59:10 +00:00
|
|
|
import configureMeasurements, { allMeasures } from 'convert-units';
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
import {
|
|
|
|
Plugin,
|
|
|
|
Configurable,
|
|
|
|
EventListener,
|
|
|
|
DependencyLoad
|
|
|
|
} from '@squeebot/core/lib/plugin';
|
|
|
|
|
2020-12-13 10:16:49 +00:00
|
|
|
import { IMessage, MessageResolver } from '@squeebot/core/lib/types';
|
2020-12-03 19:27:14 +00:00
|
|
|
import { httpGET, parseTimeToSeconds, readableTime } from '@squeebot/core/lib/common';
|
|
|
|
|
|
|
|
import { logger } from '@squeebot/core/lib/core';
|
|
|
|
|
|
|
|
type CEXResponse = {[key: string]: number};
|
|
|
|
|
2021-10-19 16:59:10 +00:00
|
|
|
const convert = configureMeasurements(allMeasures);
|
|
|
|
|
2020-12-03 19:27:14 +00:00
|
|
|
const cexCache: {[key: string]: number | CEXResponse} = {
|
|
|
|
expiry: 0,
|
|
|
|
date: 0,
|
|
|
|
cache: {},
|
|
|
|
};
|
|
|
|
|
|
|
|
const bases: {[key: number]: string[]} = {
|
|
|
|
2: ['bin', 'binary'],
|
|
|
|
8: ['oct', 'octal'],
|
|
|
|
10: ['dec', 'decimal'],
|
|
|
|
16: ['hex', 'hexadecimal']
|
|
|
|
};
|
|
|
|
|
|
|
|
function getBaseNum(base: string): number | null {
|
|
|
|
let result = null;
|
|
|
|
for (const i in bases) {
|
|
|
|
const defs = bases[i];
|
|
|
|
if (defs.indexOf(base) === -1) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
result = parseInt(i, 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (base.indexOf('b') !== 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const matcher = base.match(/b(?:ase-?)?(\d+)/);
|
|
|
|
if (!matcher || !matcher[1] || isNaN(parseInt(matcher[1], 10))) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return parseInt(matcher[1], 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
const urlRegex = /(((ftp|https?):\/\/)[-\w@:%_+.~#?,&//=]+)/g;
|
|
|
|
const fork = cprog.fork;
|
|
|
|
|
|
|
|
// Run mathjs in a separate thread to avoid the killing of the main process
|
2020-12-06 13:41:18 +00:00
|
|
|
function opMath(expression: string): Promise<string> {
|
2020-12-03 19:27:14 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
// Fork the script
|
|
|
|
const mathThread = fork(path.join(__dirname, 'math.js'));
|
|
|
|
|
|
|
|
let done = false;
|
|
|
|
|
|
|
|
// Time the request out when user enters something too complex
|
|
|
|
const timeItOut = setTimeout(() => {
|
|
|
|
mathThread.kill('SIGKILL');
|
|
|
|
done = true;
|
|
|
|
return reject(new Error('Timed out'));
|
|
|
|
}, 8000);
|
|
|
|
|
|
|
|
// Send data to the thread to process
|
2020-12-06 13:41:18 +00:00
|
|
|
mathThread.send(expression);
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
// Recieve data
|
|
|
|
mathThread.on('message', (chunk) => {
|
|
|
|
clearTimeout(timeItOut);
|
|
|
|
|
|
|
|
if (done) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const line = chunk.toString().trim();
|
|
|
|
|
|
|
|
if (line.length > 280) {
|
|
|
|
return reject(new Error('The response was too large'));
|
|
|
|
}
|
|
|
|
|
|
|
|
done = true;
|
|
|
|
|
|
|
|
if (line === 'null') {
|
|
|
|
return reject(new Error('Nothing was returned'));
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(line);
|
|
|
|
});
|
|
|
|
|
|
|
|
mathThread.on('exit', () => {
|
|
|
|
clearTimeout(timeItOut);
|
|
|
|
|
|
|
|
if (!done) {
|
|
|
|
reject(new Error('Nothing was returned'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function rgbToHex(r: number, g: number, b: number): string {
|
|
|
|
// tslint:disable-next-line: no-bitwise
|
|
|
|
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
|
|
|
|
}
|
|
|
|
|
2020-12-05 10:00:00 +00:00
|
|
|
function RGBToHSL(r: number, g: number, b: number): {[key: string]: number} | null {
|
|
|
|
// Make r, g, and b fractions of 1
|
|
|
|
r /= 255;
|
|
|
|
g /= 255;
|
|
|
|
b /= 255;
|
|
|
|
|
|
|
|
// Find greatest and smallest channel values
|
|
|
|
const cmin = Math.min(r, g, b);
|
|
|
|
const cmax = Math.max(r, g, b);
|
|
|
|
const delta = cmax - cmin;
|
|
|
|
let h = 0;
|
|
|
|
let s = 0;
|
|
|
|
let l = 0;
|
|
|
|
|
|
|
|
// Calculate hue
|
|
|
|
// No difference
|
|
|
|
if (delta === 0) {
|
|
|
|
h = 0;
|
2020-12-06 13:41:18 +00:00
|
|
|
} // Red is max
|
2020-12-05 10:00:00 +00:00
|
|
|
else if (cmax === r) {
|
|
|
|
h = ((g - b) / delta) % 6;
|
2020-12-06 13:41:18 +00:00
|
|
|
} // Green is max
|
2020-12-05 10:00:00 +00:00
|
|
|
else if (cmax === g) {
|
|
|
|
h = (b - r) / delta + 2;
|
2020-12-06 13:41:18 +00:00
|
|
|
} // Blue is max
|
2020-12-05 10:00:00 +00:00
|
|
|
else {
|
|
|
|
h = (r - g) / delta + 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
h = Math.round(h * 60);
|
|
|
|
|
|
|
|
// Make negative hues positive behind 360°
|
|
|
|
if (h < 0) {
|
|
|
|
h += 360;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate lightness
|
|
|
|
l = (cmax + cmin) / 2;
|
|
|
|
|
|
|
|
// Calculate saturation
|
|
|
|
s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
|
|
|
|
|
|
|
|
// Multiply l and s by 100
|
|
|
|
s = +(s * 100).toFixed(1);
|
|
|
|
l = +(l * 100).toFixed(1);
|
|
|
|
|
|
|
|
return { h, s, l };
|
|
|
|
}
|
|
|
|
|
2020-12-03 19:27:14 +00:00
|
|
|
function hexToRgb(hex: string): {[key: string]: number} | null {
|
|
|
|
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
|
|
|
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
|
|
hex = hex.replace(shorthandRegex, (m, r, g, b) => {
|
|
|
|
return r + r + g + g + b + b;
|
|
|
|
});
|
|
|
|
|
|
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
|
|
return result ? {
|
|
|
|
r: parseInt(result[1], 16),
|
|
|
|
g: parseInt(result[2], 16),
|
|
|
|
b: parseInt(result[3], 16)
|
|
|
|
} : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function pingTcpServer(host: string, port: number): Promise<number> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let isFinished = false;
|
|
|
|
let timeA = new Date().getTime();
|
|
|
|
const timeB = new Date().getTime();
|
|
|
|
|
|
|
|
function returnResults(status: boolean, info: number | Error): void {
|
|
|
|
if (!isFinished) {
|
|
|
|
isFinished = true;
|
|
|
|
|
|
|
|
if (info instanceof Error) {
|
|
|
|
return reject(info);
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const pingHost = net.connect({port, host}, () => {
|
|
|
|
timeA = new Date().getTime();
|
|
|
|
returnResults(true, timeA - timeB);
|
|
|
|
pingHost.end();
|
|
|
|
pingHost.destroy();
|
|
|
|
});
|
|
|
|
|
|
|
|
pingHost.setTimeout(5000);
|
|
|
|
|
|
|
|
pingHost.on('timeout', () => {
|
|
|
|
pingHost.end();
|
|
|
|
pingHost.destroy();
|
|
|
|
returnResults(false, new Error('timeout'));
|
|
|
|
});
|
|
|
|
|
|
|
|
pingHost.on('error', (e) => {
|
|
|
|
pingHost.end();
|
|
|
|
pingHost.destroy();
|
|
|
|
returnResults(false, e);
|
|
|
|
});
|
|
|
|
|
|
|
|
pingHost.on('close', () => {
|
|
|
|
returnResults(false, new Error('closed'));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-10-19 17:35:08 +00:00
|
|
|
// Create an object to efficiently translate unit names to their abbreviations
|
|
|
|
const unitIndex: { [x: string]: string[] } = {};
|
|
|
|
const backupLowercaseUnitTable: { [x: string]: string } = {};
|
|
|
|
function createUnitIndex(): void {
|
|
|
|
const fullList = convert().list();
|
|
|
|
fullList.forEach((unit) => {
|
|
|
|
const { abbr, singular, plural } = unit;
|
|
|
|
if (!unitIndex[abbr]) {
|
|
|
|
unitIndex[abbr] = [
|
|
|
|
abbr, singular.toLowerCase(), plural.toLowerCase()
|
|
|
|
];
|
|
|
|
|
2021-10-19 17:53:55 +00:00
|
|
|
// liter <-> litres
|
2021-10-19 17:35:08 +00:00
|
|
|
if (abbr === 'l') {
|
|
|
|
unitIndex[abbr].push('liter', 'liters');
|
|
|
|
}
|
|
|
|
|
2021-10-19 17:53:55 +00:00
|
|
|
// meter -> metre
|
2021-10-19 17:35:08 +00:00
|
|
|
if (singular.includes('meter')) {
|
|
|
|
unitIndex[abbr].push(
|
|
|
|
singular.replace('meter', 'metre').toLowerCase(),
|
|
|
|
plural.replace('meter', 'metre').toLowerCase(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-10-19 17:53:55 +00:00
|
|
|
// metre -> meter
|
|
|
|
if (singular.includes('metre')) {
|
|
|
|
unitIndex[abbr].push(
|
|
|
|
singular.replace('metre', 'meter').toLowerCase(),
|
|
|
|
plural.replace('metre', 'meter').toLowerCase(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// " per " -> "/"
|
|
|
|
const appendages: string[] = [];
|
|
|
|
unitIndex[abbr].forEach((entry) => {
|
|
|
|
if (entry.includes(' per ')) {
|
|
|
|
appendages.push(entry.replace(' per ', '/'));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
unitIndex[abbr].push(...appendages);
|
|
|
|
|
2021-10-19 17:35:08 +00:00
|
|
|
backupLowercaseUnitTable[abbr.toLowerCase()] = abbr;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get a unit by it's full name or abbreviation
|
|
|
|
function getUnitAbbreviation(fullText: string): string | null {
|
|
|
|
// Regular abbreviation
|
|
|
|
if (unitIndex[fullText]) {
|
|
|
|
return fullText;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lowercase abbreviation
|
|
|
|
const lowerText = fullText.toLowerCase();
|
|
|
|
if (backupLowercaseUnitTable[lowerText]) {
|
|
|
|
return backupLowercaseUnitTable[lowerText];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Text search
|
|
|
|
const found = Object.keys(unitIndex).reduce((previous, current) => {
|
|
|
|
const arrayList = unitIndex[current];
|
|
|
|
if (arrayList.includes(lowerText)) {
|
|
|
|
return current;
|
|
|
|
}
|
|
|
|
return previous;
|
|
|
|
}, '');
|
|
|
|
|
|
|
|
return found || null;
|
|
|
|
}
|
|
|
|
|
2020-12-03 19:27:14 +00:00
|
|
|
function addCommands(plugin: UtilityPlugin, commands: any): void {
|
|
|
|
const cmds = [];
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'binary',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
let response = '';
|
|
|
|
let strArr;
|
|
|
|
let i;
|
2020-12-05 10:00:00 +00:00
|
|
|
let text = msg.text.split(' ').slice(2).join(' ');
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
switch (simplified[0] ? simplified[0].toUpperCase() : null) {
|
|
|
|
case 'ENCODE':
|
|
|
|
strArr = text.split('');
|
|
|
|
|
|
|
|
for (i in strArr) {
|
|
|
|
response += ' ' + ('0000000' +
|
|
|
|
parseInt(Buffer.from(strArr[i].toString(), 'utf8').toString('hex'), 16).toString(2)).slice(-8);
|
|
|
|
}
|
|
|
|
|
|
|
|
response = response.substr(1);
|
|
|
|
|
|
|
|
break;
|
|
|
|
case 'DECODE':
|
|
|
|
text = text.split(' ').join('');
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
while (8 * (i + 1) <= text.length) {
|
|
|
|
response += Buffer.from(parseInt(text.substr(8 * i, 8), 2).toString(16), 'hex').toString('utf8');
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
response = 'Decoded: ' + response.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
msg.resolve('Operation failed.');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve(response);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Encode/decode binary (ASCII only)',
|
|
|
|
usage: '<ENCODE/DECODE> <message>'
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'hexstr',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
let response = '';
|
|
|
|
let i;
|
2020-12-05 10:00:00 +00:00
|
|
|
let text = msg.text.split(' ').slice(2).join(' ');
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
switch (simplified[0] ? simplified[0].toUpperCase() : null) {
|
|
|
|
case 'DECODE':
|
|
|
|
text = text.replace(/\s/g, '');
|
|
|
|
|
|
|
|
for (i = 0; i < text.length; i += 2) {
|
|
|
|
response += String.fromCharCode(parseInt(text.substr(i, 2), 16));
|
|
|
|
}
|
|
|
|
|
|
|
|
response = 'Decoded: ' + response.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
|
|
|
break;
|
|
|
|
case 'ENCODE':
|
|
|
|
for (i = 0; i < text.length; i++) {
|
|
|
|
response += text.charCodeAt(i).toString(16) + ' ';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
msg.resolve('Operation failed.');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve(response);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Encode/decode hexadecimal (ASCII only)',
|
|
|
|
usage: '<ENCODE/DECODE> <string>'
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'base64',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
let response = '';
|
2020-12-05 10:00:00 +00:00
|
|
|
const text = msg.text.split(' ').slice(2).join(' ');
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
switch (simplified[0] ? simplified[0].toUpperCase() : null) {
|
|
|
|
case 'DECODE':
|
|
|
|
response = 'Decoded: ' + (Buffer.from(text, 'base64').toString('ascii')).replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
|
|
|
break;
|
|
|
|
case 'ENCODE':
|
|
|
|
response = Buffer.from(text).toString('base64');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
msg.resolve('Operation failed.');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve(response);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Encode/decode base64 (ASCII only)',
|
|
|
|
usage: '<ENCODE/DECODE> <string>'
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'numsys',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-06 13:41:18 +00:00
|
|
|
if (simplified.length < 3) {
|
|
|
|
msg.resolve('Too few arguments!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-03 19:27:14 +00:00
|
|
|
const input = simplified[0];
|
|
|
|
const src = simplified[1].toLowerCase();
|
|
|
|
const dst = simplified[2].toLowerCase();
|
|
|
|
|
|
|
|
const srcBase = getBaseNum(src);
|
|
|
|
const dstBase = getBaseNum(dst);
|
|
|
|
|
|
|
|
if (!srcBase || !dstBase || dstBase > 36 || dstBase < 2 || srcBase > 36 || srcBase < 2) {
|
|
|
|
msg.resolve('Invalid conversion.');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const decimal = parseInt(input, srcBase);
|
|
|
|
const result = decimal.toString(dstBase);
|
|
|
|
|
|
|
|
msg.resolve('Result:', result);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Convert a value into a value in another numbering system.',
|
|
|
|
usage: '<value> <bin/dec/hex/oct> <bin/dec/hex/oct>',
|
|
|
|
aliases: ['convertnumbers', 'cvnums']
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'convertseconds',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
msg.resolve(readableTime(parseInt(simplified[0], 10)));
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Convert seconds to years days hours minutes seconds.',
|
|
|
|
usage: '<seconds>',
|
|
|
|
aliases: ['cvs', 'parseseconds']
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'converttime',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-05 10:00:00 +00:00
|
|
|
const str = msg.text.split(' ').slice(1).join(' ');
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
if (!str) {
|
|
|
|
msg.resolve('Invalid input');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve(parseTimeToSeconds(str) + ' seconds');
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Convert ywdhms to seconds.',
|
|
|
|
usage: '[<years>y] [<weeks>w] [<days>d] [<hours>h] [<minutes>m] [<seconds>s]',
|
|
|
|
aliases: ['cvt', 'parsetime']
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'reconverttime',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-05 10:00:00 +00:00
|
|
|
const str = msg.text.split(' ').slice(1).join(' ');
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
if (!str) {
|
|
|
|
msg.resolve('Invalid input');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const sec = parseTimeToSeconds(str);
|
|
|
|
msg.resolve(readableTime(sec));
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
aliases: ['rcvt']
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'eval',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
if (!simplified[0]) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-05 10:00:00 +00:00
|
|
|
const wholeRow = msg.text.split(' ').slice(1).join(' ');
|
2020-12-03 19:27:14 +00:00
|
|
|
|
2020-12-05 10:00:00 +00:00
|
|
|
try {
|
|
|
|
const repl = await opMath(wholeRow);
|
2020-12-03 19:27:14 +00:00
|
|
|
msg.resolve(repl);
|
2021-09-03 17:23:56 +00:00
|
|
|
} catch (e: any) {
|
2020-12-03 19:27:14 +00:00
|
|
|
msg.resolve('Could not evaluate expression:', e.message);
|
2020-12-05 10:00:00 +00:00
|
|
|
}
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
2020-12-06 13:41:18 +00:00
|
|
|
aliases: ['math', 'calc'],
|
2020-12-03 19:27:14 +00:00
|
|
|
usage: '<expression>',
|
2020-12-06 13:41:18 +00:00
|
|
|
description: 'Evaluate a math expression (See https://mathjs.org/)'
|
2020-12-03 19:27:14 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'userid',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
msg.resolve('Your userId is %s.', msg.fullSenderID);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Display your userId (internal user identification)',
|
|
|
|
hidden: true
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'roomid',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
msg.resolve('Current roomId is %s.', msg.fullRoomID);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Display the internal identification of this room',
|
|
|
|
hidden: true
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'serverid',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
if (msg.target && msg.target.server) {
|
|
|
|
msg.resolve('Current server ID is s:%s.', msg.target.server);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve('This protocol does not specify a server. ' +
|
|
|
|
'Either the protocol is for a single server only or the server variable is not supported.');
|
|
|
|
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Display the internal identification of this room',
|
|
|
|
hidden: true
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'rgb2hex',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
if (!simplified[0]) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-05 10:00:00 +00:00
|
|
|
const fullmsg = msg.text.split(' ').slice(1).join(' ');
|
2020-12-03 19:27:14 +00:00
|
|
|
const channels = fullmsg.match(/(rgb)?\(?(\d{1,3}),?\s(\d{1,3}),?\s(\d{1,3})\)?/i);
|
|
|
|
if (!channels || channels[2] == null) {
|
|
|
|
msg.resolve('Invalid parameter');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const r = parseInt(channels[2], 10);
|
|
|
|
const g = parseInt(channels[3], 10);
|
|
|
|
const b = parseInt(channels[4], 10);
|
|
|
|
|
|
|
|
if (r > 255 || g > 255 || b > 255) {
|
|
|
|
msg.resolve('Invalid colors');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-06 13:41:18 +00:00
|
|
|
msg.resolve(rgbToHex(r, g, b));
|
2020-12-03 19:27:14 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Convert RGB to HEX colors',
|
|
|
|
usage: '[rgb](<r>, <g>, <b>)|<r> <g> <b>'
|
|
|
|
});
|
|
|
|
|
2020-12-05 10:00:00 +00:00
|
|
|
cmds.push({
|
|
|
|
name: 'rgb2hsl',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-05 10:00:00 +00:00
|
|
|
if (!simplified[0]) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const fullmsg = msg.text.split(' ').slice(1).join(' ');
|
|
|
|
const channels = fullmsg.match(/(rgb)?\(?(\d{1,3}),?\s(\d{1,3}),?\s(\d{1,3})\)?/i);
|
|
|
|
if (!channels || channels[2] == null) {
|
|
|
|
msg.resolve('Invalid parameter');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const r = parseInt(channels[2], 10);
|
|
|
|
const g = parseInt(channels[3], 10);
|
|
|
|
const b = parseInt(channels[4], 10);
|
|
|
|
|
|
|
|
if (r > 255 || g > 255 || b > 255) {
|
|
|
|
msg.resolve('Invalid colors');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const hsl = RGBToHSL(r, g, b);
|
|
|
|
|
|
|
|
msg.resolve('hsl(%d, %d%, %d%)', hsl?.h, hsl?.s, hsl?.l);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Convert RGB to HSL colors',
|
|
|
|
usage: '[rgb](<r>, <g>, <b>)|<r> <g> <b>'
|
|
|
|
});
|
|
|
|
|
2020-12-03 19:27:14 +00:00
|
|
|
cmds.push({
|
|
|
|
name: 'hex2rgb',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
if (!simplified[0]) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
let hexcode = simplified[0];
|
|
|
|
|
|
|
|
if (hexcode.indexOf('#') === -1) {
|
|
|
|
hexcode = '#' + hexcode;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hexcode.length !== 4 && hexcode.length !== 7) {
|
|
|
|
msg.resolve('Invalid length');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const rgb = hexToRgb(hexcode);
|
|
|
|
if (!rgb) {
|
|
|
|
msg.resolve('Invalid HEX notation');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve('rgb(%d, %d, %d)', rgb.r, rgb.g, rgb.b);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Convert HEX to RGB colors',
|
|
|
|
usage: '#<r><g><b>|#<rr><gg><bb>'
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'isup',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
if (!simplified[0]) {
|
|
|
|
msg.resolve('Please specify host name!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (!simplified[1]) {
|
|
|
|
msg.resolve('Please specify port!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const host = simplified[0];
|
|
|
|
const port = parseInt(simplified[1], 10);
|
|
|
|
|
|
|
|
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
|
|
msg.resolve('Invalid port number!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let statusString = msg.source.format.format('bold', 'closed');
|
|
|
|
let status;
|
|
|
|
|
|
|
|
try {
|
|
|
|
status = await pingTcpServer(host, port);
|
|
|
|
statusString = msg.source.format.format('bold', 'open');
|
2021-09-03 17:23:56 +00:00
|
|
|
} catch (e: any) {
|
2020-12-03 19:27:14 +00:00
|
|
|
status = e.message;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isNaN(parseFloat(status))) {
|
|
|
|
status = status + ' ms';
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve(`Port ${port} on ${host} is ${statusString} (${status})`);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Ping a host',
|
|
|
|
usage: '<host> <port>',
|
|
|
|
aliases: ['tcpup', 'tping'],
|
|
|
|
hidden: true
|
|
|
|
});
|
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'convert',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-03 19:27:14 +00:00
|
|
|
const tqnt = parseFloat(simplified[0]);
|
|
|
|
if (isNaN(tqnt)) {
|
|
|
|
msg.resolve('Please specify a quantity, either an integer or a float!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-10-19 17:35:08 +00:00
|
|
|
let src = simplified[1];
|
2020-12-03 19:27:14 +00:00
|
|
|
let dst = simplified[2];
|
|
|
|
if (dst && dst.toLowerCase() === 'to') {
|
|
|
|
dst = simplified[3];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!src) {
|
|
|
|
msg.resolve('Please specify source unit!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dst) {
|
|
|
|
msg.resolve('Please specify destination unit!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = null;
|
2021-10-19 17:35:08 +00:00
|
|
|
src = getUnitAbbreviation(src);
|
|
|
|
dst = getUnitAbbreviation(dst);
|
|
|
|
|
|
|
|
if (!src) {
|
|
|
|
msg.resolve('Source unit not found!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dst) {
|
|
|
|
msg.resolve('Destination unit not found!');
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
res = convert(tqnt).from(src).to(dst);
|
|
|
|
} catch (e) {
|
|
|
|
res = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
const srcdesc = convert().describe(src);
|
|
|
|
const dstdesc = convert().describe(dst);
|
|
|
|
|
|
|
|
const bsrcdesc = (Math.floor(tqnt) !== 1) ? srcdesc.plural : srcdesc.singular;
|
|
|
|
const bdstdesc = (Math.floor(res) !== 1) ? dstdesc.plural : dstdesc.singular;
|
|
|
|
|
|
|
|
msg.resolve(` ${tqnt} ${bsrcdesc} => ${res} ${bdstdesc}`);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg.resolve('Failed to convert.');
|
|
|
|
return true;
|
|
|
|
},
|
2021-10-19 17:35:08 +00:00
|
|
|
description: 'Convert between quantities in different units.',
|
2020-12-03 19:27:14 +00:00
|
|
|
usage: '<number> <from unit> <to unit>',
|
|
|
|
aliases: ['cv', 'unit']
|
|
|
|
});
|
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
cmds.push({
|
|
|
|
name: 'currency',
|
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
|
|
|
let ctw: CEXResponse | null = cexCache.cache as CEXResponse;
|
|
|
|
if (cexCache.expiry < Date.now()) {
|
|
|
|
let fetched;
|
|
|
|
try {
|
|
|
|
const data = await httpGET('https://api.exchangerate.host/latest');
|
|
|
|
fetched = JSON.parse(data);
|
|
|
|
logger.log('[utility] Fetched currency exchange rates successfully.');
|
|
|
|
} catch (e) {
|
|
|
|
ctw = null;
|
2020-12-03 19:27:14 +00:00
|
|
|
}
|
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
if (!ctw || !fetched.rates) {
|
|
|
|
msg.resolve('Could not fetch currency exchange rates at this time. Please try again later.');
|
2020-12-03 19:27:14 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
cexCache.cache = fetched.rates;
|
|
|
|
cexCache.date = fetched.date;
|
|
|
|
cexCache.expiry = Date.now() + 86400000; // day
|
|
|
|
ctw = cexCache.cache as CEXResponse;
|
|
|
|
}
|
2020-12-03 19:27:14 +00:00
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
if (simplified[0] === 'date') {
|
|
|
|
msg.resolve('Currency exchange rates are as of %s', cexCache.date);
|
|
|
|
return true;
|
|
|
|
} else if (simplified[0] === 'list') {
|
|
|
|
msg.resolve('Currently supported currencies: EUR, %s', Object.keys(cexCache.cache).join(', '));
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-03 19:27:14 +00:00
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
const n = parseFloat(simplified[0]);
|
|
|
|
let f = simplified[1];
|
|
|
|
let t = simplified[2];
|
|
|
|
if (isNaN(n) || !f || !t) {
|
|
|
|
msg.resolve('Invalid parameters.');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
f = f.toUpperCase();
|
|
|
|
t = t.toUpperCase();
|
2020-12-03 19:27:14 +00:00
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
if (f !== 'EUR' && !ctw[f]) {
|
|
|
|
msg.resolve('This currency is currently not supported.');
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-03 19:27:14 +00:00
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
if (t !== 'EUR' && !ctw[t]) {
|
|
|
|
msg.resolve('This currency is currently not supported.');
|
|
|
|
return true;
|
|
|
|
}
|
2020-12-03 19:27:14 +00:00
|
|
|
|
2021-04-15 16:56:45 +00:00
|
|
|
if (f === t) {
|
|
|
|
msg.resolve('%f %s', n, f);
|
2020-12-03 19:27:14 +00:00
|
|
|
return true;
|
2021-04-15 16:56:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let ramnt: string;
|
|
|
|
if (f === 'EUR') {
|
|
|
|
ramnt = (n * ctw[t]).toFixed(4);
|
|
|
|
msg.resolve('%f EUR => %f %s', n, ramnt, t);
|
|
|
|
return true;
|
|
|
|
} else if (t === 'EUR') {
|
|
|
|
ramnt = (n / ctw[f]).toFixed(4);
|
|
|
|
msg.resolve('%f %s => %f EUR', n, f, ramnt);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const amnt = (ctw[t] * n / ctw[f]).toFixed(4);
|
|
|
|
msg.resolve('%f %s => %f %s', n, f, amnt, t);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Convert between currencies.',
|
|
|
|
usage: '<number> | [date | list] [<from currency>] [<to currency>]',
|
|
|
|
aliases: ['cex', 'exchange']
|
|
|
|
});
|
2020-12-03 19:27:14 +00:00
|
|
|
|
|
|
|
cmds.push({
|
|
|
|
name: 'randomnumber',
|
2020-12-13 10:16:49 +00:00
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
2020-12-06 13:41:18 +00:00
|
|
|
if (simplified.length < 2) {
|
|
|
|
msg.resolve('Too few arguments!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-12-03 19:27:14 +00:00
|
|
|
let min = parseInt(simplified[0], 10);
|
|
|
|
let max = parseInt(simplified[1], 10);
|
|
|
|
let count = parseInt(simplified[2], 10);
|
|
|
|
const countMax = plugin.config.config.randomMax || 64;
|
|
|
|
if (isNaN(min) || isNaN(max)) {
|
|
|
|
msg.resolve('Invalid numbers.');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (min > max) {
|
|
|
|
const realMax = min + 0;
|
|
|
|
min = max;
|
|
|
|
max = realMax;
|
|
|
|
}
|
|
|
|
if (isNaN(count)) { count = 1; }
|
|
|
|
if (String(Math.abs(min)).length > 9 || String(Math.abs(max)).length > 9) {
|
|
|
|
msg.resolve('The numbers are too large!');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (count > countMax) {
|
|
|
|
msg.resolve('Too many to generate. Maximum: ' + countMax);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
const numbers = [];
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
numbers.push(Math.floor(Math.random() * (max - min + 1)) + min);
|
|
|
|
}
|
|
|
|
msg.resolve(numbers.join(' '));
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Generate a random number between <min> and <max>.',
|
|
|
|
usage: '<min> <max> [<count>]',
|
2020-12-06 13:41:18 +00:00
|
|
|
aliases: ['rnum', 'rand', 'rng']
|
2020-12-03 19:27:14 +00:00
|
|
|
});
|
|
|
|
|
2021-10-19 16:59:10 +00:00
|
|
|
// FIXME: temporary code for removal
|
|
|
|
cmds.push({
|
|
|
|
name: 'cmtest',
|
|
|
|
execute: async (msg: IMessage, msr: MessageResolver, spec: any, prefix: string, ...simplified: any[]): Promise<boolean> => {
|
|
|
|
console.log(simplified);
|
|
|
|
msg.resolve(`argument 1: '${simplified[0]}' argument 2: '${simplified[1]}'`);
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
description: 'Test the command argument parser',
|
|
|
|
usage: '',
|
|
|
|
hidden: true,
|
|
|
|
});
|
|
|
|
|
2020-12-03 19:27:14 +00:00
|
|
|
commands.registerCommand(cmds.map((x: any) => {
|
|
|
|
x.plugin = plugin.manifest.name;
|
|
|
|
return x;
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Configurable({
|
|
|
|
ipfsGateway: 'https://ipfs.io',
|
|
|
|
randomMax: 64
|
|
|
|
})
|
|
|
|
class UtilityPlugin extends Plugin {
|
|
|
|
@DependencyLoad('simplecommands')
|
|
|
|
addCommands(cmd: any): void {
|
|
|
|
addCommands(this, cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
@EventListener('pluginUnload')
|
|
|
|
public unloadEventHandler(plugin: string | Plugin): void {
|
|
|
|
if (plugin === this.name || plugin === this) {
|
2021-10-02 09:31:46 +00:00
|
|
|
this.emit('pluginUnloaded', this);
|
2020-12-03 19:27:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
initialize(): void {
|
2020-12-05 10:00:00 +00:00
|
|
|
this.on('message', (msg: IMessage) => {
|
|
|
|
// Pre-regex check
|
|
|
|
if (msg.text.indexOf('ipfs://') === -1 && msg.text.indexOf('Qm') === -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// IPFS urlify
|
|
|
|
const mmatch = msg.text.match(/(?:ipfs:\/\/|\s|^)(Qm[\w\d]{44})(?:\s|$)/);
|
|
|
|
if (mmatch && mmatch[1]) {
|
|
|
|
msg.resolve(this.config.config.ipfsGateway + '/ipfs/' + mmatch[1]);
|
|
|
|
}
|
|
|
|
});
|
2021-10-19 17:35:08 +00:00
|
|
|
|
|
|
|
// Initialize list of units
|
|
|
|
createUnitIndex();
|
2020-12-03 19:27:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = UtilityPlugin;
|