mirror of
https://github.com/captbaritone/webamp.git
synced 2026-07-20 16:49:52 +00:00
Create a Discord bot
This commit is contained in:
parent
82d7f99814
commit
696fd077fa
14 changed files with 716 additions and 0 deletions
2
experiments/discord-bot/.gitignore
vendored
Normal file
2
experiments/discord-bot/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
config.js
|
||||
node_modules/
|
||||
37
experiments/discord-bot/commands/approve.js
Normal file
37
experiments/discord-bot/commands/approve.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
const { approve, getStatus } = require("../s3");
|
||||
const Utils = require("../utils");
|
||||
|
||||
const { getFilename } = require("../info");
|
||||
const TWEET_BOT_CHANNEL_ID = "445577489834704906";
|
||||
|
||||
async function handler(message, args) {
|
||||
const [md5] = args;
|
||||
const filename = getFilename(md5);
|
||||
if (filename == null) {
|
||||
await message.channel.send(`Could not find a skin matching ${md5}`);
|
||||
return;
|
||||
}
|
||||
const status = await getStatus(md5);
|
||||
if (status !== "UNREVIEWED") {
|
||||
await message.channel.send(`This skin has already been reviewed.`);
|
||||
return;
|
||||
}
|
||||
await approve(md5);
|
||||
const tweetBotChannel = message.client.channels.get(TWEET_BOT_CHANNEL_ID);
|
||||
await Utils.postSkin({
|
||||
filename,
|
||||
md5,
|
||||
title: `Approved: ${filename}`,
|
||||
dest: tweetBotChannel
|
||||
});
|
||||
await tweetBotChannel.send(
|
||||
`${filename} was approved by ${message.author.username}`
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
command: "approve",
|
||||
handler,
|
||||
usage: "<md5-hash>",
|
||||
description: "Approve a given skin to be Tweeted by the Twitter bot"
|
||||
};
|
||||
60
experiments/discord-bot/commands/archive.js
Normal file
60
experiments/discord-bot/commands/archive.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const fetch = require("node-fetch");
|
||||
const md5Buffer = require("md5");
|
||||
|
||||
const { getInfo } = require("../info");
|
||||
const Skin = require("./skin");
|
||||
|
||||
async function handler(message, args) {
|
||||
const { attachments } = message;
|
||||
if (attachments.length < 1) {
|
||||
await message.channel.send("Could not archive. No attachment found.");
|
||||
return;
|
||||
}
|
||||
const files = await Promise.all(
|
||||
attachments.map(async attachment => {
|
||||
const { filename, url } = attachment;
|
||||
const response = await fetch(url);
|
||||
console.log("got response");
|
||||
const buffer = await response.buffer();
|
||||
console.log("got buffer");
|
||||
const md5 = md5Buffer(buffer);
|
||||
console.log("got md5", md5);
|
||||
return { filename, buffer, md5 };
|
||||
})
|
||||
);
|
||||
|
||||
for (const file of files) {
|
||||
const info = await getInfo(file.md5);
|
||||
if (info == null) {
|
||||
fs.writeFileSync(
|
||||
path.join(
|
||||
"/Volumes/Mobile Backup/skins/skins/discord-bot",
|
||||
// TODO: Use a sub directory using md5 to avoid collision
|
||||
// file.md5,
|
||||
file.filename
|
||||
),
|
||||
file.buffer
|
||||
);
|
||||
await message.channel.send(
|
||||
`Thanks! ${
|
||||
file.filename
|
||||
} is a brand new skin. 👏 It has been queued for archiving.`
|
||||
);
|
||||
} else {
|
||||
await message.channel.send(
|
||||
`We already have ${file.filename} in our collection`
|
||||
);
|
||||
await Skin.handler(message, [file.md5]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
command: "archive",
|
||||
usage: "",
|
||||
description:
|
||||
"Queues the accompanying uploaded skin for inclusion in the Archive",
|
||||
handler
|
||||
};
|
||||
24
experiments/discord-bot/commands/random.js
Normal file
24
experiments/discord-bot/commands/random.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
const Utils = require("../utils");
|
||||
|
||||
const { getCache, getFilename } = require("../info");
|
||||
|
||||
async function handler(message) {
|
||||
const cache = getCache();
|
||||
const skins = Object.values(cache).filter(skin => skin.type === "CLASSIC");
|
||||
const skin = skins[Math.floor(Math.random() * skins.length)];
|
||||
const { md5 } = skin;
|
||||
const filename = getFilename(md5);
|
||||
await Utils.postSkin({
|
||||
filename,
|
||||
md5,
|
||||
title: `${filename}`,
|
||||
dest: message.channel
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
usage: "",
|
||||
description: "Show information about a random skin",
|
||||
command: "random",
|
||||
handler
|
||||
};
|
||||
37
experiments/discord-bot/commands/reject.js
Normal file
37
experiments/discord-bot/commands/reject.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
const { reject, getStatus } = require("../s3");
|
||||
const Utils = require("../utils");
|
||||
|
||||
const { getFilename } = require("../info");
|
||||
const TWEET_BOT_CHANNEL_ID = "445577489834704906";
|
||||
|
||||
async function handler(message, args) {
|
||||
const [md5] = args;
|
||||
const filename = getFilename(md5);
|
||||
if (filename == null) {
|
||||
await message.channel.send(`Could not find a skin matching ${md5}`);
|
||||
return;
|
||||
}
|
||||
const status = await getStatus(md5);
|
||||
if (status !== "UNREVIEWED") {
|
||||
await message.channel.send(`This skin has already been reviewed.`);
|
||||
return;
|
||||
}
|
||||
await reject(md5);
|
||||
const tweetBotChannel = message.client.channels.get(TWEET_BOT_CHANNEL_ID);
|
||||
await Utils.postSkin({
|
||||
filename,
|
||||
md5,
|
||||
title: `Rejected: ${filename}`,
|
||||
dest: tweetBotChannel
|
||||
});
|
||||
await tweetBotChannel.send(
|
||||
`${filename} was rejected by ${message.author.username}`
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
command: "reject",
|
||||
handler,
|
||||
usage: "<md5-hash>",
|
||||
description: "Reject a given skin from being Tweeted by the Twitter bot"
|
||||
};
|
||||
35
experiments/discord-bot/commands/review.js
Normal file
35
experiments/discord-bot/commands/review.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const { getSkinToReview } = require("../s3");
|
||||
const Utils = require("../utils");
|
||||
|
||||
async function reviewSkin(message) {
|
||||
const { filename, md5 } = await getSkinToReview();
|
||||
await Utils.postSkin({
|
||||
filename,
|
||||
md5,
|
||||
title: `Review: ${filename}`,
|
||||
dest: message.client.channels
|
||||
});
|
||||
}
|
||||
|
||||
async function handler(message, args) {
|
||||
const [arg1] = args;
|
||||
const count = arg1 ? Math.min(arg1, 10) : 1;
|
||||
if (count > 1) {
|
||||
message.channel.send(`Going to show ${count} skins to review`);
|
||||
}
|
||||
let i = Number(count);
|
||||
while (i--) {
|
||||
await reviewSkin(message);
|
||||
}
|
||||
if (count > 1) {
|
||||
message.channel.send(`Done reviewing ${count} skins. Thanks!`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
command: "review",
|
||||
handler,
|
||||
usage: "[<number>]",
|
||||
description:
|
||||
"Post a <number> of skins to be reviewed for inclusion in the Twitter bot. Defaults to 1"
|
||||
};
|
||||
19
experiments/discord-bot/commands/skin.js
Normal file
19
experiments/discord-bot/commands/skin.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
const { getFilename } = require("../info");
|
||||
const Utils = require("../utils");
|
||||
async function handler(message, args) {
|
||||
const [md5] = args;
|
||||
const filename = getFilename(md5);
|
||||
await Utils.postSkin({
|
||||
filename,
|
||||
md5,
|
||||
title: `${filename}`,
|
||||
dest: message.channel
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
usage: "<md5-skin>",
|
||||
description: "Show information about a skin",
|
||||
command: "skin",
|
||||
handler
|
||||
};
|
||||
25
experiments/discord-bot/commands/stats.js
Normal file
25
experiments/discord-bot/commands/stats.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
const { getCache } = require("../info");
|
||||
const { getStats } = require("../s3");
|
||||
|
||||
async function handler(message, args) {
|
||||
const info = getCache();
|
||||
let classic = 0;
|
||||
const { tweeted, approved, rejected } = await getStats();
|
||||
Object.values(info).forEach(skin => {
|
||||
if (skin.type === "CLASSIC") {
|
||||
classic++;
|
||||
}
|
||||
});
|
||||
await message.channel.send(`Unique Skins: ${classic.toLocaleString()}
|
||||
Tweeted: ${tweeted.toLocaleString()}
|
||||
Rejected: ${rejected.toLocaleString()}
|
||||
Approved: ${approved.toLocaleString()}
|
||||
`);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
command: "stats",
|
||||
handler,
|
||||
usage: "",
|
||||
description: "Give some statistics about the skin archive"
|
||||
};
|
||||
68
experiments/discord-bot/index.js
Normal file
68
experiments/discord-bot/index.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
const fs = require("fs");
|
||||
const Discord = require("discord.js");
|
||||
const config = require("./config");
|
||||
|
||||
const client = new Discord.Client();
|
||||
|
||||
function handleStats() {}
|
||||
|
||||
const handlers = {
|
||||
stats: handleStats,
|
||||
help: handleHelp
|
||||
};
|
||||
|
||||
const commands = fs
|
||||
.readdirSync("./commands")
|
||||
.filter(file => file.endsWith(".js"))
|
||||
.map(file => {
|
||||
return require(`./commands/${file}`);
|
||||
});
|
||||
|
||||
for (const command of commands) {
|
||||
handlers[command.command] = command.handler;
|
||||
}
|
||||
|
||||
async function handleHelp(message, args) {
|
||||
const commandHelp = commands
|
||||
.map(
|
||||
command =>
|
||||
`\`!${command.command} ${command.usage || ""}\` -- ${
|
||||
command.description
|
||||
}`
|
||||
)
|
||||
.join("\n");
|
||||
|
||||
const help = `
|
||||
I am a helpful bot that can do things with Winamp skins. Here are the commands I know:
|
||||
|
||||
${commandHelp}
|
||||
\`!help\` Show this message
|
||||
|
||||
You can issue these commands in a DM to me or in any channel. For tasks relating to approving skins, I will respond in the #tweet-bot channel.
|
||||
`.trim();
|
||||
message.channel.send(help);
|
||||
}
|
||||
|
||||
client.on("message", async message => {
|
||||
if (message.author.bot) {
|
||||
return;
|
||||
}
|
||||
const [rawCommand, ...args] = message.content.split(" ");
|
||||
if (!rawCommand.startsWith("!")) {
|
||||
return;
|
||||
}
|
||||
const command = rawCommand.slice(1);
|
||||
console.log("New command: ", command);
|
||||
console.log("User: ", message.author.username);
|
||||
console.log("Arguments: ", args);
|
||||
const handler = handlers[command];
|
||||
if (handler != null) {
|
||||
handler(message, args);
|
||||
}
|
||||
});
|
||||
|
||||
client.on("error", error => {
|
||||
console.error("The WebSocket encountered an error:", error);
|
||||
});
|
||||
|
||||
client.login(config.discordToken);
|
||||
27
experiments/discord-bot/info.js
Normal file
27
experiments/discord-bot/info.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
let cache = null;
|
||||
|
||||
function getCache() {
|
||||
if (cache == null) {
|
||||
cache = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join("/Volumes/Mobile Backup/skins/cache", "info.json"),
|
||||
"utf8"
|
||||
)
|
||||
);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
function getInfo(md5) {
|
||||
return getCache()[md5];
|
||||
}
|
||||
|
||||
function getFilename(md5) {
|
||||
const info = getInfo(md5);
|
||||
return info.filePaths.map(filepath => path.basename(filepath))[0];
|
||||
}
|
||||
|
||||
module.exports = { getInfo, getCache, getFilename };
|
||||
17
experiments/discord-bot/package.json
Normal file
17
experiments/discord-bot/package.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "discord-bot",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.429.0",
|
||||
"discord.js": "^11.4.2",
|
||||
"md5": "^2.2.1",
|
||||
"node-fetch": "^2.3.0",
|
||||
"rgb-hex": "^2.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"prettier": {}
|
||||
}
|
||||
115
experiments/discord-bot/s3.js
Normal file
115
experiments/discord-bot/s3.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
const AWS = require("aws-sdk");
|
||||
AWS.config.update({ region: "us-west-2" });
|
||||
|
||||
const s3 = new AWS.S3();
|
||||
|
||||
function getFile(key) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const bucketName = "winamp2-js-skins";
|
||||
s3.getObject({ Bucket: bucketName, Key: key }, (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const body = Buffer.from(data.Body).toString("utf8");
|
||||
resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function putFile(key, body) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const bucketName = "winamp2-js-skins";
|
||||
s3.putObject({ Bucket: bucketName, Key: key, Body: body }, (err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getLines(body) {
|
||||
return body.split("\n").map(line => line.trim());
|
||||
}
|
||||
|
||||
async function getStatus(md5) {
|
||||
const [approved, rejected, tweeted] = await Promise.all([
|
||||
getFile("approved.txt"),
|
||||
getFile("rejected.txt"),
|
||||
getFile("tweeted.txt")
|
||||
]);
|
||||
const approvedSet = new Set(getLines(approved));
|
||||
const rejectedSet = new Set(getLines(rejected));
|
||||
const tweetedSet = new Set(getLines(tweeted));
|
||||
if (tweetedSet.has(md5)) {
|
||||
return "TWEETED";
|
||||
}
|
||||
if (rejectedSet.has(md5)) {
|
||||
return "REJECTED";
|
||||
}
|
||||
if (approvedSet.has(md5)) {
|
||||
return "APPROVED";
|
||||
}
|
||||
return "UNREVIEWED";
|
||||
}
|
||||
|
||||
async function getStats() {
|
||||
const [approved, rejected, tweeted] = await Promise.all([
|
||||
getFile("approved.txt"),
|
||||
getFile("rejected.txt"),
|
||||
getFile("tweeted.txt")
|
||||
]);
|
||||
return {
|
||||
approved: new Set(approved).size - new Set(tweeted).size,
|
||||
rejected: new Set(rejected).size,
|
||||
tweeted: new Set(tweeted).size
|
||||
};
|
||||
}
|
||||
|
||||
async function getSkinToReview() {
|
||||
console.log("Reading from s3...");
|
||||
const [filenames, approved, rejected, tweeted] = await Promise.all([
|
||||
getFile("filenames.txt"),
|
||||
getFile("approved.txt"),
|
||||
getFile("rejected.txt"),
|
||||
getFile("tweeted.txt")
|
||||
]);
|
||||
console.log("Got all files");
|
||||
|
||||
const approvedSet = new Set(getLines(approved));
|
||||
const rejectedSet = new Set(getLines(rejected));
|
||||
const tweetedSet = new Set(getLines(tweeted));
|
||||
|
||||
const filenameLines = getLines(filenames);
|
||||
const skins = filenameLines.map(line => {
|
||||
const [md5, ...filename] = line.split(" ");
|
||||
return { md5, filename: filename.join(" ") };
|
||||
});
|
||||
const toReview = skins.filter(({ md5 }) => {
|
||||
return !(
|
||||
approvedSet.has(md5) ||
|
||||
rejectedSet.has(md5) ||
|
||||
tweetedSet.has(md5)
|
||||
);
|
||||
});
|
||||
console.log(toReview.length, "skins to review");
|
||||
return toReview[0];
|
||||
}
|
||||
|
||||
async function appendLine(key, line) {
|
||||
const currentContent = await getFile(key);
|
||||
const newContent = `${currentContent}${line}\n`;
|
||||
return putFile(key, newContent);
|
||||
}
|
||||
|
||||
async function approve(md5) {
|
||||
return appendLine("approved.txt", md5);
|
||||
}
|
||||
|
||||
async function reject(md5) {
|
||||
return appendLine("rejected.txt", md5);
|
||||
}
|
||||
|
||||
module.exports = { getSkinToReview, approve, reject, getStatus, getStats };
|
||||
94
experiments/discord-bot/utils.js
Normal file
94
experiments/discord-bot/utils.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
const Discord = require("discord.js");
|
||||
const rgbHex = require("rgb-hex");
|
||||
const { getInfo } = require("./info");
|
||||
const { approve, reject, getStatus } = require("./s3");
|
||||
|
||||
const filter = reaction => {
|
||||
return ["👍", "👎"].some(name => reaction.emoji.name === name);
|
||||
};
|
||||
|
||||
async function postSkin({ filename, md5, title, dest }) {
|
||||
const screenshotUrl = `https://s3.amazonaws.com/webamp-uploaded-skins/screenshots/${md5}.png`;
|
||||
const skinUrl = `https://s3.amazonaws.com/webamp-uploaded-skins/skins/${md5}.wsz`;
|
||||
const webampUrl = `https://webamp.org?skinUrl=${skinUrl}`;
|
||||
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setTitle(title)
|
||||
.addField("Try Online", `[webamp.org](${webampUrl})`, true)
|
||||
.addField("Download", `[${filename}](${skinUrl})`, true)
|
||||
.addField("Md5", md5, true)
|
||||
.setImage(screenshotUrl);
|
||||
|
||||
const info = getInfo(md5);
|
||||
if (info == null) {
|
||||
embed.addField("Warning", "Cached metadata not found", true);
|
||||
} else {
|
||||
if (info.averageColor) {
|
||||
const color = rgbHex(info.averageColor);
|
||||
if (String(color).length === 6) {
|
||||
embed.setColor(`#${color}`);
|
||||
} else {
|
||||
console.log("Did not get a safe color from ", info.averageColor);
|
||||
console.log("Got ", color);
|
||||
}
|
||||
}
|
||||
if (info.emails != null && info.emails.length) {
|
||||
const uniqueEmails = Array.from(new Set(info.emails));
|
||||
// TODO: Fix email parsing
|
||||
embed.addField("Emails", uniqueEmails.join(", "), true);
|
||||
}
|
||||
if (info.tweetUrl != null) {
|
||||
let likes = "";
|
||||
if (info.twitterLikes != null) {
|
||||
likes = `(${Number(info.twitterLikes).toLocaleString()} likes) `;
|
||||
}
|
||||
embed.addField(
|
||||
"Tweet Status",
|
||||
`[Tweeted](${info.tweetUrl}) ${likes}🐦`,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
const status = await getStatus(md5);
|
||||
if (status === "UNREVIEWED") {
|
||||
embed.setFooter("React with 👍 or 👎 to approve or deny");
|
||||
}
|
||||
embed.addField("Tweet Status", getPrettyTwitterStatus(status), true);
|
||||
}
|
||||
}
|
||||
|
||||
const msg = await dest.send(embed);
|
||||
|
||||
await msg.awaitReactions(filter, { max: 1 }).then(async collected => {
|
||||
const vote = collected.first();
|
||||
const user = vote.users.first();
|
||||
switch (vote.emoji.name) {
|
||||
case "👍":
|
||||
await approve(md5);
|
||||
console.log(`${user.username} approved ${md5}`);
|
||||
await msg.channel.send(`${filename} was approved by ${user.username}`);
|
||||
msg.react("✅");
|
||||
break;
|
||||
case "👎":
|
||||
await reject(md5);
|
||||
console.log(`${user.username} rejected ${md5}`);
|
||||
await msg.channel.send(`${filename} was rejected by ${user.username}`);
|
||||
msg.react("❌");
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getPrettyTwitterStatus(status) {
|
||||
switch (status) {
|
||||
case "APPROVED":
|
||||
return "Approved ✅";
|
||||
case "REJECTED":
|
||||
return "Rejected ❌";
|
||||
case "UNREVIEWED":
|
||||
return "Unreviewed ❔";
|
||||
case "TWEETED":
|
||||
return "Tweeted 🐦";
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { postSkin };
|
||||
156
experiments/discord-bot/yarn.lock
Normal file
156
experiments/discord-bot/yarn.lock
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
async-limiter@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||
|
||||
aws-sdk@^2.429.0:
|
||||
version "2.429.0"
|
||||
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.429.0.tgz#27c32734f6473f69a39c29a694d54d15b5beb68a"
|
||||
dependencies:
|
||||
buffer "4.9.1"
|
||||
events "1.1.1"
|
||||
ieee754 "1.1.8"
|
||||
jmespath "0.15.0"
|
||||
querystring "0.2.0"
|
||||
sax "1.2.1"
|
||||
url "0.10.3"
|
||||
uuid "3.3.2"
|
||||
xml2js "0.4.19"
|
||||
|
||||
base64-js@^1.0.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
|
||||
|
||||
buffer@4.9.1:
|
||||
version "4.9.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
|
||||
dependencies:
|
||||
base64-js "^1.0.2"
|
||||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
charenc@~0.0.1:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||
|
||||
crypt@~0.0.1:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||
|
||||
discord.js@^11.4.2:
|
||||
version "11.4.2"
|
||||
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-11.4.2.tgz#54586981926521572051f2a30b984aad2b49786e"
|
||||
dependencies:
|
||||
long "^4.0.0"
|
||||
prism-media "^0.0.3"
|
||||
snekfetch "^3.6.4"
|
||||
tweetnacl "^1.0.0"
|
||||
ws "^4.0.0"
|
||||
|
||||
events@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||
|
||||
ieee754@1.1.8:
|
||||
version "1.1.8"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||
|
||||
is-buffer@~1.1.1:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||
|
||||
isarray@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
jmespath@0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||
|
||||
long@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||
|
||||
md5@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
|
||||
dependencies:
|
||||
charenc "~0.0.1"
|
||||
crypt "~0.0.1"
|
||||
is-buffer "~1.1.1"
|
||||
|
||||
node-fetch@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5"
|
||||
|
||||
prism-media@^0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/prism-media/-/prism-media-0.0.3.tgz#8842d4fae804f099d3b48a9a38e3c2bab6f4855b"
|
||||
|
||||
punycode@1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
|
||||
|
||||
querystring@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
|
||||
rgb-hex@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/rgb-hex/-/rgb-hex-2.1.0.tgz#c773c5fe2268a25578d92539a82a7a5ce53beda6"
|
||||
|
||||
safe-buffer@~5.1.0:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
sax@1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
|
||||
|
||||
sax@>=0.6.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
|
||||
snekfetch@^3.6.4:
|
||||
version "3.6.4"
|
||||
resolved "https://registry.yarnpkg.com/snekfetch/-/snekfetch-3.6.4.tgz#d13e80a616d892f3d38daae4289f4d258a645120"
|
||||
|
||||
tweetnacl@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.1.tgz#2594d42da73cd036bd0d2a54683dd35a6b55ca17"
|
||||
|
||||
url@0.10.3:
|
||||
version "0.10.3"
|
||||
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||
dependencies:
|
||||
punycode "1.3.2"
|
||||
querystring "0.2.0"
|
||||
|
||||
uuid@3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
|
||||
ws@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-4.1.0.tgz#a979b5d7d4da68bf54efe0408967c324869a7289"
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
xml2js@0.4.19:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
|
||||
dependencies:
|
||||
sax ">=0.6.0"
|
||||
xmlbuilder "~9.0.1"
|
||||
|
||||
xmlbuilder@~9.0.1:
|
||||
version "9.0.7"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
|
||||
Loading…
Add table
Add a link
Reference in a new issue