mirror of
https://github.com/janderedev/automod.git
synced 2024-12-22 10:45:27 +00:00
make bridge user configurable
This commit is contained in:
parent
f34d01745d
commit
96446b3e1d
109
bot/src/bot/commands/bridge.ts
Normal file
109
bot/src/bot/commands/bridge.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { ulid } from "ulid";
|
||||
import { dbs } from "../..";
|
||||
import CommandCategory from "../../struct/commands/CommandCategory";
|
||||
import SimpleCommand from "../../struct/commands/SimpleCommand";
|
||||
import MessageCommandContext from "../../struct/MessageCommandContext";
|
||||
import { DEFAULT_PREFIX } from "../modules/command_handler";
|
||||
import { isBotManager, NO_MANAGER_MSG } from "../util";
|
||||
|
||||
const DISCORD_INVITE_URL = 'https://discord.com/api/oauth2/authorize?client_id=965692929643524136&permissions=536996864&scope=bot%20applications.commands'; // todo: read this from env or smth
|
||||
|
||||
export default {
|
||||
name: 'bridge',
|
||||
aliases: null,
|
||||
description: 'Bridge a channel with Discord',
|
||||
category: CommandCategory.Misc,
|
||||
run: async (message: MessageCommandContext, args: string[]) => {
|
||||
if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG);
|
||||
|
||||
switch(args[0]?.toLowerCase()) {
|
||||
case 'link': {
|
||||
const count = await dbs.BRIDGE_CONFIG.count({ revolt: message.channel_id });
|
||||
if (count) return message.reply(`This channel is already bridged.`);
|
||||
|
||||
// Invalidate previous bridge request
|
||||
await dbs.BRIDGE_REQUESTS.remove({ revolt: message.channel_id });
|
||||
|
||||
const reqId = ulid();
|
||||
await dbs.BRIDGE_REQUESTS.insert({
|
||||
id: reqId,
|
||||
revolt: message.channel_id,
|
||||
expires: Date.now() + (1000 * 60 * 15),
|
||||
});
|
||||
|
||||
await message.reply(`### Link request created.\n`
|
||||
+ `Request ID: \`${reqId}\`\n\n`
|
||||
+ `[Invite the bridge bot to your Discord server](<${DISCORD_INVITE_URL}>) `
|
||||
+ `and run \`/bridge confirm ${reqId}\` in the channel you wish to link.\n`
|
||||
+ `This request expires in 15 minutes.`);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'unlink': {
|
||||
const res = await dbs.BRIDGE_CONFIG.remove({ revolt: message.channel_id });
|
||||
if (res.deletedCount) await message.reply(`Channel unlinked!`);
|
||||
else await message.reply(`Unable to unlink; no channel linked.`);
|
||||
break;
|
||||
}
|
||||
case 'unlink_all': {
|
||||
const query = { revolt: { $in: message.channel?.server?.channel_ids || [] } };
|
||||
if (args[1] == 'CONFIRM') {
|
||||
const res = await dbs.BRIDGE_CONFIG.remove(query);
|
||||
if (res.deletedCount) {
|
||||
await message.reply(`All channels have been unlinked. (Count: **${res.deletedCount}**)`);
|
||||
} else {
|
||||
await message.reply(`No bridged channels found; nothing to delete.`);
|
||||
}
|
||||
} else {
|
||||
const res = await dbs.BRIDGE_CONFIG.count(query);
|
||||
if (!res) await message.reply(`No bridged channels found; nothing to delete.`);
|
||||
else {
|
||||
await message.reply(`${res} bridged channels found. `
|
||||
+ `Run \`${DEFAULT_PREFIX}bridge unlink_all CONFIRM\` to confirm deletion.`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'list': {
|
||||
const links = await dbs.BRIDGE_CONFIG.find({ revolt: { $in: message.channel?.server?.channel_ids || [] } });
|
||||
|
||||
await message.reply({
|
||||
content: '#',
|
||||
embeds: [
|
||||
{
|
||||
type: 'Text',
|
||||
title: `Bridges in ${message.channel?.server?.name}`,
|
||||
description: `**${links.length}** bridged channels found.\n\n`
|
||||
+ links.map(l => `<#${l.revolt}> **->** ${l.discord}\n`),
|
||||
}
|
||||
]
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'help': {
|
||||
await message.reply({
|
||||
content: '#',
|
||||
embeds: [
|
||||
{
|
||||
type: 'Text',
|
||||
title: 'Discord Bridge',
|
||||
description: `Bridges allow you to link your Revolt server to a Discord server `
|
||||
+ `by relaying all messages.\n\n`
|
||||
+ `To link a channel, first run \`${DEFAULT_PREFIX}bridge link\` on Revolt. `
|
||||
+ `This will provide you with a link ID.\n`
|
||||
+ `On Discord, first [add the Bridge bot to your server](<${DISCORD_INVITE_URL}>), `
|
||||
+ `then run the command: \`/bridge confirm [ID]\`.\n\n`
|
||||
+ `You can list all bridges in a Revolt server by running \`${DEFAULT_PREFIX}bridge list\`\n\n`
|
||||
+ `To unlink a channel, run \`/bridge unlink\` from either Discord or Revolt. If you wish to `
|
||||
+ `unbridge all channels in a Revolt server, run \`${DEFAULT_PREFIX}bridge unlink_all\`.`
|
||||
}
|
||||
]
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
await message.reply(`Run \`${DEFAULT_PREFIX}bridge help\` for help.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} as SimpleCommand;
|
|
@ -11,6 +11,8 @@ import PendingLogin from './struct/PendingLogin';
|
|||
import TempBan from './struct/TempBan';
|
||||
import { VoteEntry } from './bot/commands/votekick';
|
||||
import ScannedUser from './struct/ScannedUser';
|
||||
import BridgeRequest from './struct/BridgeRequest';
|
||||
import BridgeConfig from './struct/BridgeConfig';
|
||||
|
||||
logger.info('Initializing client');
|
||||
|
||||
|
@ -32,6 +34,8 @@ const dbs = {
|
|||
TEMPBANS: db.get<TempBan>('tempbans'),
|
||||
VOTEKICKS: db.get<VoteEntry>('votekicks'),
|
||||
SCANNED_USERS: db.get<ScannedUser>('scanned_users'),
|
||||
BRIDGE_CONFIG: db.get<BridgeConfig>('bridge_config'),
|
||||
BRIDGE_REQUESTS: db.get<BridgeRequest>('bridge_requests'),
|
||||
}
|
||||
|
||||
export { client, dbs }
|
||||
|
|
13
bot/src/struct/BridgeConfig.ts
Normal file
13
bot/src/struct/BridgeConfig.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
export default class {
|
||||
// Revolt channel ID
|
||||
revolt?: string;
|
||||
|
||||
// Discord channel ID
|
||||
discord?: string;
|
||||
|
||||
// Discord webhook
|
||||
discordWebhook?: {
|
||||
id: string;
|
||||
token: string;
|
||||
}
|
||||
}
|
9
bot/src/struct/BridgeRequest.ts
Normal file
9
bot/src/struct/BridgeRequest.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export default class {
|
||||
// Bridge request ID, needed to confirm link from Discord side
|
||||
id: string;
|
||||
|
||||
// The Revolt channel ID
|
||||
revolt: string;
|
||||
|
||||
expires: number;
|
||||
}
|
|
@ -13,8 +13,10 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^0.4.1",
|
||||
"@janderedev/revolt.js": "^5.2.8-patch.2",
|
||||
"axios": "^0.26.1",
|
||||
"discord-api-types": "^0.31.2",
|
||||
"discord.js": "^13.6.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
|
|
|
@ -23,5 +23,6 @@ const login = () => new Promise((resolve: (value: Discord.Client) => void) => {
|
|||
});
|
||||
|
||||
import('./events');
|
||||
import('./commands');
|
||||
|
||||
export { client, login }
|
||||
|
|
121
bridge/src/discord/commands.ts
Normal file
121
bridge/src/discord/commands.ts
Normal file
|
@ -0,0 +1,121 @@
|
|||
// fuck slash commands
|
||||
|
||||
import { client } from "./client";
|
||||
import { REST } from '@discordjs/rest';
|
||||
import { Routes } from 'discord-api-types/v9';
|
||||
import { BRIDGE_CONFIG, BRIDGE_REQUESTS, logger } from "..";
|
||||
import { TextChannel } from "discord.js";
|
||||
|
||||
const COMMANDS: any[] = [
|
||||
{
|
||||
name: 'bridge',
|
||||
description: 'Confirm or delete Revolt bridges',
|
||||
options: [
|
||||
{
|
||||
name: 'confirm',
|
||||
description: 'Confirm a bridge initiated from Revolt',
|
||||
type: 1, // Subcommand
|
||||
options: [
|
||||
{
|
||||
name: "id",
|
||||
description: "The bridge request ID",
|
||||
required: true,
|
||||
type: 3,
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'unlink',
|
||||
description: 'Unbridge the current channel',
|
||||
type: 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken(process.env['DISCORD_TOKEN']!);
|
||||
|
||||
client.once('ready', async () => {
|
||||
try {
|
||||
logger.info(`Refreshing application commands.`);
|
||||
|
||||
if (process.env.NODE_ENV != 'production' && process.env.DEV_GUILD) {
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(client.user!.id, process.env.DEV_GUILD),
|
||||
{ body: COMMANDS },
|
||||
);
|
||||
logger.done(`Application commands for ${process.env.DEV_GUILD} have been updated.`);
|
||||
} else {
|
||||
await rest.put(
|
||||
Routes.applicationCommands(client.user!.id),
|
||||
{ body: COMMANDS },
|
||||
);
|
||||
logger.done(`Global application commands have been updated.`);
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
|
||||
client.on('interactionCreate', async interaction => {
|
||||
try {
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
logger.debug(`Command received: /${interaction.commandName}`);
|
||||
|
||||
// The revolutionary Jan command handler
|
||||
switch(interaction.commandName) {
|
||||
case 'bridge':
|
||||
if (!interaction.memberPermissions?.has('MANAGE_GUILD')) {
|
||||
return await interaction.reply(`\`MANAGE_GUILD\` permission is required for this.`);
|
||||
}
|
||||
|
||||
const ownPerms = (interaction.channel as TextChannel).permissionsFor(client.user!)!;
|
||||
switch(interaction.options.getSubcommand(true)) {
|
||||
case 'confirm':
|
||||
if (!ownPerms.has('MANAGE_WEBHOOKS'))
|
||||
return interaction.reply('Sorry, I lack permission to manage webhooks in this channel.');
|
||||
|
||||
const id = interaction.options.getString('id', true);
|
||||
const request = await BRIDGE_REQUESTS.findOne({ id: id });
|
||||
if (!request || request.expires < Date.now()) return await interaction.reply('Unknown ID.');
|
||||
|
||||
const webhook = await (interaction.channel as TextChannel)
|
||||
.createWebhook('AutoMod Bridge', { avatar: client.user?.avatarURL() });
|
||||
|
||||
await BRIDGE_REQUESTS.remove({ id: id });
|
||||
await BRIDGE_CONFIG.insert({
|
||||
discord: interaction.channelId,
|
||||
revolt: request.revolt,
|
||||
discordWebhook: {
|
||||
id: webhook.id,
|
||||
token: webhook.token || '',
|
||||
}
|
||||
});
|
||||
|
||||
return await interaction.reply(`✅ Channel bridged!`);
|
||||
case 'unlink':
|
||||
const res = await BRIDGE_CONFIG.findOneAndDelete({ discord: interaction.channelId });
|
||||
if (res?._id) {
|
||||
await interaction.reply('Channel unbridged.');
|
||||
if (ownPerms.has('MANAGE_WEBHOOKS') && res.discordWebhook) {
|
||||
try {
|
||||
const hooks = await (interaction.channel as TextChannel).fetchWebhooks();
|
||||
if (hooks.get(res?.discordWebhook?.id)) await hooks.get(res?.discordWebhook?.id)
|
||||
?.delete('Channel has been unbridged');
|
||||
} catch(_) {}
|
||||
}
|
||||
}
|
||||
else await interaction.reply('This channel is not bridged.');
|
||||
|
||||
break;
|
||||
default: await interaction.reply('Unknown subcommand');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
if (interaction.isCommand()) interaction.reply('An error has occurred: ' + e).catch(() => {});
|
||||
}
|
||||
});
|
|
@ -6,6 +6,7 @@ import { login as loginDiscord } from './discord/client';
|
|||
import { ICollection } from 'monk';
|
||||
import BridgeConfig from './types/BridgeConfig';
|
||||
import BridgedMessage from './types/BridgedMessage';
|
||||
import BridgeRequest from './types/BridgeRequest';
|
||||
|
||||
config();
|
||||
|
||||
|
@ -13,6 +14,7 @@ const logger: Log75 = new (Log75 as any).default(LogLevel.Debug);
|
|||
const db = getDb();
|
||||
const BRIDGED_MESSAGES: ICollection<BridgedMessage> = db.get('bridged_messages');
|
||||
const BRIDGE_CONFIG: ICollection<BridgeConfig> = db.get('bridge_config');
|
||||
const BRIDGE_REQUESTS: ICollection<BridgeRequest> = db.get('bridge_requests');
|
||||
|
||||
for (const v of [ 'REVOLT_TOKEN', 'DISCORD_TOKEN', 'DB_STRING' ]) {
|
||||
if (!process.env[v]) {
|
||||
|
@ -28,4 +30,4 @@ for (const v of [ 'REVOLT_TOKEN', 'DISCORD_TOKEN', 'DB_STRING' ]) {
|
|||
]);
|
||||
})();
|
||||
|
||||
export { logger, db, BRIDGED_MESSAGES, BRIDGE_CONFIG }
|
||||
export { logger, db, BRIDGED_MESSAGES, BRIDGE_CONFIG, BRIDGE_REQUESTS }
|
||||
|
|
|
@ -77,8 +77,36 @@ client.on('message', async message => {
|
|||
if (bridgedMsg) return logger.debug(`Revolt: Message has already been bridged; ignoring`);
|
||||
if (!bridgeCfg?.discord) return logger.debug(`Revolt: No Discord channel associated`);
|
||||
if (!bridgeCfg.discordWebhook) {
|
||||
// Todo: Create a new webhook instead of exiting
|
||||
return logger.debug(`Revolt: No Discord webhook stored`);
|
||||
logger.debug(`Revolt: No Discord webhook stored; Creating new Webhook`);
|
||||
|
||||
try {
|
||||
const channel = await discordClient.channels.fetch(bridgeCfg.discord);
|
||||
if (!channel || !channel.isText()) throw 'Error: Unable to fetch channel';
|
||||
const ownPerms = (channel as TextChannel).permissionsFor(discordClient.user!);
|
||||
if (!ownPerms?.has('MANAGE_WEBHOOKS')) throw 'Error: Bot user does not have MANAGE_WEBHOOKS permission';
|
||||
|
||||
const hook = await (channel as TextChannel).createWebhook('AutoMod Bridge', { avatar: discordClient.user?.avatarURL() });
|
||||
|
||||
bridgeCfg.discordWebhook = {
|
||||
id: hook.id,
|
||||
token: hook.token || '',
|
||||
};
|
||||
await BRIDGE_CONFIG.update(
|
||||
{ revolt: message.channel_id },
|
||||
{
|
||||
$set: {
|
||||
discordWebhook: bridgeCfg.discordWebhook,
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch(e) {
|
||||
logger.warn(`Unable to create new webhook for channel ${bridgeCfg.discord}; Deleting link\n${e}`);
|
||||
await BRIDGE_CONFIG.remove({ revolt: message.channel_id });
|
||||
await message.channel?.sendMessage(':warning: I was unable to create a webhook in the bridged Discord channel. '
|
||||
+ `The bridge has been removed; if you wish to rebridge, use the \`/bridge\` command.`).catch(() => {});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await BRIDGED_MESSAGES.update(
|
||||
|
@ -103,8 +131,8 @@ client.on('message', async message => {
|
|||
);
|
||||
|
||||
const client = new WebhookClient({
|
||||
id: bridgeCfg.discordWebhook.id,
|
||||
token: bridgeCfg.discordWebhook.token,
|
||||
id: bridgeCfg.discordWebhook!.id,
|
||||
token: bridgeCfg.discordWebhook!.token,
|
||||
});
|
||||
|
||||
const payload: MessagePayload|WebhookMessageOptions = {
|
||||
|
@ -176,7 +204,15 @@ client.on('message', async message => {
|
|||
});
|
||||
})
|
||||
.catch(async e => {
|
||||
console.error('Failed to execute webhook', e?.response?.data ?? e);
|
||||
console.error('Failed to execute webhook:', e?.response?.data ?? e);
|
||||
if (`${e}` == 'DiscordAPIError: Unknown Webhook') {
|
||||
try {
|
||||
logger.warn('Revolt: Got Unknown Webhook error, deleting webhook config');
|
||||
await BRIDGE_CONFIG.update({ revolt: message.channel_id }, { $set: { discordWebhook: undefined } });
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
|
|
9
bridge/src/types/BridgeRequest.ts
Normal file
9
bridge/src/types/BridgeRequest.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export default class {
|
||||
// Bridge request ID, needed to confirm link from Discord side
|
||||
id: string;
|
||||
|
||||
// The Revolt channel ID
|
||||
revolt: string;
|
||||
|
||||
expires: number;
|
||||
}
|
|
@ -18,6 +18,25 @@
|
|||
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.4.0.tgz#b6488286a1cc7b41b644d7e6086f25a1c1e6f837"
|
||||
integrity sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==
|
||||
|
||||
"@discordjs/collection@^0.7.0-dev":
|
||||
version "0.7.0-dev.1650672508-3617093"
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.7.0-dev.1650672508-3617093.tgz#2b418f650922b1e52b057b481558bb6b377bd4d2"
|
||||
integrity sha512-Got8gPiFFEwY0tJo6hK/ZGvg8LFEYMyopchL/l5WjvN5YXDSKqlcSfWk3SqA9F8Eb2ZloauUoXY2B3uMMJUUBA==
|
||||
|
||||
"@discordjs/rest@^0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-0.4.1.tgz#d0a7e79df7a7f59bd01630013b3c70231e22a31d"
|
||||
integrity sha512-rtWy+AIfNlfjGkAgA2TJLASdqli07aTNQceVGT6RQQiQaEqV0nsfBO4WtDlDzk7PmO3w+InP3dpwEolJI5jz0A==
|
||||
dependencies:
|
||||
"@discordjs/collection" "^0.7.0-dev"
|
||||
"@sapphire/async-queue" "^1.3.1"
|
||||
"@sapphire/snowflake" "^3.2.1"
|
||||
"@types/node-fetch" "^2.6.1"
|
||||
discord-api-types "^0.29.0"
|
||||
form-data "^4.0.0"
|
||||
node-fetch "^2.6.7"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@insertish/exponential-backoff@3.1.0-patch.0":
|
||||
version "3.1.0-patch.0"
|
||||
resolved "https://registry.yarnpkg.com/@insertish/exponential-backoff/-/exponential-backoff-3.1.0-patch.0.tgz#1fff134f70fc0906d11d09069d51183b542e42cf"
|
||||
|
@ -55,11 +74,16 @@
|
|||
ulid "^2.3.0"
|
||||
ws "^8.2.2"
|
||||
|
||||
"@sapphire/async-queue@^1.1.9":
|
||||
"@sapphire/async-queue@^1.1.9", "@sapphire/async-queue@^1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.3.1.tgz#9d861e626dbffae02d808e13f823d4510e450a78"
|
||||
integrity sha512-FFTlPOWZX1kDj9xCAsRzH5xEJfawg1lNoYAA+ecOWJMHOfiZYb1uXOI3ne9U4UILSEPwfE68p3T9wUHwIQfR0g==
|
||||
|
||||
"@sapphire/snowflake@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-3.2.1.tgz#027f217779ec7fd324d35cf941b3de49b4f67877"
|
||||
integrity sha512-vmZq1I6J6iNRQVXP+N9HzOMOY4ORB3MunoFeWCw/aBnZTf1cDgDvP0RZFQS53B1TN95AIgFY9T+ItQ/fWAUYWQ==
|
||||
|
||||
"@sindresorhus/is@^4.2.0":
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
||||
|
@ -80,7 +104,7 @@
|
|||
"@types/bson" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node-fetch@^2.5.12":
|
||||
"@types/node-fetch@^2.5.12", "@types/node-fetch@^2.6.1":
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975"
|
||||
integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==
|
||||
|
@ -196,6 +220,16 @@ discord-api-types@^0.26.0:
|
|||
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.26.1.tgz#726f766ddc37d60da95740991d22cb6ef2ed787b"
|
||||
integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==
|
||||
|
||||
discord-api-types@^0.29.0:
|
||||
version "0.29.0"
|
||||
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.29.0.tgz#8346352b623ddd8d8eed386b6eb758e2d82d6005"
|
||||
integrity sha512-Ekq1ICNpOTVajXKZguNFrsDeTmam+ZeA38txsNLZnANdXUjU6QBPIZLUQTC6MzigFGb0Tt8vk4xLnXmzv0shNg==
|
||||
|
||||
discord-api-types@^0.31.2:
|
||||
version "0.31.2"
|
||||
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.31.2.tgz#8d131e25340bd695815af3bb77128a6993c1b516"
|
||||
integrity sha512-gpzXTvFVg7AjKVVJFH0oJGC0q0tO34iJGSHZNz9u3aqLxlD6LfxEs9wWVVikJqn9gra940oUTaPFizCkRDcEiA==
|
||||
|
||||
discord.js@^13.6.0:
|
||||
version "13.6.0"
|
||||
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.6.0.tgz#d8a8a591dbf25cbcf9c783d5ddf22c4694860475"
|
||||
|
@ -389,7 +423,7 @@ ms@2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
node-fetch@^2.6.1, node-fetch@^2.6.7:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
|
|
Loading…
Reference in a new issue