Simp-O-Matic

Dumb Discord bot in TS.
git clone git://git.knutsen.co/Simp-O-Matic
Log | Files | Refs | README | LICENSE

commit 2fd6c61cd53a52cbb22848867fa38212daa1cc97
parent e538169498c7a28c5a71982e91c653cbf5aa25cc
Author: Demonstrandum <moi@knutsen.co>
Date:   Fri, 13 Nov 2020 02:31:07 +0000

Add Dockerfile, replace ytdl-core with youtube-dl (Python), add text to speech, migrate from using Pastebin, make aliases case insensitive, +more.

Diffstat:
M.gitignore | 5++---
ADockerfile | 25+++++++++++++++++++++++++
Mgenerate_secrets.sh | 5++---
Alib/api/jsonblob.ts | 15+++++++++++++++
Dlib/api/pastebin.ts | 32--------------------------------
Mlib/commands/abbreviate.ts | 9++++++++-
Mlib/commands/alias.ts | 1+
Alib/commands/tts.ts | 43+++++++++++++++++++++++++++++++++++++++++++
Mlib/commands/vc.ts | 115+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mlib/commands/youtube.ts | 10++--------
Mlib/default.ts | 5+++++
Mlib/extensions.ts | 27+++++++++++++++++++++++++++
Mlib/main.ts | 27++++++++++++---------------
Mlib/utils.ts | 6+++---
Mpackage.json | 7++-----
Myarn.lock | 131+++++++++++++++++++++++--------------------------------------------------------
16 files changed, 259 insertions(+), 204 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -4,7 +4,7 @@ # Secrets! secrets.json -.env +.env* export_secrets.sh # Node / NPM / Yarn @@ -35,4 +35,4 @@ desktop.ini # Some ide/source code editor folders .idea/ -.vscode/- \ No newline at end of file +.vscode/ diff --git a/Dockerfile b/Dockerfile @@ -0,0 +1,25 @@ +FROM node:14-alpine + +# Dependencies +RUN apk add --update nodejs git ffmpeg espeak yarn python3 + +# Prepare /app +RUN mkdir /app +WORKDIR /app +COPY package.json yarn.lock /app/ +RUN yarn install + +# --- Build and run --- +RUN mkdir -p /app/build /app/public +# Choose which .env to use. +COPY .env.DEBUG /app/.env +COPY heart.png generate_secrets.sh HELP.md lib \ + clone_nocheckout.sh tsconfig.json tslint.json \ + web bot.json /app/ +# Build +RUN cp /app/bot.json /app/generate_secrets.sh /app/HELP.md /app/build/ +RUN /app/node_modules/.bin/tsc -b /app/tsconfig.json + +# Run +ENTRYPOINT ["sh", "-c", "source /app/.env && \"$@\"", "-s"] +CMD ["yarn", "start"] diff --git a/generate_secrets.sh b/generate_secrets.sh @@ -52,9 +52,8 @@ cat <<- JSON "darksky": { "key": "$DARKSKY_KEY" }, - "pastebin": { - "key": "$PASTEBIN_KEY", - "password": "$PASTEBIN_PASSWORD" + "jsonblob": { + "key": "$JSONBLOB_ID" } } JSON diff --git a/lib/api/jsonblob.ts b/lib/api/jsonblob.ts @@ -0,0 +1,15 @@ +import JSONBlobClient from 'jsonblob'; + +const JSONBLOB_ID = process.env['JSONBLOB_ID']; + +const client = new JSONBlobClient(JSONBLOB_ID); + +export const latest = () => new Promise((resolve, reject) => { + client.getBlob(JSONBLOB_ID) + .then(raw => resolve(raw)) + .catch(er => reject(er)); +}); + +export const update = async (stringified : string) => { + return await client.updateBlob(JSON.parse(stringified), JSONBLOB_ID); +}; diff --git a/lib/api/pastebin.ts b/lib/api/pastebin.ts @@ -1,32 +0,0 @@ -import paste from 'better-pastebin'; - -const PASTE_USER = "knutsen"; -const PASTE_PASS = process.env['PASTEBIN_PASSWORD']; -const PASTE_ID = "V37uQYQB"; -export const pastebin_url = `https://pastebin.com/${PASTE_ID}`; - -paste.setDevKey(process.env['PASTEBIN_KEY']); - -export const pastebin_latest = () => new Promise((resolve, reject) => { - paste.get(PASTE_ID, (succ, res) => { - if (!succ) - return reject('Error getting paste.'); - resolve(JSON.parse(res)); - }); -}); - -export const pastebin_update = async (stringified : string) => { - await paste.login(PASTE_USER, PASTE_PASS, async (succ, res) => { - if (!succ) - return Promise.reject(console.log('Could not log in.')); - - return await paste.edit(PASTE_ID, { - contents: stringified - }, (worked, _) => { - if (!worked) - return Promise.reject('Error updating paste...'); - return Promise.resolve('Pastebin edit successful!'); - }); - }); - return Promise.resolve('Pastebin update successful'); -}; diff --git a/lib/commands/abbreviate.ts b/lib/commands/abbreviate.ts @@ -1,4 +1,11 @@ export default async (home_scope: HomeScope) => { const { message, args } = home_scope; - message.channel.send(args.map(w => [...w].find(c => isNaN(c as any)?c.toLowerCase() != c.toUpperCase():c) || '').join('').toUpperCase() + ` (${args.join(' ')})`) + const words = args.map(w => w.trim().capitalize()); + message.channel.send(words.map(w => + [...w].find(c => + isNaN(c as any) + ? c.toLowerCase() != c.toUpperCase() + : c) + || '') + .join('').toUpperCase() + ` (${words.join(' ')})`) } diff --git a/lib/commands/alias.ts b/lib/commands/alias.ts @@ -54,6 +54,7 @@ export default (home_scope: HomeScope) => { if (args[0][0] === CONFIG.commands.prefix) args[0] = args[0].tail(); + args[0] = args[0].toLowerCase(); if (args[1][0] === CONFIG.commands.prefix) args[1] = args[1].tail(); diff --git a/lib/commands/tts.ts b/lib/commands/tts.ts @@ -0,0 +1,43 @@ +import * as cp from 'child_process'; +import { VoiceConnection } from 'discord.js'; + +export default async (hs : HomeScope) => { + const { message, args, INSTANCE_VARIABLES } = hs; + + if (!message.guild) { + message.answer("Stop talkingQ to yourself, loser."); + return; + } + + const guild = message.guild.id + const GID = INSTANCE_VARIABLES.guilds[guild]; + + if (!GID.vc) { + message.answer("Let me join your voice-chat first."); + } + + const text = args.join(' '); + + // Generate speech. + const child = cp.spawn('espeak', ['-s170', text, '--stdout'], { + stdio: ['ignore', 'pipe', 'ignore'] + }); + + const stream = child.stdout; + + const temp = GID.vc.play(stream); + + temp.on('finish', () => { + temp.destroy(); + if (GID.vc_current_stream) { + // THIS DOES NOT WORK. I cannot seem to get the song to + // resume (if there was a song playing). I've tried + // many ways, but someone else is going to have to figure + // it out. + GID.vc_dispatcher = GID.vc.play(GID.vc_current_stream); + console.log("Resumed playback."); + } + + console.log("Finished speaking."); + }); +}; diff --git a/lib/commands/vc.ts b/lib/commands/vc.ts @@ -1,21 +1,18 @@ import { TextChannel } from 'discord.js'; -import ytdl from 'ytdl-core'; +import * as cp from 'child_process'; -const DL_OPTIONS : any = { - filter: 'audioonly', - dlChunkSize: 0, - quality: 'highestaudio', - // Helps fix random cut-off towards end of playback. - highWaterMark: 1 << 25 -}; +const YTDL_OPTIONS = [ + '--audio-format', 'opus', + '-i', '--no-continue', '-o', '-' // Send to STDOUT. +]; -export default async(home_scope: HomeScope) => { +export default async (home_scope: HomeScope) => { const { message, args, CONFIG, CLIENT, INSTANCE_VARIABLES } = home_scope; - if(!message.guild) { + if (!message.guild) { message.answer("Just use youtube-dl at home."); return; - }; + } const guild : string = message.guild.id; const GID : Types.GuildInstanceData = INSTANCE_VARIABLES.guilds[guild]; @@ -26,7 +23,21 @@ export default async(home_scope: HomeScope) => { const attempt_prefetch = (url: string): boolean => { let stream = null; try { - stream = ytdl(url, DL_OPTIONS); + const child = cp.spawn('youtube-dl', [...YTDL_OPTIONS, url], { + stdio: ['ignore', 'pipe', 'pipe'] + }); + child.on('close', async (code) => { + if (code && code !== 0) { + console.log(`Exited with code ${code}:`); + console.log(await child.stderr.utf8()); + stream = null; + child.stdout = null; + GID.vc_prefetch[url] = null; + CONFIG.vc_queue = CONFIG.vc_queue.filter(q => q !== url); + message.answer("Error downloading media."); + } + }); + stream = child.stdout; } catch (e) { console.log(e); } if (stream) { @@ -50,11 +61,13 @@ export default async(home_scope: HomeScope) => { try { GID.vc.disconnect(); GID.vc.channel.leave(); + message.answer("Let's listen again some time :3"); } catch (error) { message.answer("```" + `${error}` + "```"); } break; - } case "pause": { + } case "stop": + case "pause": { if (GID.vc_dispatcher && !GID.vc_dispatcher.paused) { GID.vc_dispatcher.pause(); message.answer("Paused playback."); @@ -62,22 +75,26 @@ export default async(home_scope: HomeScope) => { message.answer("Nothing is playing"); } break; - } case "play": { + } case "resume": + case "play": { + if (!GID.vc) { + message.answer("Let me join a voice channel first."); + return; + } if (GID.vc_dispatcher && GID.vc_dispatcher.paused) { GID.vc_dispatcher.resume(); message.answer("Resuming playback."); - } else { - if (CONFIG.vc_queue.length === 0) { - message.answer("Please add a URL to the queue first."); - return; - } + return; + } - const stream = GID.vc_prefetch[CONFIG.vc_queue.shift()]; - GID.vc_current_stream = stream; - GID.vc_dispatcher = GID.vc.play(stream); - message.channel.send("Playing media from queue..."); + if (CONFIG.vc_queue.length === 0) { + message.answer("Please add a URL to the queue first."); + return; + } + const set_event_listeners = () => { const end_handler = () => { + console.log('VC dispatcher finished.') GID.vc_dispatcher.destroy(); if (CONFIG.vc_queue.length === 0) { CLIENT.channels.fetch(CONFIG.vc_channel) @@ -89,58 +106,76 @@ export default async(home_scope: HomeScope) => { const stream = GID.vc_prefetch[next]; GID.vc_current_stream = stream; GID.vc_dispatcher = GID.vc.play(stream); + set_event_listeners(); + CLIENT.channels.fetch(CONFIG.vc_channel) .then((ch: TextChannel) => ch.send(`Now playing: ${next}`)); - } + }; + GID.vc_dispatcher.on('error', e => { + console.error(`Dispatcher error (${e}):\n${e.stack}`); + CLIENT.channels + .fetch(CONFIG.vc_channel).then((ch: TextChannel) => + ch.send(`Got error during playback: \`${e}\``)); + }); GID.vc_dispatcher.on('finish', end_handler); GID.vc_current_stream.on('end', () => { + console.log('VC stream ended.'); CLIENT.channels.fetch(CONFIG.vc_channel) .then((ch: TextChannel) => ch.send("Stream ended.")); GID.vc_dispatcher.end(); }); - } + }; + + const stream = GID.vc_prefetch[CONFIG.vc_queue.shift()]; + GID.vc_current_stream = stream; + GID.vc_dispatcher = GID.vc.play(stream); + message.channel.send("Playing media from queue..."); + set_event_listeners(); + break; - } case "d": { + } case "rm": + case "remove": + case "d": + case "delete": { const pos = Number(args[1]); CONFIG.vc_queue.splice(pos - 1, 1); message.answer(`Removed media from queue at index ${pos}.`); break; - } case "i": { + } case "insert": + case "i": { const pos = Number(args[1]); const url = args[2]; - const success = attempt_prefetch(url); - if (success) { + if (attempt_prefetch(url)) { CONFIG.vc_queue.splice(pos - 1, 0, url); - message.answer(`Inserted into queue at index ${pos}.`); - } else { - message.answer("URL or media-type not valid."); + message.answer(`Inserting into queue at index ${pos}.`); } break; - } case "ls": { + } case "queue": + case "list": + case "ls": { message.answer(ls(CONFIG.vc_queue)); break; - } case "requeue": { + } case "clear": + case "requeue": { CONFIG.vc_queue = []; GID.vc_current_stream = null; message.answer("Queue cleared"); GID.vc_dispatcher.end(); break; - } case "skip": { + } case "next": + case "skip": { GID.vc_dispatcher.end(); GID.vc_current_stream.destroy(); message.answer("Skipping..."); break; } default: { const url = args[0]; - const success = attempt_prefetch(url); - if (success) { + if (attempt_prefetch(url)) { CONFIG.vc_queue.push(url); - message.answer("Succesfully added media to queue."); - } else { - message.answer("URL or media-type not valid."); + message.answer("Adding media to queue..."); } } } diff --git a/lib/commands/youtube.ts b/lib/commands/youtube.ts @@ -6,15 +6,12 @@ import fetch from "node-fetch"; * !youtube x n, where 1 <= n <= 20 * !youtube new x * !youtube {channel,playlist} x - * !youtube raw x */ export default async (home_scope: HomeScope) => { const { message, args } = home_scope; let query = args.join(' ').trim(); - - const rawOut = !!(args[0] == "raw" && args.shift()); - + const sort_by = (args[0] == "new") ? (args.shift(), "upload_date") : "relevance"; @@ -47,10 +44,7 @@ export default async (home_scope: HomeScope) => { const views : string = Number(res.viewCount).to_abbrev(1); - if(rawOut) - message.channel.send("https://youtu.be/" + res.videoId); - else - message.answer(`Search for '${query}' (result №${num}):` + message.answer(`Search for '${query}' (result №${num}):` + ` https://youtu.be/${res.videoId}` + `\npublished ${res.publishedText},` + ` view count: ${views}, duration: ${duration}`); diff --git a/lib/default.ts b/lib/default.ts @@ -70,6 +70,11 @@ const DEFAULT_GUILD_CONFIG : Types.Config = { 'save': 'export', 'trans': 'translate', 'cuddle': 'hug', + 'abbr': 'abbreviate', + 'abbrev': 'abbreviate', + 't2s': 'tts', + 'speak': 'tts', + 'espeak': 'tts' }, }, diff --git a/lib/extensions.ts b/lib/extensions.ts @@ -19,6 +19,10 @@ declare global { INSTANCE_VARIABLES: Types.InstanceVariables }; + type Mutable<O> = { + -readonly [K in keyof O]: O[K] + } + namespace Types { export type Match = { match: string | RegExp, @@ -147,6 +151,7 @@ declare global { emojify(): string; shorten(width?: number): string; lines(): string[]; + utf8(): Promise<string>; } interface Number { @@ -281,6 +286,28 @@ String.prototype.lines = function () { .split('<-|LINE|->'); }; +String.prototype.utf8 = function () { + return Promise.resolve(String(this)); +}; + +// Readable stream extensions: +declare module "stream" { + interface Readable { + utf8(): Promise<string>; + } +} + +stream.Readable.prototype.utf8 = function () { + const chunks = []; + return new Promise((resolve, reject) => { + this.on('data', chunk => chunks.push(chunk)) + this.on('error', reject) + this.on('end', () => + resolve(Buffer.concat(chunks).toString('utf8'))) + }); +}; + + // Number Extensions: Number.prototype.round_to = function (dp : number) { const exp = 10 ** dp; diff --git a/lib/main.ts b/lib/main.ts @@ -22,15 +22,13 @@ import { execSync as shell, exec } from 'child_process'; import './extensions'; import { deep_merge, pp, compile_match, export_config, access, glue_strings, - deep_copy, recursive_regex_to_string, pastebin_pull } from './utils'; + deep_copy, recursive_regex_to_string, jsonblob_pull } from './utils'; // Default bot configuration for a Guild, JSON. import DEFAULT_GUILD_CONFIG from './default'; // API specific modules. -import { pastebin_latest, - pastebin_update, - pastebin_url } from './api/pastebin'; +import * as JSONBlob from './api/jsonblob'; import { Guild } from 'discord.js'; import { Timer } from './commands/cron'; @@ -285,7 +283,7 @@ export class SimpOMatic { if (i > 300) return 'CYCLIC_ALIAS'; ++i; } - return expanded; + return expanded.toLowerCase(); } process_command(message : Message, ignore_spam: boolean = false) { @@ -398,7 +396,7 @@ Would you like to slow down a little?`.squeeze()); const file_name = `export-${today}.json`; const file_dest = `${process.cwd()}/${file_name}`; write_file(file_dest, export_config(GLOBAL_CONFIG, {})); - pastebin_update(export_config(GLOBAL_CONFIG, {})); + JSONBlob.update(export_config(GLOBAL_CONFIG, {})); if (export_string.length < 1980) { message.channel.send("```json\n" + export_string + "\n```"); @@ -406,17 +404,16 @@ Would you like to slow down a little?`.squeeze()); const attach = new MessageAttachment(file_dest, file_name); message.channel.send("**Export:**", attach); - message.answer(`A copy of this export (\`export-${today}.json\`) \ - has been saved to the local file system. - Pastebin file: ${pastebin_url}`.squeeze()); + message.answer(`A copy of this export (\`export-${today}.json\`)` + + ` has been saved to the local file system.`); break; } case 'refresh': { - message.reply('Pulling pastebin...'); - pastebin_pull(GLOBAL_CONFIG).then((res: Types.GlobalConfig) => { + message.reply('Pulling JSON blob...'); + jsonblob_pull(GLOBAL_CONFIG).then((res: Types.GlobalConfig) => { GLOBAL_CONFIG = res; message.reply('Dynamic configuration refresh succeded.'); }).catch(e => { - message.reply('Could not update from pastebin:\n```' + message.reply('Could not update from JSON blob:\n```' + e.toString() + '```'); }); break; @@ -646,9 +643,9 @@ function on_termination(error_type, e?: Error) { write_file(`${process.cwd()}/export-exit.json`, exported); - pastebin_update(exported) + JSONBlob.update(exported) .then(_ => { - console.log('Finished pastebin update.'); + console.log('Finished JSONBlob update.'); system_message(CLIENT, `Current configuration saved.`); }).catch(e => { console.warn('Pastebin not saved!', e); @@ -677,7 +674,7 @@ process process.on('uncaughtException', (e) => e); // GLOBAL_CONFIG will eventually update to the online version. -pastebin_pull(GLOBAL_CONFIG).then((res: Types.GlobalConfig) => { +jsonblob_pull(GLOBAL_CONFIG).then((res: Types.GlobalConfig) => { GLOBAL_CONFIG = res; // Start The Simp'O'Matic. CLIENT = SimpOMatic.start(); diff --git a/lib/utils.ts b/lib/utils.ts @@ -2,7 +2,7 @@ import { inspect } from 'util'; import deep_clone from 'deepcopy'; import { HELP_SECTIONS, KNOWN_COMMANDS } from './main'; -import { pastebin_latest } from './api/pastebin'; +import * as JSONBlob from './api/jsonblob'; import './extensions'; export const deep_copy = deep_clone; @@ -123,10 +123,10 @@ export const export_config = (obj: Types.GlobalConfig, { ugly = false }) => { return JSON.dump(o, null, ugly ? null : 4); }; -export const pastebin_pull = (global_conf: Types.GlobalConfig) => +export const jsonblob_pull = (global_conf: Types.GlobalConfig) => new Promise((resolve, reject) => { // GLOBAL_CONFIG will eventually update to the online version. - pastebin_latest().then(res => { + JSONBlob.latest().then(res => { global_conf = deep_merge(global_conf, res); // Remove any duplicates. const gc_string = export_config(global_conf, { ugly: true }); diff --git a/package.json b/package.json @@ -42,7 +42,7 @@ "@types/nodegit": "^0.26.3", "@types/ws": "^7.2.2", "node-forge": "^0.10.0", - "better-pastebin": "^0.4.1", + "jsonblob": "^1.0.1", "cowsay": "^1.4.0", "deepcopy": "^2.0.0", "discord.js": "12.0.2", @@ -59,10 +59,7 @@ "tslib": "^1.11.1", "typescript": "^3.8.3", "unirest": "^0.6.0", - "ytdl-core": "^4.0.3", - "@discordjs/opus": "^0.3.3", - "node-opus": "^0.3.3", - "opusscript": "0.0.7" + "@discordjs/opus": "^0.3.3" }, "devDependencies": { "tslint": "^6.1.0" diff --git a/yarn.lock b/yarn.lock @@ -537,6 +537,14 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== +axios@^0.18.0: + version "0.18.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" + integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -554,15 +562,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -better-pastebin@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/better-pastebin/-/better-pastebin-0.4.1.tgz#79a7752f59cd47720cc9a7c8409a4a1fa58cf926" - integrity sha1-ead1L1nNR3IMyafIQJpKH6WM+SY= - dependencies: - cheerio ">=0.18.0" - request ">=2.51.0" - xml2js ">=0.4.4" - bignumber.js@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" @@ -682,28 +681,6 @@ chalk@^2.0.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -cheerio@>=0.18.0: - version "0.22.0" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" - integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4= - dependencies: - css-select "~1.2.0" - dom-serializer "~0.1.0" - entities "~1.1.1" - htmlparser2 "^3.9.1" - lodash.assignin "^4.0.9" - lodash.bind "^4.1.4" - lodash.defaults "^4.0.1" - lodash.filter "^4.4.0" - lodash.flatten "^4.2.0" - lodash.foreach "^4.3.0" - lodash.map "^4.4.0" - lodash.merge "^4.4.0" - lodash.pick "^4.2.1" - lodash.reduce "^4.4.0" - lodash.reject "^4.4.0" - lodash.some "^4.4.0" - cheerio@^1.0.0-rc.3: version "1.0.0-rc.3" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" @@ -830,6 +807,13 @@ debug@4: dependencies: ms "^2.1.1" +debug@=3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -908,7 +892,7 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -dom-serializer@~0.1.0, dom-serializer@~0.1.1: +dom-serializer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== @@ -1064,6 +1048,13 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== + dependencies: + debug "=3.1.0" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -1396,6 +1387,11 @@ ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== +is-buffer@^2.0.2: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -1506,6 +1502,13 @@ json5@^2.1.0: dependencies: minimist "^1.2.5" +jsonblob@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/jsonblob/-/jsonblob-1.0.1.tgz#95e57f87a95723203e4d7dd69dbdd6e0058413b6" + integrity sha512-s+t4+yZkYfuMMaHtPs0qzUoefB8Rmasj7LYikwT7vXk9hEmX4Rp3isZJ2mrCCDC9SJgLVmNVfORt5vcP81N0DQ== + dependencies: + axios "^0.18.0" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -1561,66 +1564,6 @@ load-bmfont@^1.3.1, load-bmfont@^1.4.0: xhr "^2.0.1" xtend "^4.0.0" -lodash.assignin@^4.0.9: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.assignin/-/lodash.assignin-4.2.0.tgz#ba8df5fb841eb0a3e8044232b0e263a8dc6a28a2" - integrity sha1-uo31+4QesKPoBEIysOJjqNxqKKI= - -lodash.bind@^4.1.4: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" - integrity sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU= - -lodash.defaults@^4.0.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" - integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= - -lodash.filter@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" - integrity sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4= - -lodash.flatten@^4.2.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" - integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= - -lodash.foreach@^4.3.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" - integrity sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM= - -lodash.map@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= - -lodash.merge@^4.4.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.pick@^4.2.1: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= - -lodash.reduce@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" - integrity sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= - -lodash.reject@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.reject/-/lodash.reject-4.6.0.tgz#80d6492dc1470864bbf583533b651f42a9f52415" - integrity sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU= - -lodash.some@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" - integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= - lodash@^4.15.0, lodash@^4.17.14: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" @@ -2202,7 +2145,7 @@ regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== -request@>=2.51.0, request@^2.34, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@^2.34, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -2636,7 +2579,7 @@ xml-parse-from-string@^1.0.0: resolved "https://registry.yarnpkg.com/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz#a9029e929d3dbcded169f3c6e28238d95a5d5a28" integrity sha1-qQKekp09vN7RafPG4oI42VpdWig= -xml2js@>=0.4.4, xml2js@^0.4.5: +xml2js@^0.4.5: version "0.4.23" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==