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:
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==