add custom discord client and add polling endpoint
This commit is contained in:
parent
3dd2e6ada1
commit
0697dd0a82
10 changed files with 435 additions and 114 deletions
238
DiscordClient.js
Normal file
238
DiscordClient.js
Normal file
|
@ -0,0 +1,238 @@
|
|||
import EventEmitter from "events";
|
||||
import zlib from "zlib";
|
||||
import { WebSocket } from "ws";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
const opcodes = {
|
||||
EVENT: 0,
|
||||
CLIENT_HEARTBEAT: 1,
|
||||
IDENTIFY: 2,
|
||||
HELLO: 10,
|
||||
HEARTBEAT_ACK: 11,
|
||||
};
|
||||
|
||||
class DiscordClient extends EventEmitter {
|
||||
constructor(token, { intents, baseDomain="discord.com", gatewayUrl="wss://gateway.discord.gg/?v=9&encoding=json&compress=zlib-stream", apiBase="https://discord.com/api/v9" } = {}) {
|
||||
super();
|
||||
|
||||
this.token = token;
|
||||
this.gatewayUrl = gatewayUrl;
|
||||
this.apiBase = apiBase;
|
||||
this.inflate = zlib.createInflate({
|
||||
chunkSize: 128 * 1024
|
||||
});
|
||||
this.ws = null;
|
||||
this.intents = intents;
|
||||
|
||||
this.user = null;
|
||||
this.guilds = [];
|
||||
this.sessionId = null;
|
||||
this.seq = null;
|
||||
this.gotServerHeartbeatACK = true;
|
||||
}
|
||||
|
||||
_setHeartbeat(interval) {
|
||||
this._heartbeatIntervalTime = interval;
|
||||
if (interval < 0 && this._heartbeatInterval) {
|
||||
clearInterval(this._heartbeatInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
this._heartbeatInterval = setInterval(() => {
|
||||
if (!this.gotServerHeartbeatACK) {
|
||||
this.emit("error", "NO_HEARTBEAT_ACK");
|
||||
return;
|
||||
}
|
||||
this.gotServerHeartbeatACK = false;
|
||||
|
||||
this.ws.send(JSON.stringify({
|
||||
op: opcodes.CLIENT_HEARTBEAT,
|
||||
d: this.seq
|
||||
}));
|
||||
}, this._heartbeatIntervalTime);
|
||||
}
|
||||
|
||||
_handleGatewayMessage(ws, message) {
|
||||
try {
|
||||
message = JSON.parse(message);
|
||||
} catch(e) {
|
||||
console.error("error: DiscordClient: on `message`: failed to parse incoming message as JSON", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.s) {
|
||||
this.seq = message.s;
|
||||
}
|
||||
|
||||
const payload = message.d;
|
||||
|
||||
switch (message.op) {
|
||||
case opcodes.HELLO: {
|
||||
this._setHeartbeat(payload.heartbeat_interval);
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
op: opcodes.IDENTIFY,
|
||||
d: {
|
||||
token: this.token,
|
||||
intents: this.intents,
|
||||
properties: {
|
||||
"$os": "linux",
|
||||
"$browser": "generic",
|
||||
"$device": "generic"
|
||||
},
|
||||
presence: {
|
||||
since: Date.now(),
|
||||
activities: [
|
||||
{
|
||||
type: 2, // LISTENING
|
||||
name: "the voices"
|
||||
}
|
||||
],
|
||||
status: "online",
|
||||
afk: false
|
||||
}
|
||||
}
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
case opcodes.EVENT: {
|
||||
switch (message.t) {
|
||||
case "READY": {
|
||||
console.log("DiscordClient: ready");
|
||||
this.user = payload.user;
|
||||
this.sessionId = payload.session_id;
|
||||
this.guilds = payload.guilds;
|
||||
break;
|
||||
}
|
||||
|
||||
case "GUILD_CREATE": {
|
||||
const targetGuildIndex = this.guilds.findIndex(e => e.id === payload.id);
|
||||
if (targetGuildIndex < 0) {
|
||||
this.guilds.push(payload);
|
||||
break;
|
||||
}
|
||||
// The guild already exists in our array. This means that
|
||||
// this GUILD_CREATE event is completing our `Unavailable Guild`
|
||||
// objects that we got from the initial READY.
|
||||
this.guilds[targetGuildIndex] = payload;
|
||||
break;
|
||||
}
|
||||
|
||||
case "GUILD_UPDATE": {
|
||||
const targetGuildIndex = this.guilds.findIndex(e => e.id === payload.id);
|
||||
if (targetGuildIndex < 0) {
|
||||
// tried to update a guild that doesn't exist???
|
||||
this.emit("warn", "got GUILD_UPDATE for a guild that doesn't exist");
|
||||
break;
|
||||
}
|
||||
this.guilds[targetGuildIndex] = payload;
|
||||
break;
|
||||
}
|
||||
|
||||
case "GUILD_DELETE": {
|
||||
const targetGuildIndex = this.guilds.findIndex(e => e.id === payload.id);
|
||||
if (targetGuildIndex < 0) {
|
||||
// tried to delete a guild that doesn't exist???
|
||||
this.emit("warn", "got GUILD_DELETE for a guild that doesn't exist");
|
||||
break;
|
||||
}
|
||||
this.guilds.splice(targetGuildIndex, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case "CHANNEL_CREATE": {
|
||||
const parentGuildIndex = this.guilds.findIndex(e => e.id === payload.guild_id);
|
||||
if (parentGuildIndex < 0) {
|
||||
this.emit("warn", "got CHANNEL_CREATE for a guild that doesn't exist");
|
||||
break;
|
||||
}
|
||||
this.guilds[parentGuildIndex].channels.push(payload);
|
||||
break;
|
||||
}
|
||||
|
||||
case "CHANNEL_UPDATE": {
|
||||
const parentGuildIndex = this.guilds.findIndex(e => e.id === payload.guild_id);
|
||||
if (parentGuildIndex < 0) {
|
||||
this.emit("warn", "got CHANNEL_CREATE for a guild that doesn't exist");
|
||||
break;
|
||||
}
|
||||
const relevantChannelIndex = this.guilds[parentGuildIndex].channels.findIndex(e => e.id === payload.id);
|
||||
this.guilds[parentGuildIndex].channels[relevantChannelIndex] = payload;
|
||||
break;
|
||||
}
|
||||
|
||||
case "CHANNEL_DELETE": {
|
||||
const parentGuildIndex = this.guilds.findIndex(e => e.id === payload.guild_id);
|
||||
if (parentGuildIndex < 0) {
|
||||
this.emit("warn", "got CHANNEL_CREATE for a guild that doesn't exist");
|
||||
break;
|
||||
}
|
||||
const relevantChannelIndex = this.guilds[parentGuildIndex].channels.findIndex(e => e.id === payload.id);
|
||||
this.guilds[parentGuildIndex].channels.splice(relevantChannelIndex, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.emit(message.t, payload);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case opcodes.HEARTBEAT_ACK: {
|
||||
this.gotServerHeartbeatACK = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
console.warn(`warn: DiscordClient: got unhandled opcode "${message.op}"`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect() {
|
||||
const ws = new WebSocket(this.gatewayUrl);
|
||||
|
||||
this.ws = ws;
|
||||
|
||||
// we decompressed the data, send it to the handler now
|
||||
this.inflate.on("data", (message) => this._handleGatewayMessage(ws, message));
|
||||
|
||||
ws.on("message", (data, isBinary) => {
|
||||
// pass the data to the decompressor
|
||||
this.inflate.write(data);
|
||||
});
|
||||
|
||||
ws.on("close", (code, reason) => {
|
||||
reason = reason.toString();
|
||||
console.error(`DiscordClient: on \`close\`: disconnected from gateway: code \`${code}\`, reason \`${reason}\``);
|
||||
})
|
||||
}
|
||||
|
||||
async api([method, path], body=undefined, throwOnError=true) {
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
"authorization": `Bot ${this.token}`
|
||||
}
|
||||
};
|
||||
|
||||
if (method !== "GET" && method !== "HEAD" && typeof body === "object") {
|
||||
options.headers["content-type"] = "application/json";
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.apiBase}${path}`, options);
|
||||
const json = await response.json();
|
||||
|
||||
if (!response.ok && throwOnError) {
|
||||
throw new Error(`API request returned non-success status ${response.status}, with JSON body ${JSON.stringify(json)}`);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscordClient;
|
|
@ -1,80 +1,83 @@
|
|||
const { EventEmitter } = require("events");
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
class WatchedGuild extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.knownWebhooks = new Map();
|
||||
this.eventStack = [];
|
||||
this.upstreamGuildId = null;
|
||||
}
|
||||
|
||||
pushEvent(e) {
|
||||
this.eventStack.push(e);
|
||||
this.emit("pushed", e);
|
||||
}
|
||||
|
||||
consumeEvent() {
|
||||
return this.eventStack.pop();
|
||||
}
|
||||
|
||||
consumeAll() {
|
||||
const events = [...this.eventStack];
|
||||
this.eventStack = [];
|
||||
return events;
|
||||
}
|
||||
|
||||
hasEvents() {
|
||||
return this.eventStack.length > 0;
|
||||
}
|
||||
|
||||
_pushMessageEvent(message) {
|
||||
this.pushEvent({
|
||||
eventType: "messageCreate",
|
||||
message: message.toJSON()
|
||||
holdForEvent() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// potential memory leak here when too many promises are created and waiting
|
||||
this.once("pushed", (event) => {
|
||||
resolve(event);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
discordConnect(bot) {
|
||||
this.bot = bot;
|
||||
this.bot.on("messageCreate", (message) => {
|
||||
if (message.guildId !== this.upstreamGuildId)
|
||||
this.guildObject = this.bot.guilds.find(e => e.id === this.upstreamGuildId);
|
||||
|
||||
if (!this.guildObject) {
|
||||
throw new Error("Could not find guild object from bot cache by id (is the upstreamGuildId valid and does the bot have access to it?)");
|
||||
}
|
||||
|
||||
this.bot.on("GUILD_CREATE", (guild) => {
|
||||
if (guild.id === this.upstreamGuildId)
|
||||
this.guildObject = guild;
|
||||
});
|
||||
|
||||
this.bot.on("GUILD_UPDATE", (guild) => {
|
||||
if (guild.id === this.upstreamGuildId)
|
||||
this.guildObject = guild;
|
||||
});
|
||||
|
||||
this.bot.on("MESSAGE_CREATE", (message) => {
|
||||
if (message.guild_id !== this.upstreamGuildId)
|
||||
return;
|
||||
|
||||
this._pushMessageEvent(message);
|
||||
this.pushEvent({
|
||||
eventType: "MESSAGE_CREATE",
|
||||
message: message
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async discordSendMessage(messageContent, channelId, username, avatarURL=undefined) {
|
||||
if (!this.bot)
|
||||
throw new Error("Bot not connected");
|
||||
userFacingChannelList() {
|
||||
return this.guildObject.channels.map(channel => ({ id: channel.id, name: channel.name, position: channel.position, type: channel.type, nsfw: channel.nsfw }));
|
||||
}
|
||||
|
||||
async discordSendMessage(content, channelId, username, avatarURL=undefined) {
|
||||
let webhook = this.knownWebhooks.get(channelId);
|
||||
if (!webhook) {
|
||||
webhook = (await this.bot.getChannelWebhooks(channelId))
|
||||
.filter(w => w.name == "well_known__bridge")[0];
|
||||
|
||||
webhook = (await this.bot.api(["GET", `/channels/${channelId}/webhooks`]))
|
||||
.find(e => e.name === "well_known__bridge");
|
||||
|
||||
if (!webhook)
|
||||
webhook = await this.bot.createChannelWebhook(channelId, {
|
||||
webhook = await this.bot.api(["POST", `/channels/${channelId}/webhooks`], {
|
||||
name: "well_known__bridge"
|
||||
}, "This webhook was created by the bridge API bot.");
|
||||
});
|
||||
|
||||
this.knownWebhooks.set(channelId, webhook);
|
||||
}
|
||||
|
||||
await this.bot.executeWebhook(webhook.id, webhook.token, {
|
||||
allowedMentions: {
|
||||
everyone: false,
|
||||
roles: false,
|
||||
users: true
|
||||
},
|
||||
content: messageContent,
|
||||
await this.bot.api(["POST", `/webhooks/${webhook.id}/${webhook.token}?wait=true`], {
|
||||
content,
|
||||
username,
|
||||
avatar_url: avatarURL,
|
||||
tts: false,
|
||||
wait: true,
|
||||
avatarURL,
|
||||
username
|
||||
allowed_mentions: {
|
||||
parse: ["users"]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WatchedGuild;
|
||||
export default WatchedGuild;
|
||||
|
|
29
common.js
29
common.js
|
@ -1,17 +1,19 @@
|
|||
const Eris = require("eris");
|
||||
const { discordToken, watchedGuildIds } = require("./config");
|
||||
const WatchedGuild = require("./WatchedGuild");
|
||||
import { discordToken, watchedGuildIds } from "./config.js";
|
||||
import DiscordClient from "./DiscordClient.js";
|
||||
import WatchedGuild from "./WatchedGuild.js";
|
||||
|
||||
const bot = new Eris(discordToken, {
|
||||
intents: [
|
||||
"guildMessages"
|
||||
]
|
||||
export const guildMap = new Map();
|
||||
export const bot = new DiscordClient(discordToken, {
|
||||
intents: 0 | (1 << 0) | (1 << 9) // GUILDS & GUILD_MESSAGES
|
||||
});
|
||||
|
||||
const guildMap = new Map();
|
||||
export function wait(time) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => resolve(), time);
|
||||
});
|
||||
}
|
||||
|
||||
bot.on("ready", () => {
|
||||
console.log("discord bot: ready");
|
||||
bot.on("READY", () => {
|
||||
watchedGuildIds.forEach(id => {
|
||||
const watchedGuild = new WatchedGuild();
|
||||
watchedGuild.upstreamGuildId = id;
|
||||
|
@ -19,10 +21,3 @@ bot.on("ready", () => {
|
|||
guildMap.set(id, watchedGuild);
|
||||
});
|
||||
});
|
||||
|
||||
bot.connect();
|
||||
|
||||
module.exports = {
|
||||
bot,
|
||||
guildMap
|
||||
};
|
||||
|
|
12
config.js
12
config.js
|
@ -1,7 +1,5 @@
|
|||
module.exports = {
|
||||
mainHttpListenPort: 4050,
|
||||
watchedGuildIds: ["822089558886842418"],
|
||||
jwtSecret: process.env.JWT_SECRET,
|
||||
discordToken: process.env.DISCORD_TOKEN,
|
||||
dangerousAdminMode: true
|
||||
};
|
||||
export const mainHttpListenPort = 4050;
|
||||
export const watchedGuildIds = ["822089558886842418"];
|
||||
export const jwtSecret = process.env.JWT_SECRET;
|
||||
export const discordToken = process.env.DISCORD_TOKEN;
|
||||
export const dangerousAdminMode = true;
|
||||
|
|
9
index.js
9
index.js
|
@ -1,7 +1,9 @@
|
|||
const express = require("express");
|
||||
const { mainHttpListenPort } = require("./config");
|
||||
import express from "express";
|
||||
import apiRoute from "./routes/api.js";
|
||||
import { mainHttpListenPort } from "./config.js";
|
||||
import { bot } from "./common.js";
|
||||
|
||||
const app = express();
|
||||
const apiRoute = require("./routes/api");
|
||||
|
||||
app.use(express.json());
|
||||
app.use("/", express.static("public/"));
|
||||
|
@ -13,4 +15,5 @@ app.get("/", (req, res) => {
|
|||
|
||||
app.listen(mainHttpListenPort, () => {
|
||||
console.log(`server main: listen on ${mainHttpListenPort}`);
|
||||
bot.connect();
|
||||
});
|
|
@ -3,9 +3,11 @@
|
|||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"eris": "^0.16.1",
|
||||
"express": "^4.17.2",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"node-fetch": "^3.2.0",
|
||||
"ws": "^8.4.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="createToken('testinguser', '0', ['0']);">create token</button>
|
||||
<button onclick="sendMessage('0', '0', 'hello');">send message</button>
|
||||
<button onclick="createToken('testinguser', '0', ['822089558886842418']);">create token</button>
|
||||
<button onclick="sendMessage('822089558886842418', '822089558886842421', 'hello');">send message</button>
|
||||
<button onclick="getChannels('822089558886842418');">get channels</button>
|
||||
<button onclick="eventPoll('822089558886842418');">poll event</button>
|
||||
|
||||
|
||||
<script>
|
||||
let createdToken;
|
||||
|
@ -41,8 +44,27 @@
|
|||
"authorization": createdToken
|
||||
}
|
||||
});
|
||||
const json = await res.json();
|
||||
console.log("sendMessage()", json);
|
||||
console.log("sendMessage() stauts", await res.status);
|
||||
}
|
||||
|
||||
async function getChannels(guildId) {
|
||||
const res = await fetch(`http://localhost:4050/api/v1/guilds/${guildId}/channels`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"authorization": createdToken
|
||||
}
|
||||
});
|
||||
console.log("getChannels()", await res.json());
|
||||
}
|
||||
|
||||
async function eventPoll(guildId) {
|
||||
const res = await fetch(`http://localhost:4050/api/v1/guilds/${guildId}/events/poll`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"authorization": createdToken
|
||||
}
|
||||
});
|
||||
console.log("eventPoll()", await res.json());
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
const express = require("express");
|
||||
const { guildMap } = require("../common");
|
||||
const { dangerousAdminMode } = require("../config");
|
||||
const { checkAuth, createToken } = require("../tokens");
|
||||
import express from "express";
|
||||
import { guildMap, wait } from "../common.js";
|
||||
import { dangerousAdminMode } from "../config.js";
|
||||
import { checkAuth, createToken } from "../tokens.js";
|
||||
|
||||
const router = express();
|
||||
|
||||
router.get("/", (req, res) => {
|
||||
|
@ -46,11 +47,57 @@ router.post("/guilds/:guildId/channels/:channelId/messages/create", checkAuth(as
|
|||
const guild = guildMap.get(guildId);
|
||||
try {
|
||||
await guild.discordSendMessage(messageContent, channelId, username, avatarURL);
|
||||
res.status(201).send({ error: false, message: "SUCCESS_MESSAGE_CREATED" });
|
||||
res.status(201).send("");
|
||||
} catch(e) {
|
||||
console.error("server main: api: message create: error: ", e);
|
||||
res.status(500).send({ error: true, message: "ERROR_MESSAGE_SEND_FAILURE" });
|
||||
}
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
router.get("/guilds/:guildId/channels", checkAuth(async (req, res) => {
|
||||
const guildId = req.params.guildId;
|
||||
if (!guildId)
|
||||
return res.status(400).send({ error: true, message: "ERROR_NO_GUILD_ID" });
|
||||
|
||||
const { guildAccess } = req.user;
|
||||
|
||||
if (guildAccess.indexOf(guildId) === -1)
|
||||
return res.status(403).send({ error: true, message: "ERROR_NO_GUILD_ACCESS" });
|
||||
|
||||
const guild = guildMap.get(guildId);
|
||||
try {
|
||||
res.status(200).send({ error: false, channels: guild.userFacingChannelList() });
|
||||
} catch(e) {
|
||||
console.error("server main: api: guild get channels: error: ", e);
|
||||
res.status(500).send({ error: true, message: "ERROR_CHANNELS_FETCH_FAILURE" });
|
||||
}
|
||||
}));
|
||||
|
||||
router.get("/guilds/:guildId/events/poll", checkAuth(async (req, res) => {
|
||||
const guildId = req.params.guildId;
|
||||
if (!guildId)
|
||||
return res.status(400).send({ error: true, message: "ERROR_NO_GUILD_ID" });
|
||||
|
||||
const { guildAccess } = req.user;
|
||||
|
||||
if (guildAccess.indexOf(guildId) === -1)
|
||||
return res.status(403).send({ error: true, message: "ERROR_NO_GUILD_ACCESS" });
|
||||
|
||||
const guild = guildMap.get(guildId);
|
||||
try {
|
||||
Promise.race([
|
||||
guild.holdForEvent(),
|
||||
wait(10000)
|
||||
]).then(event => {
|
||||
res.status(200).send({ error: false, event });
|
||||
})
|
||||
.catch(() => {
|
||||
res.status(200).send({ error: false, event: null });
|
||||
});
|
||||
} catch(e) {
|
||||
console.error("server main: api: guild poll events: error: ", e);
|
||||
res.status(500).send({ error: true, message: "ERROR_POLL_FAILURE" });
|
||||
}
|
||||
}));
|
||||
|
||||
export default router;
|
||||
|
|
16
tokens.js
16
tokens.js
|
@ -1,7 +1,7 @@
|
|||
const jsonwebtoken = require("jsonwebtoken");
|
||||
const { jwtSecret } = require("./config");
|
||||
import jsonwebtoken from "jsonwebtoken";
|
||||
import { jwtSecret } from "./config.js";
|
||||
|
||||
function createToken({ username, avatarURL, discordID, guildAccess }) {
|
||||
export function createToken({ username, avatarURL, discordID, guildAccess }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jsonwebtoken.sign({ username, avatarURL, discordID, guildAccess }, jwtSecret, (err, token) => {
|
||||
if (err)
|
||||
|
@ -12,7 +12,7 @@ function createToken({ username, avatarURL, discordID, guildAccess }) {
|
|||
});
|
||||
}
|
||||
|
||||
function decodeToken(token) {
|
||||
export function decodeToken(token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jsonwebtoken.verify(token, jwtSecret, (err, token) => {
|
||||
if (err)
|
||||
|
@ -23,7 +23,7 @@ function decodeToken(token) {
|
|||
});
|
||||
}
|
||||
|
||||
function checkAuth(callback) {
|
||||
export function checkAuth(callback) {
|
||||
return async (req, res) => {
|
||||
const token = req.get("authorization");
|
||||
if (token) {
|
||||
|
@ -48,9 +48,3 @@ function checkAuth(callback) {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createToken,
|
||||
decodeToken,
|
||||
checkAuth
|
||||
};
|
||||
|
|
61
yarn.lock
61
yarn.lock
|
@ -63,6 +63,11 @@ cookie@0.4.1:
|
|||
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1"
|
||||
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==
|
||||
|
||||
data-uri-to-buffer@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
|
||||
integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==
|
||||
|
||||
debug@2.6.9:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
|
@ -97,16 +102,6 @@ encodeurl@~1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
eris@^0.16.1:
|
||||
version "0.16.1"
|
||||
resolved "https://registry.yarnpkg.com/eris/-/eris-0.16.1.tgz#44b0a9220944fc73dd74538cd614826bfbfcde61"
|
||||
integrity sha512-fqjgaddSvUlUjA7s85OvZimLrgCwX58Z6FXOIxdNFJdT6XReJ/LOWZKdew2CaalM8BvN2JKzn98HmKYb3zMhKg==
|
||||
dependencies:
|
||||
ws "^8.2.3"
|
||||
optionalDependencies:
|
||||
opusscript "^0.0.8"
|
||||
tweetnacl "^1.0.3"
|
||||
|
||||
escape-html@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
|
||||
|
@ -153,6 +148,14 @@ express@^4.17.2:
|
|||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717"
|
||||
integrity sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==
|
||||
dependencies:
|
||||
node-domexception "^1.0.0"
|
||||
web-streams-polyfill "^3.0.3"
|
||||
|
||||
finalhandler@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
|
||||
|
@ -166,6 +169,13 @@ finalhandler@~1.1.2:
|
|||
statuses "~1.5.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
formdata-polyfill@^4.0.10:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
|
||||
integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
|
||||
dependencies:
|
||||
fetch-blob "^3.1.2"
|
||||
|
||||
forwarded@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
|
||||
|
@ -319,6 +329,20 @@ negotiator@0.6.2:
|
|||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||
|
||||
node-domexception@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
|
||||
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
|
||||
|
||||
node-fetch@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.0.tgz#59390db4e489184fa35d4b74caf5510e8dfbaf3b"
|
||||
integrity sha512-8xeimMwMItMw8hRrOl3C9/xzU49HV/yE6ORew/l+dxWimO5A4Ra8ld2rerlJvc/O7et5Z1zrWsPX43v1QBjCxw==
|
||||
dependencies:
|
||||
data-uri-to-buffer "^4.0.0"
|
||||
fetch-blob "^3.1.4"
|
||||
formdata-polyfill "^4.0.10"
|
||||
|
||||
on-finished@~2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
|
||||
|
@ -326,11 +350,6 @@ on-finished@~2.3.0:
|
|||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
opusscript@^0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/opusscript/-/opusscript-0.0.8.tgz#00b49e81281b4d99092d013b1812af8654bd0a87"
|
||||
integrity sha512-VSTi1aWFuCkRCVq+tx/BQ5q9fMnQ9pVZ3JU4UHKqTkf0ED3fKEPdr+gKAAl3IA2hj9rrP6iyq3hlcJq3HELtNQ==
|
||||
|
||||
parseurl@~1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
|
||||
|
@ -428,11 +447,6 @@ toidentifier@1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
|
||||
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
|
||||
|
||||
tweetnacl@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596"
|
||||
integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==
|
||||
|
||||
type-is@~1.6.18:
|
||||
version "1.6.18"
|
||||
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
|
||||
|
@ -456,7 +470,12 @@ vary@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||
|
||||
ws@^8.2.3:
|
||||
web-streams-polyfill@^3.0.3:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965"
|
||||
integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==
|
||||
|
||||
ws@^8.4.2:
|
||||
version "8.4.2"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
|
||||
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==
|
||||
|
|
Loading…
Reference in a new issue