From 0621386e552459d4d98c9ff2aaa279547e9fa68c Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 5 Dec 2020 12:00:00 +0200 Subject: [PATCH] more plugins ported over --- debug/plugin.json | 9 + debug/plugin.ts | 118 ++++++++ diction/plugin.json | 9 + diction/plugin.ts | 121 ++++++++ fun/plugin.json | 9 + fun/plugin.ts | 85 ++++++ gamedig/plugin.json | 9 + gamedig/plugin.ts | 116 ++++++++ package-lock.json | 575 +++++++++++++++++++++++++++++++++++++- package.json | 4 + squeebot.repo.json | 28 ++ timezone/plugin.json | 9 + timezone/plugin.ts | 64 +++++ url-fediverse/plugin.json | 9 + url-fediverse/plugin.ts | 99 +++++++ urlreply/plugin.json | 9 + urlreply/plugin.ts | 384 +++++++++++++++++++++++++ utility/plugin.ts | 220 ++++++++------- 18 files changed, 1770 insertions(+), 107 deletions(-) create mode 100644 debug/plugin.json create mode 100644 debug/plugin.ts create mode 100644 diction/plugin.json create mode 100644 diction/plugin.ts create mode 100644 fun/plugin.json create mode 100644 fun/plugin.ts create mode 100644 gamedig/plugin.json create mode 100644 gamedig/plugin.ts create mode 100644 timezone/plugin.json create mode 100644 timezone/plugin.ts create mode 100644 url-fediverse/plugin.json create mode 100644 url-fediverse/plugin.ts create mode 100644 urlreply/plugin.json create mode 100644 urlreply/plugin.ts diff --git a/debug/plugin.json b/debug/plugin.json new file mode 100644 index 0000000..c3e0e34 --- /dev/null +++ b/debug/plugin.json @@ -0,0 +1,9 @@ +{ + "main": "plugin.js", + "name": "debug", + "description": "In-chat debugging tools", + "version": "1.0.0", + "tags": ["commands", "tools"], + "dependencies": ["simplecommands"], + "npmDependencies": [] +} diff --git a/debug/plugin.ts b/debug/plugin.ts new file mode 100644 index 0000000..cb55598 --- /dev/null +++ b/debug/plugin.ts @@ -0,0 +1,118 @@ +import util from 'util'; +import cprog from 'child_process'; + +import { + Plugin, + Configurable, + EventListener, + DependencyLoad +} from '@squeebot/core/lib/plugin'; + +import { IMessage } from '@squeebot/core/lib/types'; + +import { ISqueebotCore, logger } from '@squeebot/core/lib/core'; + +function addCommands(plugin: UtilityPlugin, commands: any): void { + const cmds = [{ + plugin: plugin.manifest.name, + name: 'evaljs', + execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { + if (!simplified[0]) { + return true; + } + const script = msg.data.split(' ').slice(1).join(' '); + + // Disallow child_process when shell is disallowed + if ((script.indexOf('child_process') !== -1 || + script.indexOf('cprog') !== -1 || + script.indexOf('fork') !== -1) && + !plugin.config.config.allowShell) { + msg.resolve('Error: child_process is not allowed in evaljs due to security reasons.'); + return true; + } + + try { + // tslint:disable-next-line: no-eval + const mesh = eval(script); + if (mesh === undefined) { + return true; + } + msg.resolve(util.format(mesh)); + } catch (e) { + msg.resolve('Error: ' + e.message); + } + + return true; + }, + usage: '', + description: 'Execute JavaScript in a command context', + permissions: ['system_execute'], + hidden: true + }]; + + if (plugin.config.config.allowShell) { + logger.warn('WARNING! Shell command execution is enabled! Make absolutely sure that there is proper authentication!'); + if (process.getuid && process.getuid() === 0) { + logger.warn('NEVER run Squeebot as root! Run `useradd squeebot`! We are not responsible for possible security leaks!'); + } + + cmds.push({ + plugin: plugin.manifest.name, + name: 'sh', + execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { + const stripnl = (simplified[0] !== '-n'); + const cmd = simplified.slice(stripnl ? 0 : 1).join(' '); + if (!cmd) { + msg.resolve('Nothing to execute!'); + return true; + } + + cprog.exec(cmd, {shell: '/bin/bash'}, (error, stdout, stderr) => { + if (stdout) { + if (stripnl) { stdout = stdout.replace(/\n/g, ' ;; '); } + + return msg.resolve(stdout); + } + + msg.resolve('Error executing command.'); + logger.error(stderr || error); + }); + + return true; + }, + description: 'Run raw shell command', + usage: '', + hidden: true, + permissions: ['system_shell'], + }); + } + + commands.registerCommand(cmds); +} + +@Configurable({ + allowShell: false, +}) +class UtilityPlugin extends Plugin { + public core: ISqueebotCore | null = null; + + @DependencyLoad('simplecommands') + addCommands(cmd: any): void { + addCommands(this, cmd); + } + + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + this.config.save().then(() => + this.emit('pluginUnloaded', this)); + } + } + + initialize(): void { + this.on('core', (c: ISqueebotCore) => this.core = c); + this.emitTo('core', 'request-core', this.name); + } +} + +module.exports = UtilityPlugin; diff --git a/diction/plugin.json b/diction/plugin.json new file mode 100644 index 0000000..50e564d --- /dev/null +++ b/diction/plugin.json @@ -0,0 +1,9 @@ +{ + "main": "plugin.js", + "name": "diction", + "description": "Find definitions for words", + "version": "1.0.0", + "tags": ["commands", "utility", "dictionary"], + "dependencies": ["simplecommands"], + "npmDependencies": [] +} diff --git a/diction/plugin.ts b/diction/plugin.ts new file mode 100644 index 0000000..d643683 --- /dev/null +++ b/diction/plugin.ts @@ -0,0 +1,121 @@ +import { httpGET } from '@squeebot/core/lib/common'; +import { logger } from '@squeebot/core/lib/core'; +import { + Plugin, + Configurable, + EventListener, + DependencyLoad +} from '@squeebot/core/lib/plugin'; +import { IMessage } from '@squeebot/core/lib/types'; + +const poslist = [ + 'noun', + 'verb', + 'adjective', + 'adverb', + 'participle', + 'article', + 'pronoun', + 'preposition', + 'conjunction', + 'interjection', + 'determiner', +]; + +let lastQuery = 0; + +@Configurable({ + lexicala: { + username: '', + password: '', + }, + cooldown: 5, +}) +class DictionPlugin extends Plugin { + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + this.emit('pluginUnloaded', this); + } + } + + @DependencyLoad('simplecommands') + addCommands(cmd: any): void { + const user = this.config.get('lexicala.username'); + const passwd = this.config.get('lexicala.password'); + const rate = this.config.get('cooldown', 5); + const rauth = 'Basic ' + Buffer.from(`${user}:${passwd}`).toString('base64'); + + if (!user || !passwd) { + logger.warn('Lexicala define command is disabled due to no credentials.'); + return; + } + + cmd.registerCommand({ + name: 'define', + plugin: this.name, + execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { + if (lastQuery > Date.now() - rate * 1000) { + msg.resolve('You\'re doing that too fast!'); + return true; + } + + let pos: string | null = simplified[0]; + if (pos && poslist.indexOf(pos.toLowerCase()) !== -1 && simplified.length > 1) { + pos = '&pos=' + pos; + } else { + pos = ''; + } + + const word = encodeURIComponent(msg.text.split(' ').slice(pos ? 2 : 1).join(' ')); + + if (!word) { + msg.resolve('Please specifiy a word or term!'); + return true; + } + + lastQuery = Date.now(); + + let response; + try { + const s = `https://dictapi.lexicala.com/search?source=global&language=en${pos}&text=${word}`; + response = await httpGET(s, { Authorization: rauth }); + response = JSON.parse(response); + } catch (e) { + msg.resolve('Server did not respond.'); + return true; + } + + if (!response.n_results || response.n_results === 0 || !response.results) { + msg.resolve('Nothing found.'); + return true; + } + + const resolve = []; + for (const def of response.results) { + const wd = def.headword.text; + const wdp = def.headword.pos; + const results = def.senses.map((rep: any) => rep.definition).slice(0, 4); + + resolve.push({ + word: wd, + type: wdp, + results, + }); + } + + const str = resolve.slice(0, 4).map((rep: any) => { + return `${rep.word} (${rep.type}):\n` + rep.results.join('\n'); + }).join('\n'); + + msg.resolve(str); + return true; + }, + aliases: ['df'], + description: 'Find definitions for words', + usage: '[] ' + }); + } +} + +module.exports = DictionPlugin; diff --git a/fun/plugin.json b/fun/plugin.json new file mode 100644 index 0000000..485892f --- /dev/null +++ b/fun/plugin.json @@ -0,0 +1,9 @@ +{ + "main": "plugin.js", + "name": "fun", + "description": "Fun commands", + "version": "1.0.0", + "tags": ["commands", "fun"], + "dependencies": ["simplecommands"], + "npmDependencies": [] +} diff --git a/fun/plugin.ts b/fun/plugin.ts new file mode 100644 index 0000000..e8db1bd --- /dev/null +++ b/fun/plugin.ts @@ -0,0 +1,85 @@ +import { + Plugin, + Configurable, + EventListener, + DependencyLoad +} from '@squeebot/core/lib/plugin'; + +import { IMessage } from '@squeebot/core/lib/types'; + +const alpaca = ['a3d53439', '870b9e10', 'c40c5aad', '77e87cd4', '5815410d', '1855876d', 'd134d47f', '6e0db005', 'b2e471da', 'd083213f', '9ac93967', '1f53ea27', '00ab6411', 'bfdc0079', 'ce29fc6f', '552665d0', '8be91f16', 'b7499ec2', 'b9d52259', '6e505095', '4bc30df8', 'c1854975', 'fe87532c', '9e6cb311', '9e6d6e69', 'f79992bf', '11aa0714', 'c27e712f', '8ee7d28c', 'd79b158f', 'a495bad4', 'df93dc7e', '57ed2f42', '9e2c0722', '3a6c0bab', '4268b737', '490f09bf', 'f6d8fca6', '3d3d96b1', 'f1d82aaa', 'fb1a6e0e', '31a96aee', 'a0cc346f', '9f88d146', 'a55a91a4', '83ee2889', '94c60ee3', '30e66d58', '27870c50', '7adf19c6', '2920ceac', 'ddd62b77', '86a6efe7', '02d2d4f4', 'bebd657c', '1c5a3508', 'c3c6fc4b', '1761ebbc', '7eeaab38', 'd9a12e72', '42356945', 'dff46a1a', '413c741a', 'a7761c60', 'f3316806', '782b8b6c', '6547d625', 'd17bea20', 'fdc5c329', '56bb5991', '9a9ebe09', '8bb0818e', '4692f354', '9d91ce89', 'b3fb3715', '0d03a684', '115af660', '845031e2', '945d26bd', 'd3b1dd25', 'c2cb872f', '0c202666', '357e5195', 'fef6732c', '8a88104e', '1fdbc9ec', 'f6ad84dc', '473eb851', 'd078e723', '5079e902', '8a81a94f', '285c6ef8', '443b7f25', '0ba9ecac', '36d044a9', '7fa1366e', '88dedd53', '7839d770', '50ee9fd7', 'f0a4f672', '74526d28', '639106bf', '8aa61988', 'f6615095', '42385e98', 'ba94be4d', '8c81f5ee', '7bfb19f9', '5cc54d76', '962d2cfb', 'eaee222d', '994575be', '114927a6', 'd3119ed2', 'ce62f835', '0dc3fbb7', 'dc7f629f', '103d03cb', '96e54eb1', 'be395291', 'c469785e', '0c3d13cf', 'f61a1b43', 'bb64b44e', 'c8cdf1e7', '9cd9589a', '92f745d3', 'c9a168e4', 'b5a59c3e', 'ae5e3337', 'ff2a8f2d', 'eef41a7e', 'ef4fad05', '4a2f3a69', '7f9af8df', '9731dcba', 'f4b64638', 'f19b7a28', '87b5f9bb', '8e261176', '6353444c', '68cf22f1', 'e8c05fa5', '0d080496', 'f15c48a1', 'd0dcb52d', '1774d947', 'ead69e2f', '16a9d0d0', 'bae38658', '1b802c4b', '4502f826', '0a4bbb94', '957892e3', '54b50c3b', '6d0e742e', 'e3231c9d', '4e88f997', 'de8a6111', '9d9ff907', 'dbecd1de', 'c11dd818', '6ce07218', 'e73aec0b', 'e5a030b1', '52c22730', '6648a887', 'dabec190', '079c58e1', '94b7882c', 'ac905c5f', '11845f34', '87a871cd', 'bacb3cb9', '9f8584fd', 'f6788d62', 'fae5c010', '8d73c238', '1fe8c363', 'c7408956', '313bd1fb', '0a2de87a', '0fec45a8', '5a23fe9f', '9496c280', 'dc04d424', '1dee8f8b', '44f222a4', 'a0d96fd5', '70563299', 'b3e7bfd2', '49f47c49', '20d103e1', '7ebe873a', '7b13e329', '85cc1c96', 'b569d2d5', '61ccd52f', '8e99ecad', '63b87b93', '3b2e9d58', 'c07a484a', '3e414e22', 'ea030f21', 'd94348a6', '87b486cf', 'fbd5f38e', '81d7c9fc', '4a8aec26', '0d5cb865', 'f840c174', '8762c66d', '5a99399c', 'c00c8cf1', '122ae6de', 'adb4efc5', '84e09e92', 'b21658fd', 'c25cfcaf', '0ac96278', 'a8e49500', '05a639ef', 'bb10bed8', '6a844be3', 'ab06cede', 'b9a8c57e', '6e6b1745', 'a8bf0e1d', 'ae9c19bf', '0392b422', '9dc049e4', '61efd0d2', 'f0759c3d', '29482eb7', 'aa059afe', '7a03c62f', 'a8a8b79c', '59629470', 'c1949ac1', '721db006', '486a31f9', '793cf9cd', '81eb3518', '319c6398', 'd35b269d', '1d1a5014', 'a10506a9', 'ed61196c', '5fd4c99e', '1d92b5ab', 'c34a7a0e', 'ad19db59', '25346668', '0b17393e', '466bd057', 'f9860642', '7d74f7d0', '6719ce53', '95864f2a', 'e810e07c', '9a0a127d', '428f9e7c', '98d645ba', '444a27d5', '999f6fe9']; + +@Configurable({ + simpleReplies: { + ping: 'pong' + } +}) +class FunPlugin extends Plugin { + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + this.emit('pluginUnloaded', this); + } + } + + initialize(): void { + // Hug action + this.on('message', (data: IMessage) => { + const messageRefined = data.text.toLowerCase().trim().replace(/@/g, ''); + const myName = data.source.me.name.toLowerCase(); + if (messageRefined.indexOf('hugs ' + myName) !== -1) { + // Add a little bit of delay (personal preference) + setTimeout(() => { + if (data.sender) { + data.resolve(data.source.format.format('action', 'hugs ' + + data.mention(data.sender))); + } + }, 1000); + } + }); + } + + @DependencyLoad('simplecommands') + addCommands(cmd: any): void { + const cmds: any = []; + if (this.config.config.simpleReplies) { + const simpleReply = this.config.config.simpleReplies; + for (const name in simpleReply) { + cmds.push({ + name, + plugin: this.name, + execute: async (msg: IMessage): Promise => { + msg.resolve(simpleReply[name]); + return true; + }, + hidden: true + }); + } + } + + cmds.push({ + name: 'hug', + plugin: this.name, + execute: async (msg: IMessage): Promise => { + msg.resolve(msg.source.format.format('action', 'hugs %s'), msg.sender?.name); + return true; + }, + hidden: true + }); + + cmds.push({ + name: 'alpaca', + plugin: this.name, + execute: async (msg: IMessage): Promise => { + const rand = Math.floor(Math.random() * alpaca.length); + msg.resolve('http://jocketf.se/c/' + alpaca[rand]); + return true; + }, + hidden: true + }); + + cmd.registerCommand(cmds); + } +} + +module.exports = FunPlugin; diff --git a/gamedig/plugin.json b/gamedig/plugin.json new file mode 100644 index 0000000..44031f7 --- /dev/null +++ b/gamedig/plugin.json @@ -0,0 +1,9 @@ +{ + "main": "plugin.js", + "name": "gamedig", + "description": "Game server status string", + "version": "1.0.0", + "tags": ["commands", "games"], + "dependencies": ["simplecommands"], + "npmDependencies": ["gamedig@^2.0.23"] +} diff --git a/gamedig/plugin.ts b/gamedig/plugin.ts new file mode 100644 index 0000000..4380a61 --- /dev/null +++ b/gamedig/plugin.ts @@ -0,0 +1,116 @@ +import { + Plugin, + Configurable, + EventListener, + DependencyLoad +} from '@squeebot/core/lib/plugin'; + +import { IMessage } from '@squeebot/core/lib/types'; + +import { query } from 'gamedig'; + +interface IMinecraftType { + vanilla: { + raw: { + description: { + extra: [ + { + text: string; + } + ]; + }, + players: { + max: number, + online: number; + }, + version: { + name: string; + } + } + }; +} + +@Configurable({ + games: [] +}) +class GamePlugin extends Plugin { + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + this.emit('pluginUnloaded', this); + } + } + + @DependencyLoad('simplecommands') + addCommands(cmd: any): void { + for (const i in this.config.get('games', [])) { + const game = this.config.get('games', [])[i]; + if (game.game === 'minecraft' && game.host) { + const port = game.port || 25565; + const command: any = { + plugin: this.name, + name: 'minecraft', + execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { + const keys: any[] = []; + let inclPlayers = false; + if (simplified[0] === 'players' || simplified[0] === 'online') { + inclPlayers = true; + } + + keys.push(['field', 'Minecraft', { type: 'title' }]); + + let state; + try { + state = await query({ + type: 'minecraftping', + host: game.host, + port, + }); + if (!state) { + throw new Error(); + } + } catch (e) { + msg.resolve([['field', 'Server is offline.', { type: 'title' }]]); + return true; + } + + if (inclPlayers) { + const players = []; + if (state.players.length > 0) { + for (const j in state.players) { + players.push(state.players[j].name); + } + } + keys.push(['field', players.length ? players.join(', ') : 'No players', { label: 'Players online' }]); + msg.resolve(keys); + return true; + } + + keys.push(['field', state.connect, { label: 'Address' }]); + + const raw = state.raw as IMinecraftType; + if (raw && raw.vanilla && raw.vanilla.raw) { + keys.push(['field', raw.vanilla.raw.description.extra[0].text + .replace(/§./g, '').replace(/\n/g, ' '), { label: 'MOTD', type: 'description' }]); + keys.push(['field', state.players.length + '/' + state.maxplayers, { label: 'Players' }]); + keys.push(['field', raw.vanilla.raw.version.name, { label: 'Version' }]); + } + + msg.resolve(keys); + return true; + }, + description: 'Minecraft server', + usage: '[players]', + aliases: ['mc'] + }; + + if (game.rooms) { + command.source = game.rooms; + } + cmd.registerCommand(command); + } + } + } +} + +module.exports = GamePlugin; diff --git a/package-lock.json b/package-lock.json index 5cf2940..5349e15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,11 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@sindresorhus/is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz", + "integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ==" + }, "@squeebot/core": { "version": "file:../core", "requires": { @@ -451,12 +456,59 @@ } } }, + "@szmarczak/http-timer": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", + "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", + "requires": { + "defer-to-connect": "^2.0.0" + } + }, + "@types/cacheable-request": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", + "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", + "requires": { + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" + } + }, + "@types/cheerio": { + "version": "0.22.22", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.22.tgz", + "integrity": "sha512-05DYX4zU96IBfZFY+t3Mh88nlwSMtmmzSYaQkKN48T495VV1dkHSah6qYyDTN5ngaS0i0VonH37m+RuzSM0YiA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/convert-units": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@types/convert-units/-/convert-units-2.3.3.tgz", "integrity": "sha512-oo4ZyjU0nZMDQFP0AKYk4KvFjydlSpbgXTCdF+TC3d/TL4b85/zi0W5pC97UrzXXpwsbcVEfS01nEgUqKgjSww==", "dev": true }, + "@types/gamedig": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/gamedig/-/gamedig-2.0.1.tgz", + "integrity": "sha512-lMMFmfcVfX4auHXaxyPGswwQn5e/SwoQ/OmdRvpGxf/TpoGgQAyr9UXMxeTfqbaisi1JfhdlyM5mNNcQ3fQI3Q==", + "dev": true + }, + "@types/http-cache-semantics": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" + }, + "@types/keyv": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", + "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", + "requires": { + "@types/node": "*" + } + }, "@types/mathjs": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/@types/mathjs/-/mathjs-6.0.8.tgz", @@ -469,14 +521,119 @@ "@types/node": { "version": "14.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.10.tgz", - "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==", - "dev": true + "integrity": "sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==" + }, + "@types/responselike": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", + "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "requires": { + "@types/node": "*" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "barse": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/barse/-/barse-0.4.3.tgz", + "integrity": "sha1-KJhk15XQECu7sYHmbs0IxUobwMs=", + "requires": { + "readable-stream": "~1.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "cacheable-lookup": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz", + "integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==" + }, + "cacheable-request": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", + "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^2.0.0" + } + }, + "cheerio": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz", + "integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.1", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "requires": { + "graceful-readlink": ">= 1.0.0" + } }, "complex.js": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.0.11.tgz", "integrity": "sha512-6IArJLApNtdg1P1dFtn3dnyzoZBEF0MwMnrfF1exSBRpZYoy4yieMkpZhQDC0uwctw48vii0CFVyHfpgZ/DfGw==" }, + "compressjs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/compressjs/-/compressjs-1.0.3.tgz", + "integrity": "sha1-ldt03VuQOM+AvKMhqw7eJxtJWbY=", + "requires": { + "amdefine": "~1.0.0", + "commander": "~2.8.1" + } + }, "convert-units": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/convert-units/-/convert-units-2.3.4.tgz", @@ -486,26 +643,239 @@ "lodash.keys": "2.3.x" } }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, "decimal.js": { "version": "10.2.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==" }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "defer-to-connect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz", + "integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, "escape-latex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" }, + "event-to-promise": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/event-to-promise/-/event-to-promise-0.7.0.tgz", + "integrity": "sha1-ywffzUGNoiIdkPd+q3E7wjXiCQ8=" + }, "fraction.js": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.0.12.tgz", "integrity": "sha512-8Z1K0VTG4hzYY7kA/1sj4/r1/RWLBD3xwReT/RCrUCbzPszjNQCCsy3ktkU/eaEqX3MYa4pY37a52eiBlPMlhA==" }, + "gamedig": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/gamedig/-/gamedig-2.0.23.tgz", + "integrity": "sha512-RNVRjdnPK1PaeB0ypqkqZQZnCIkINPs5d5ynQFUsptuNwL1rXjKwFOhYhM3euFYq3zOiaGd6yJfnBkizgDWlQg==", + "requires": { + "cheerio": "^1.0.0-rc.3", + "compressjs": "^1.0.2", + "gbxremote": "^0.2.1", + "got": "^11.5.1", + "iconv-lite": "^0.6.2", + "long": "^4.0.0", + "minimist": "^1.2.5", + "moment": "^2.27.0", + "punycode": "^2.1.1", + "varint": "^5.0.0" + } + }, + "gbxremote": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.2.1.tgz", + "integrity": "sha1-hN9PvXgXgNxdaS0krASi1/Bd23w=", + "requires": { + "any-promise": "^1.1.0", + "barse": "~0.4.2", + "event-to-promise": "^0.7.0", + "string-to-stream": "^1.0.1", + "xmlrpc": "^1.3.1" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "got": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.0.tgz", + "integrity": "sha512-k9noyoIIY9EejuhaBNLyZ31D5328LeqnyPNXJQb2XlJZcKakLqN5m6O/ikhq/0lw56kUYS54fVm+D1x57YC9oQ==", + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.1", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "http2-wrapper": { + "version": "1.0.0-beta.5.2", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz", + "integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + } + }, + "iconv-lite": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz", + "integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, "javascript-natural-sort": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", "integrity": "sha1-+eIwPUUH9tdDVac2ZNFED7Wg71k=" }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "keyv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", + "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, "lodash._basebind": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.3.0.tgz", @@ -660,6 +1030,16 @@ "lodash._renative": "~2.3.0" } }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + }, "mathjs": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-8.0.1.tgz", @@ -675,11 +1055,173 @@ "typed-function": "^2.0.0" } }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz", + "integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==" + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "resolve-alpn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz", + "integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==" + }, + "responselike": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", + "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "requires": { + "lowercase-keys": "^2.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "seedrandom": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" }, + "string-to-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz", + "integrity": "sha512-QySF2+3Rwq0SdO3s7BAp4x+c3qsClpPQ6abAmb0DGViiSBAkT5kL6JT2iyzEVP+T1SmzHrQD1TwlP9QAHCc+Sw==", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.1.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, "tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", @@ -694,6 +1236,35 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz", "integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "xmlbuilder": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-8.2.2.tgz", + "integrity": "sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=" + }, + "xmlrpc": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/xmlrpc/-/xmlrpc-1.3.2.tgz", + "integrity": "sha1-JrLqNHhI0Ciqx+dRS1NRl23j6D0=", + "requires": { + "sax": "1.2.x", + "xmlbuilder": "8.2.x" + } } } } diff --git a/package.json b/package.json index 014f097..6f1962e 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,16 @@ "license": "ISC", "dependencies": { "@squeebot/core": "file:../core", + "cheerio": "^1.0.0-rc.3", "convert-units": "^2.3.4", + "gamedig": "^2.0.23", "mathjs": "^8.0.1", "typescript": "^4.1.2" }, "devDependencies": { + "@types/cheerio": "^0.22.22", "@types/convert-units": "^2.3.3", + "@types/gamedig": "^2.0.1", "@types/mathjs": "^6.0.8", "@types/node": "^14.14.10" } diff --git a/squeebot.repo.json b/squeebot.repo.json index 3af80fa..4e79ac0 100644 --- a/squeebot.repo.json +++ b/squeebot.repo.json @@ -1,6 +1,34 @@ { "name": "plugins-evert", "plugins": [ + { + "name": "debug", + "version": "1.0.0" + }, + { + "name": "diction", + "version": "1.0.0" + }, + { + "name": "fun", + "version": "1.0.0" + }, + { + "name": "gamedig", + "version": "1.0.0" + }, + { + "name": "timezone", + "version": "1.0.0" + }, + { + "name": "url-fediverse", + "version": "1.0.0" + }, + { + "name": "urlreply", + "version": "1.0.0" + }, { "name": "utility", "version": "3.0.0" diff --git a/timezone/plugin.json b/timezone/plugin.json new file mode 100644 index 0000000..24d3bff --- /dev/null +++ b/timezone/plugin.json @@ -0,0 +1,9 @@ +{ + "main": "plugin.js", + "name": "timezone", + "description": "Show time from different time zones", + "version": "1.0.0", + "tags": ["commands", "utility", "time"], + "dependencies": ["simplecommands"], + "npmDependencies": [] +} diff --git a/timezone/plugin.ts b/timezone/plugin.ts new file mode 100644 index 0000000..601872e --- /dev/null +++ b/timezone/plugin.ts @@ -0,0 +1,64 @@ +import { + Plugin, + Configurable, + EventListener, + DependencyLoad +} from '@squeebot/core/lib/plugin'; +import { IMessage } from '@squeebot/core/lib/types'; + +const tz: {[key: string]: number} = {'UTC-12': -12, 'UTC-11': -11, 'UTC-10': -10, 'UTC-9': -9, 'UTC-9:30': -9.5, 'UTC-8': -8, 'UTC-7': -7, 'UTC-6': -6, 'UTC-5': -5, 'UTC-4': -4, 'UTC-3': -3, 'UTC-3:30': -3.5, 'UTC-2': -2, 'UTC-1': -1, UTC: 0, 'UTC+1': 1, 'UTC+2': 2, 'UTC+3': 3, 'UTC+4': 4, 'UTC+4:30': 4.5, 'UTC+5': 5, 'UTC+5:30': 5.5, 'UTC+5:45': 5.75, 'UTC+6': 6, 'UTC+6:30': 6.5, 'UTC+7': 7, 'UTC+8': 8, 'UTC+8:30': 8.5, 'UTC+8:45': 8.75, 'UTC+9': 9, 'UTC+9:30': 9.5, 'UTC+10': 10, 'UTC+10:30': 10.5, 'UTC+11': 11, 'UTC+12': 12, 'UTC+12:45': 12.75, 'UTC+13': 13, 'UTC+13:45': 13.75, 'UTC+14': 14, BST: 1, CET: 1, DFT: 1, IST: 1, MET: 1, WAT: 1, WEST: 1, CAT: 2, CEST: 2, EET: 2, HAEC: 2, MEST: 2, SAST: 2, USZ1: 2, WAST: 2, AST: 3, EAT: 3, EEST: 3, FET: 3, IDT: 3, IOT: 3, MSK: 3, SYOT: 3, TRT: 3, IRST: 3.5, AMT: 4, AZT: 4, GET: 4, GST: 4, MUT: 4, RET: 4, SAMT: 4, SCT: 4, VOLT: 4, AFT: 4.5, IRDT: 4.5, MAWT: 5, MVT: 5, ORAT: 5, PKT: 5, TFT: 5, TJT: 5, TMT: 5, UZT: 5, YEKT: 5, IndianST: 5.5, SLST: 5.5, NPT: 5.75, BIOT: 6, BTT: 6, KGT: 6, OMST: 6, VOST: 6, CCT: 6.5, MMT: 6.5, ACT: 6.5, CXT: 7, DAVT: 7, HOVT: 7, ICT: 7, KRAT: 7, THA: 7, WIT: 7, AWST: 8, BDT: 8, CHOT: 8, CIT: 8, CT: 8, HKT: 8, HOVST: 8, IRKT: 8, MST: 8, MYT: 8, PHT: 8, SGT: 8, SST: 8, ULAT: 8, WST: 8, CWST: 8.5, CHOST: 9, EIT: 9, JST: 9, KST: 9, TLT: 9, ULAST: 9, YAKT: 9, ACST: 9.5, AEST: 10, CHST: 10, CHUT: 10, DDUT: 10, PGT: 10, VLAT: 10, ACDT: 10.5, LHST: 10.5, AEDT: 11, KOST: 11, MIST: 11, NCT: 11, NFT: 11, PONT: 11, SAKT: 11, SBT: 11, SRET: 11, VUT: 11, FJT: 12, GILT: 12, MAGT: 12, MHT: 12, NZST: 12, PETT: 12, TVT: 12, WAKT: 12, CHAST: 12.75, NZDT: 13, PHOT: 13, TKT: 13, TOT: 13, CHADT: 13.75, LINT: 14, AZOT: -1, CVT: -1, EGT: -1, BRST: -2, FNT: -2, PMDT: -2, UYST: -2, NDT: -2.5, ADT: -3, AMST: -3, ART: -3, BRT: -3, CLST: -3, FKST: -3, GFT: -3, PMST: -3, PYST: -3, ROTT: -3, UYT: -3, NST: -3.5, NT: -3.5, AtST: -4, BOT: -4, CLT: -4, COST: -4, ECT: -4, EDT: -4, FKT: -4, GYT: -4, PYT: -4, VET: -4, CDT: -5, COT: -5, EASST: -5, EST: -5, PET: -5, CST: -6, EAST: -6, GALT: -6, MDT: -6, PDT: -7, AKDT: -8, CIST: -8, PST: -8, AKST: -9, GAMT: -9, GIT: -9, HADT: -9, MART: -9.5, MIT: -9.5, CKT: -10, HAST: -10, TAHT: -10, NUT: -11, BIT: -12, GMT: 0, WET: 0}; + +// Offset timezone from UTC +function offsetTZ(offset: number): number { + const utc = new Date(new Date().toUTCString()).getTime(); + return utc + 3600000 * offset; +} + +// Offset timezone from UTC (readable string) +function offsetTZStr(offset: number, tzn: string | null): string { + const real = offset >= 0 ? '+' + offset : offset; + return new Date(offsetTZ(offset)).toUTCString().replace('GMT', 'UTC') + real + (tzn ? ' (' + tzn + ')' : ''); +} + +function timeNow(short: string): string | null { + short = short.toUpperCase(); + const offset = tz[short]; + if (offset == null) { return null; } + return offsetTZStr(offset, short.indexOf('UTC') !== 0 ? short : null); +} + +@Configurable({ + timezone: 'UTC' +}) +class TZPlugin extends Plugin { + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + this.emit('pluginUnloaded', this); + } + } + + @DependencyLoad('simplecommands') + addCommands(cmd: any): void { + cmd.registerCommand({ + name: 'timezone', + plugin: this.name, + execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { + const cfg = this.config.get('timezone', 'UTC'); + const tn = timeNow(simplified[0] || cfg); + if (!tn) { + msg.resolve('Unknown or unlisted timezone abbreviation'); + return true; + } + + msg.resolve(tn); + return true; + }, + aliases: ['tz'], + description: 'Date and Time in a certain time zone', + usage: '[abbrv]|UTC<+|->' + }); + } +} + +module.exports = TZPlugin; diff --git a/url-fediverse/plugin.json b/url-fediverse/plugin.json new file mode 100644 index 0000000..e6c9ef5 --- /dev/null +++ b/url-fediverse/plugin.json @@ -0,0 +1,9 @@ +{ + "main": "plugin.js", + "name": "url-fediverse", + "description": "URLReply Fediverse", + "version": "1.0.0", + "tags": ["urlreply"], + "dependencies": ["urlreply"], + "npmDependencies": [] +} diff --git a/url-fediverse/plugin.ts b/url-fediverse/plugin.ts new file mode 100644 index 0000000..d03525a --- /dev/null +++ b/url-fediverse/plugin.ts @@ -0,0 +1,99 @@ +import { httpGET, sanitizeEscapedText } from '@squeebot/core/lib/common'; +import { + Plugin, + EventListener, + DependencyLoad +} from '@squeebot/core/lib/plugin'; + +import { IMessage } from '@squeebot/core/lib/types'; + +async function mastodonResponse(msg: IMessage, url: string, type = 'Mastodon'): Promise { + let murl; + if (type === 'Mastodon') { + murl = url.match(/^(https?):\/\/((?:[\w\d-]+\.)*[\w\d-]+\.\w{2,16})\/(?:users\/)?@?([\w-_]+)\/(?:statuses\/)?(\d+[^&#?\s/])/i); + } else if (type === 'Pleroma') { + murl = url.match(/^(https?):\/\/((?:[\w\d-]+\.)*[\w\d-]+\.\w{2,16})\/(notice)\/([^&#?\s/]+)/i); + } + if (!murl) { + return false; + } + + const url2go = `${murl[1]}://${murl[2]}/api/v1/statuses/${murl[4]}`; + let data; + + try { + data = await httpGET(url2go); + if (!data) { + throw new Error('No API response, probably not ' + type + ' after all.') + } + data = JSON.parse(data); + } catch (e) { + return false; + } + + let content = data.content; + + if (!content) { + return false; + } + + if (data.spoiler_text) { + content = '[CW] ' + data.spoiler_text; + } + + const keys = []; + let end = sanitizeEscapedText(content.replace(/<\/p>/g, '\n') // Add newlines to paragraph endings + .replace(/
/g, '\n') // Add newlines instead of
+ .replace(/(<([^>]+)>)/ig, '')); // Strip the rest of the HTML out + + if (end.length > 220) { + end = end.substring(0, 220) + '…'; + } + + keys.push(['field', type, { color: 'cyan', type: 'title' }]); + keys.push(['field', data.favourites_count.toString(), { color: 'red', label: ['★', 'Favourites'], type: 'metric' }]); + keys.push(['field', data.reblogs_count.toString(), { color: 'green', label: ['↱↲', 'Reblogs'], type: 'metric' }]); + keys.push(['bold', '@' + data.account.acct + ':']); + keys.push(['field', end, { type: 'content' }]); + + msg.resolve(keys); + return true; +} + +class FediResponsePlugin extends Plugin { + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + this.emit('pluginUnloaded', this); + } + } + + @DependencyLoad('urlreply') + addUrlReply(urlreply: any): void { + urlreply.registerHandler(this.name, 'html/mastodon', + async (url: string, msg: IMessage, title: string, body: any): Promise => { + const type = title === 'Pleroma' ? 'Pleroma' : 'Mastodon'; + let pass = false; + + if (type === 'Pleroma') { + pass = true; + } else { + const tag = body('a[href="https://joinmastodon.org/"]'); + if (tag && tag.text() && tag.text().indexOf('Mastodon') !== -1) { + pass = true; + } + } + + if (pass) { + const mastodonTest = await mastodonResponse(msg, url, type); + if (mastodonTest) { + return true; + } + } + + return false; + }); + } +} + +module.exports = FediResponsePlugin; diff --git a/urlreply/plugin.json b/urlreply/plugin.json new file mode 100644 index 0000000..4668470 --- /dev/null +++ b/urlreply/plugin.json @@ -0,0 +1,9 @@ +{ + "main": "plugin.js", + "name": "urlreply", + "description": "Fetch titles from web pages, specifically made for IRC", + "version": "1.0.0", + "tags": ["irc"], + "dependencies": [], + "npmDependencies": ["cheerio@^1.0.0-rc.3"] +} diff --git a/urlreply/plugin.ts b/urlreply/plugin.ts new file mode 100644 index 0000000..9886f02 --- /dev/null +++ b/urlreply/plugin.ts @@ -0,0 +1,384 @@ +import { httpGET, parseTimeToSeconds, toHHMMSS } from '@squeebot/core/lib/common'; +import { logger } from '@squeebot/core/lib/core'; +import { + Plugin, + Configurable, + EventListener, +} from '@squeebot/core/lib/plugin'; +import { IMessage } from '@squeebot/core/lib/types'; + +import cheerio from 'cheerio'; +import * as urllib from 'url'; + +interface URLHandler { + plugin: string; + action: Function; +} + +let urlHandlers: {[key: string]: URLHandler} = {}; +let htmlHandlers: any[] = []; + +const urlRegex = /(((ftp|https?):\/\/)[-\w@:%_+.~#?,&//=]+)/g; + +function findUrls(text: string): string[] { + const source = (text || '').toString(); + const urlArray = []; + let matchArray = urlRegex.exec(source); + + while (matchArray !== null) { + urlArray.push(matchArray[0]); + matchArray = urlRegex.exec(source); + } + + return urlArray; +} + +// http://stackoverflow.com/a/22149575 +function ytDuration(source: string): string { + const a = source.match(/\d+/g); + let res; + + if (!a) { + return ''; + } + + if (source.indexOf('M') >= 0 && source.indexOf('H') === -1 && source.indexOf('S') === -1) { + res = [0, a[0], 0]; + } + + if (source.indexOf('H') >= 0 && source.indexOf('M') === -1) { + res = [a[0], 0, a[1]]; + } + + if (source.indexOf('H') >= 0 && source.indexOf('M') === -1 && source.indexOf('S') === -1) { + res = [a[0], 0, 0]; + } + + let duration = 0; + + if (a.length === 3) { + duration = duration + parseInt(a[0], 10) * 3600; + duration = duration + parseInt(a[1], 10) * 60; + duration = duration + parseInt(a[2], 10); + } + + if (a.length === 2) { + duration = duration + parseInt(a[0], 10) * 60; + duration = duration + parseInt(a[1], 10); + } + + if (a.length === 1) { + duration = duration + parseInt(a[0], 10); + } + + return toHHMMSS(duration.toString()); +} + +async function dailymotionFromId(id: string, msg: IMessage): Promise { + const url = 'https://api.dailymotion.com/video/' + id + '?fields=id,title,owner,owner.screenname,duration,views_total'; + + let data = await httpGET(url); + + try { + data = JSON.parse(data); + } catch (e) { + return false; + } + + const keys = []; + + keys.push(['field', 'Dailymotion', { type: 'title' }]); + keys.push(['field', data.title, { type: 'description' }]); + keys.push(['field', data.views_total.toString(), { label: 'Views', type: 'metric' }]); + keys.push(['field', data.duration.toString(), { label: 'Duration', type: 'duration' }]); + keys.push(['field', data['owner.screenname'], { label: ['By'] }]); + + msg.resolve(keys); + + return true; +} + +async function getSoundcloudFromUrl(plugin: URLReplyPlugin, url: string, msg: IMessage): Promise { + const token = plugin.config.get('tokens.soundcloud'); + if (!token) { + return false; + } + + const sAPI = 'https://api.soundcloud.com/resolve?url=' + encodeURIComponent(url) + '&client_id=' + token; + + let data = await httpGET(sAPI); + + try { + data = JSON.parse(data); + } catch (e) { + return false; + } + + if (!data) { + return false; + } + const keys = []; + + keys.push(['field', 'SoundCloud' + ((data.kind === 'playlist') ? ' Playlist' : ''), { type: 'title', color: 'gold' }]); + keys.push(['field', data.title, { type: 'description' }]); + + if (data.kind === 'track') { + keys.push(['field', data.playback_count.toString(), { color: 'green', label: ['▶', 'Plays'], type: 'metric' }]); + keys.push(['field', data.favoritings_count.toString(), { color: 'red', label: ['♥', 'Favourites'], type: 'metric' }]); + } else { + keys.push(['field', data.track_count.toString(), { label: 'Tracks', type: 'metric' }]); + } + + keys.push(['field', Math.floor(data.duration / 1000).toString(), { label: 'Duration', type: 'duration' }]); + keys.push(['field', data.user.username, { label: ['By'] }]); + + msg.resolve(keys); + + return true; +} + +async function getYoutubeFromVideo( + plugin: URLReplyPlugin, + id: string, + full: urllib.URL, + msg: IMessage): Promise { + const gtoken = plugin.config.get('tokens.google'); + + if (!gtoken) { + return false; + } + + const gAPI = 'https://www.googleapis.com/youtube/v3/videos?id=' + id + '&key=' + + gtoken + '&part=snippet,contentDetails,statistics'; + + let data = await httpGET(gAPI); + try { + data = JSON.parse(data); + } catch (e) { + return false; + } + + if (!data || !data.items || !data.items.length) { + msg.resolve('Video does not exist or is private.'); + return false; + } + + const vid = data.items[0]; + const time = full.searchParams.get('t'); + let live = false; + + if (!vid.snippet) { + return false; + } + + const keys = []; + + if (vid.snippet.liveBroadcastContent === 'live') { + live = true; + keys.push(['field', '[LIVE]', {color: 'red'}]); + } + + if (time) { + let tag = parseInt(time, 10); + if (tag != null && !isNaN(tag)) { + if (time.indexOf('s') !== -1 || time.indexOf('m') !== -1 || time.indexOf('h') !== -1) { + tag = parseTimeToSeconds(time); + } + keys.push(['field', `[${toHHMMSS(tag)}]`, {color: 'red'}]); + } + } + + keys.push(['field', 'You' + msg.source.format.color('brown', 'Tube'), { color: 'white', type: 'title' }]); + keys.push(['field', vid.snippet.title, {type: 'description'}]); + + keys.push(['field', vid.statistics.viewCount.toString(), { type: 'metric', label: 'Views' }]); + + if (!live) { + keys.push(['field', ytDuration(vid.contentDetails.duration.toString()), { label: 'Duration' }]); + } + + if (vid.statistics && vid.statistics.likeCount != null) { + const likeCount = vid.statistics.likeCount.toString(); + const dislikeCount = vid.statistics.dislikeCount.toString(); + + keys.push(['field', likeCount, { color: 'limegreen', label: ['▲', 'Likes'], type: 'metric' }]); + keys.push(['field', dislikeCount, { color: 'red', label: ['▼', 'Dislikes'], type: 'metric' }]); + } + + keys.push(['field', vid.snippet.channelTitle, { label: ['By'] }]); + + msg.resolve(keys); + + return true; +} + +@Configurable({ + tokens: { + google: null, + soundcloud: null + } +}) +class URLReplyPlugin extends Plugin { + registerHandler(plugin: string, match: string, handler: Function): void { + if (!handler || typeof handler !== 'function') { + throw new Error('Expected handler function as third argument.'); + } + + if (plugin !== 'urlreply') { + logger.log('[urlreply] %s has added an handler for "%s"', plugin, match); + } + + if (match.indexOf('html') === 0) { + htmlHandlers.push([plugin, handler]); + } else { + urlHandlers[match] = { + plugin, action: handler + }; + } + } + + resetHandlers(): void { + urlHandlers = {}; + htmlHandlers = []; + + // YouTube + this.registerHandler(this.name, 'youtube.com/', async (url: urllib.URL, msg: IMessage, data: any) => { + const det = url.searchParams.get('v'); + + if (!det) { + return false; + } + + return getYoutubeFromVideo.apply(this, [this, det, url, msg]); + }); + + this.registerHandler(this.name, 'youtu.be/', async (url: urllib.URL, msg: IMessage, data: any) => { + const det = url.pathname.substring(1); + + if (!det) { + return false; + } + + return getYoutubeFromVideo.apply(this, [this, det, url, msg]); + }); + + // Dailymotion + this.registerHandler(this.name, 'dailymotion.com/video/', async (url: urllib.URL, msg: IMessage, data: any) => { + const det = url.href.match('/video/([^?&#]*)'); + + if (!det) { + return false; + } + + return dailymotionFromId.apply(this, [det[1], msg]); + }); + + // SoundCloud + this.registerHandler(this.name, 'soundcloud.com/', async (url: urllib.URL, msg: IMessage, data: any) => { + return getSoundcloudFromUrl.apply(this, [this, url.href, msg]); + }); + } + + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + this.emit('pluginUnloaded', this); + } + } + + initialize(): void { + this.resetHandlers(); + this.on('message', async (msg: IMessage) => { + // Find URLS in the message + const urls = findUrls(msg.text); + if (!urls.length) { + return; + } + + const url = urls[0]; + let matched = false; + + // Find handlers matching this URL + for (const handler in urlHandlers) { + const obj = urlHandlers[handler]; + if (url.indexOf(handler) !== -1 || !obj.action) { + try { + const urlParsed = new urllib.URL(url); + + matched = await obj.action.apply(this, [urlParsed, msg]); + } catch (e) { + logger.error(e.stack); + matched = false; + } + break; + } + } + + // If there were no matches, pull the title of the website + if (!matched) { + try { + const data = await httpGET(url, {}, true); + if (!data) { return; } + + const full = cheerio.load(data); + const head = full('head'); + + if (!head) { return; } + + const titleEl = head.find('title'); + + if (!titleEl || !titleEl.length) { return; } + let title = titleEl.eq(0).text(); + title = title.replace(/\n/g, ' ').trim(); + title = msg.source.format.strip(title); + + // Try HTML handlers + let htmlHandle = false; + for (const k in htmlHandlers) { + const handler = htmlHandlers[k]; + if (htmlHandle) { break; } + // Ensure handler is a function + if (!handler[1] || typeof handler[1] !== 'function') { continue; } + try { + htmlHandle = await handler[1].apply(this, [url, msg, title, full]); + } catch (e) { + logger.error(e); + htmlHandle = false; + } + } + + if (htmlHandle) { return; } + if (title.length > 140) { title = title.substring(0, 140) + '…'; } + + msg.resolve(msg.source.format.color('purple', '[ ') + title + msg.source.format.color('purple', ' ]')); + } catch (e) { + logger.error(e); + } + } + }); + } + + @EventListener('pluginUnloaded') + unloadedPlugin(plugin: string | Plugin): void { + const id = typeof plugin === 'string' ? plugin : plugin.manifest.name; + + // Delete URL handlers for plugin + for (const i in urlHandlers) { + if (urlHandlers[i].plugin === id) { + delete urlHandlers[i]; + } + } + + // Delete HTML handlers for plugin + const resolve = []; + for (const i in htmlHandlers) { + const k = htmlHandlers[i]; + if (k[0] !== id) { + resolve.push(k); + } + } + htmlHandlers = resolve; + } +} + +module.exports = URLReplyPlugin; diff --git a/utility/plugin.ts b/utility/plugin.ts index 5eaf29e..9aa8737 100644 --- a/utility/plugin.ts +++ b/utility/plugin.ts @@ -1,6 +1,5 @@ import path from 'path'; import net from 'net'; -import util from 'util'; import cprog from 'child_process'; import convert from 'convert-units'; @@ -114,6 +113,61 @@ function rgbToHex(r: number, g: number, b: number): string { return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } +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; + } + + // Red is max + else if (cmax === r) { + h = ((g - b) / delta) % 6; + } + + // Green is max + else if (cmax === g) { + h = (b - r) / delta + 2; + } + + // Blue is max + 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 }; +} + 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; @@ -183,7 +237,7 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { let response = ''; let strArr; let i; - let text = msg.data.split(' ').slice(2).join(' '); + let text = msg.text.split(' ').slice(2).join(' '); try { switch (simplified[0] ? simplified[0].toUpperCase() : null) { @@ -226,7 +280,7 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { let response = ''; let i; - let text = msg.data.split(' ').slice(2).join(' '); + let text = msg.text.split(' ').slice(2).join(' '); try { switch (simplified[0] ? simplified[0].toUpperCase() : null) { @@ -261,7 +315,7 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { name: 'base64', execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { let response = ''; - const text = msg.data.split(' ').slice(2).join(' '); + const text = msg.text.split(' ').slice(2).join(' '); try { switch (simplified[0] ? simplified[0].toUpperCase() : null) { @@ -324,7 +378,7 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { cmds.push({ name: 'converttime', execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { - const str = msg.data.split(' ').slice(1).join(' '); + const str = msg.text.split(' ').slice(1).join(' '); if (!str) { msg.resolve('Invalid input'); @@ -342,7 +396,7 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { cmds.push({ name: 'reconverttime', execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { - const str = msg.data.split(' ').slice(1).join(' '); + const str = msg.text.split(' ').slice(1).join(' '); if (!str) { msg.resolve('Invalid input'); @@ -364,13 +418,14 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { return true; } - const wholeRow = msg.data.split(' ').slice(1).join(' '); + const wholeRow = msg.text.split(' ').slice(1).join(' '); - opMath(wholeRow).then((repl) => { + try { + const repl = await opMath(wholeRow); msg.resolve(repl); - }, (e) => { + } catch (e) { msg.resolve('Could not evaluate expression:', e.message); - }); + } return true; }, @@ -386,13 +441,14 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { return true; } - const wholeRow = msg.data.split(' ').slice(1).join(' '); + const wholeRow = msg.text.split(' ').slice(1).join(' '); - opMath(wholeRow, 'simplify').then((repl) => { + try { + const repl = await opMath(wholeRow, 'simplify'); msg.resolve(repl); - }, () => { - msg.resolve('Could not evaluate expression!'); - }); + } catch (e) { + msg.resolve('Could not evaluate expression:', e.message); + } return true; }, @@ -401,40 +457,6 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { description: 'Simplify a math expression' }); - cmds.push({ - name: 'evaljs', - execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { - if (!simplified[0]) { - return true; - } - const script = msg.data.split(' ').slice(1).join(' '); - - // Disallow child_process when shell is disallowed - if ((script.indexOf('child_process') !== -1 || - script.indexOf('cprog') !== -1 || - script.indexOf('fork') !== -1) && - !plugin.config.config.allowShell) { - msg.resolve('Error: child_process is not allowed in evaljs due to security reasons.'); - return true; - } - - try { - const mesh = eval(script); /* eslint no-eval: off */ - if (mesh === undefined) { - return true; - } - msg.resolve(util.format(mesh)); - } catch (e) { - msg.resolve('Error: ' + e.message); - } - - return true; - }, - description: 'Execute JavaScript in a command context', - permissions: ['system_execute'], - hidden: true - }); - cmds.push({ name: 'userid', execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { @@ -479,7 +501,7 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { return true; } - const fullmsg = msg.data.split(' ').slice(1).join(' '); + 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'); @@ -503,6 +525,37 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { usage: '[rgb](, , )| ' }); + cmds.push({ + name: 'rgb2hsl', + execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { + 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](, , )| ' + }); + cmds.push({ name: 'hex2rgb', execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { @@ -743,42 +796,6 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { aliases: ['rnum', 'rand'] }); - if (plugin.config.config.allowShell) { - logger.warn('WARNING! Shell command execution is enabled! Make absolutely sure that there is proper authentication!'); - if (process.getuid && process.getuid() === 0) { - logger.warn('NEVER run Squeebot as root! Run `useradd squeebot`! We are not responsible for possible security leaks!'); - } - - cmds.push({ - name: 'sh', - execute: async (msg: IMessage, spec: any, prefix: string, ...simplified: any[]): Promise => { - const stripnl = (simplified[0] !== '-n'); - const cmd = simplified.slice(stripnl ? 0 : 1).join(' '); - if (!cmd) { - msg.resolve('Nothing to execute!'); - return true; - } - - cprog.exec(cmd, {shell: '/bin/bash'}, (error, stdout, stderr) => { - if (stdout) { - if (stripnl) { stdout = stdout.replace(/\n/g, ' ;; '); } - - return msg.resolve(stdout); - } - - msg.resolve('Error executing command.'); - logger.error(stderr || error); - }); - - return true; - }, - description: 'Run raw shell command.', - usage: '', - hidden: true, - permissions: ['system_execute'], - }); - } - commands.registerCommand(cmds.map((x: any) => { x.plugin = plugin.manifest.name; return x; @@ -786,27 +803,10 @@ function addCommands(plugin: UtilityPlugin, commands: any): void { } @Configurable({ - allowShell: false, - googleapikey: null, ipfsGateway: 'https://ipfs.io', randomMax: 64 }) class UtilityPlugin extends Plugin { - bindEvents(): void { - this.on('message', (msg: IMessage) => { - // Pre-regex check - if (msg.data.indexOf('ipfs://') === -1 && msg.data.indexOf('Qm') === -1) { - return; - } - - // IPFS urlify - const mmatch = msg.data.match(/(?:ipfs:\/\/|\s|^)(Qm[\w\d]{44})(?:\s|$)/); - if (mmatch && mmatch[1]) { - msg.resolve(this.config.config.ipfsGateway + '/ipfs/' + mmatch[1]); - } - }); - } - @DependencyLoad('simplecommands') addCommands(cmd: any): void { addCommands(this, cmd); @@ -821,8 +821,18 @@ class UtilityPlugin extends Plugin { } initialize(): void { - this.bindEvents(); - this.emit('pluginLoaded', this); + 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]); + } + }); } }