mirror of
https://github.com/DarthKilroy/Spot.git
synced 2025-12-19 02:06:48 +00:00
rewrite
this is a rewrite of the bot in typescript. detritus is used as a discord library instead of discord.js
This commit is contained in:
parent
6688d4dcd8
commit
56091a6df7
33 changed files with 731 additions and 1116 deletions
|
|
@ -1,6 +1,6 @@
|
|||
FROM alpine:latest
|
||||
|
||||
RUN apk add nodejs-current yarn
|
||||
RUN apk add nodejs-current npm yarn git
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock /app/
|
||||
|
|
@ -8,4 +8,4 @@ RUN yarn
|
|||
|
||||
COPY . /app
|
||||
|
||||
CMD ["node", "bot.js"]
|
||||
CMD ["yarn", "ts-node", "bot.ts"]
|
||||
45
bot.js
45
bot.js
|
|
@ -1,45 +0,0 @@
|
|||
const Logger = require('@lilywonhalf/pretty-logger');
|
||||
|
||||
const { Client } = require('discord.js');
|
||||
const Config = require('./config.json');
|
||||
const Command = require('./model/command');
|
||||
const fs = require('fs');
|
||||
const dotenv = require('dotenv')
|
||||
|
||||
dotenv.config();
|
||||
|
||||
global.bot = new Client({ fetchAllMembers: true });
|
||||
global.isRightGuild = (guildSnowflake) => guildSnowflake === Config.guild;
|
||||
|
||||
const crashRecover = (exception) => {
|
||||
Logger.exception(exception);
|
||||
Logger.notice('Need reboot');
|
||||
};
|
||||
|
||||
process.on('uncaughtException', crashRecover);
|
||||
bot.on('error', crashRecover);
|
||||
|
||||
Command.init();
|
||||
|
||||
const help = require("./command/help");
|
||||
bot.ws.on('INTERACTION_CREATE', help.interactionHandler);
|
||||
|
||||
bot.on('ready', () => {
|
||||
fs.readdirSync('./event/')
|
||||
.filter(filename => filename.endsWith('.js'))
|
||||
.map(filename => filename.substr(0, filename.length - 3))
|
||||
.forEach(filename => {
|
||||
const event = filename.replace(/([_-][a-z])/gu, character => `${character.substr(1).toUpperCase()}`);
|
||||
|
||||
if (filename !== 'ready') {
|
||||
bot.on(event, require(`./event/${filename}`));
|
||||
} else {
|
||||
require(`./event/${filename}`)();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Logger.info('--------');
|
||||
|
||||
Logger.info('Logging in...');
|
||||
bot.login(process.env.token);
|
||||
48
bot.ts
Normal file
48
bot.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import * as dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
|
||||
import config from "./config";
|
||||
|
||||
import { Socket } from 'detritus-client-socket/lib/gateway';
|
||||
import { Client } from "detritus-client-rest";
|
||||
|
||||
import * as types from 'discord-api-types/v10';
|
||||
|
||||
const socket = new Socket(process.env.token!, { intents: 33283 });
|
||||
export const restClient = new Client(process.env.token);
|
||||
|
||||
import handleMessage from './messageHandler';
|
||||
import help from './command/help';
|
||||
|
||||
socket.on('packet', async (evt: types.GatewayDispatchPayload) => handleEvtInner(evt).catch(e => console.error(e, JSON.stringify(e, null, 2))));
|
||||
|
||||
async function handleEvtInner(evt: types.GatewayDispatchPayload) {
|
||||
if (evt.op != 0) return;
|
||||
if (evt.t == 'READY') {
|
||||
console.log("successfully logged in:", evt.d.user);
|
||||
}
|
||||
|
||||
// i am quite sure this is the correct type
|
||||
// @ts-expect-error
|
||||
if (evt.t == 'INTERACTION_CREATE') await help.interactionHandler(evt.d);
|
||||
|
||||
if (evt.t == 'GUILD_MEMBER_ADD') {
|
||||
if (evt.d.guild_id != config.guild) return;
|
||||
if (evt.d.user!.bot) return;
|
||||
|
||||
const msg = `Welcome, <@${evt.d.user!.id}>! If you joined for any specific support questions `
|
||||
+ `please check out <#863171642905591830> first to see if your issue is known, `
|
||||
+ `and make sure that your app is up-to-date before posting.`;
|
||||
await restClient.createMessage(config.channels.joins, { content: msg });
|
||||
}
|
||||
|
||||
if (evt.t == 'MESSAGE_CREATE') {
|
||||
if (evt.d.guild_id != config.guild) return;
|
||||
if (evt.d.author.bot) return;
|
||||
await handleMessage(evt.d).catch(console.error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
console.log('Logging in...');
|
||||
socket.connect("https://gateway.discord.gg");
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
const Logger = require('@lilywonhalf/pretty-logger');
|
||||
const Discord = require('discord.js');
|
||||
const CommandCategory = require('../model/command-category');
|
||||
const Guild = require('../model/guild');
|
||||
|
||||
module.exports = {
|
||||
aliases: ['av'],
|
||||
category: CommandCategory.FUN,
|
||||
isAllowedForContext: () => true,
|
||||
description: 'Displays the avatar of the specified member.',
|
||||
process: async (message, args) => {
|
||||
let user = null;
|
||||
|
||||
if (args.length > 0) {
|
||||
const result = Guild.findDesignatedMemberInMessage(message);
|
||||
|
||||
if (result.foundMembers.length > 0) {
|
||||
if (result.foundMembers[0].user !== undefined) {
|
||||
user = result.foundMembers[0].user;
|
||||
} else {
|
||||
user = result.foundMembers[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
user = message.author;
|
||||
}
|
||||
|
||||
if (user !== null) {
|
||||
const url = user.displayAvatarURL({ dynamic: true });
|
||||
|
||||
message.channel.send(new Discord.MessageAttachment(
|
||||
url + '?size=2048',
|
||||
user.id + url.substr(url.lastIndexOf('.'))
|
||||
)).catch(error => Logger.warning(error.toString()));
|
||||
} else {
|
||||
message.reply('I... Have no idea who that could be, sorry.');
|
||||
}
|
||||
}
|
||||
}
|
||||
25
command/avatar.ts
Normal file
25
command/avatar.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { APIGuildMember, APIUser, GatewayMessageCreateDispatchData } from "discord-api-types/v10";
|
||||
import { restClient } from "../bot";
|
||||
import CommandCategory from "../model/command-category";
|
||||
|
||||
export default {
|
||||
aliases: ['avatar', 'av'],
|
||||
category: CommandCategory.FUN,
|
||||
isAllowedForContext: (_: GatewayMessageCreateDispatchData) => true,
|
||||
description: 'Displays the avatar of the specified member.',
|
||||
process: async (message: GatewayMessageCreateDispatchData, args: string[]) => {
|
||||
let user: APIUser;
|
||||
|
||||
if (message.mentions.length == 0)
|
||||
user = message.author;
|
||||
|
||||
else if (message.mentions.length > 1)
|
||||
return restClient.createMessage(message.channel_id, `<@${message.author.id}>, there are too many mentions in this message! `
|
||||
+`please pick only one user whose avatar you want to show.`);
|
||||
|
||||
else user = message.mentions[0];
|
||||
|
||||
const url = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=2048`;
|
||||
await restClient.createMessage(message.channel_id, { embed: { image: { url } } });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
const Logger = require('@lilywonhalf/pretty-logger');
|
||||
const Config = require('../config.json');
|
||||
const CommandCategory = require('../model/command-category');
|
||||
const CommandPermission = require('../model/command-permission');
|
||||
const { search } = require('../model/jira');
|
||||
|
||||
const MAX_CHARACTERS = 1950;
|
||||
|
||||
module.exports = {
|
||||
aliases: ['change-log', 'cl'],
|
||||
category: CommandCategory.MODERATION,
|
||||
isAllowedForContext: CommandPermission.isMemberMod,
|
||||
description: 'Builds the changelog for a given version',
|
||||
process: async (message, args) => {
|
||||
|
||||
const errorHandler = async (error) => {
|
||||
if (error) {
|
||||
Logger.exception(error);
|
||||
}
|
||||
|
||||
await message.reactions.removeAll();
|
||||
await message.react('❌');
|
||||
}
|
||||
|
||||
await message.react('⏳').catch(() => {});
|
||||
const issues = args.length > 0 && args[0] ? await search(args[0]).catch(errorHandler) : await search().catch(errorHandler);
|
||||
|
||||
const taskType = Config.jira.issueTypes.task;
|
||||
const bugType = Config.jira.issueTypes.bug;
|
||||
const features = issues.filter(issue => parseInt(issue.fields.issuetype.id) === taskType);
|
||||
const bugs = issues.filter(issue => parseInt(issue.fields.issuetype.id) === bugType);
|
||||
|
||||
if (features.length < 1 && bugs.length < 1) {
|
||||
await message.channel.send('No issues found.');
|
||||
await message.reactions.removeAll().catch(() => {});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const output = `${features.map(
|
||||
issue => `* Feature: ${issue.key} - ${issue.fields.summary}`
|
||||
).join('\n')}\n\n${bugs.map(
|
||||
issue => `* Fixed: ${issue.key} - ${issue.fields.summary}`
|
||||
).join('\n')}`.trim();
|
||||
const messages = [];
|
||||
let currentMessage = '```';
|
||||
|
||||
for (let line of output.split('\n')) {
|
||||
if (currentMessage.length + line.length >= MAX_CHARACTERS) {
|
||||
messages.push(`${currentMessage}\`\`\``);
|
||||
currentMessage = '```';
|
||||
}
|
||||
|
||||
currentMessage = `${currentMessage}\n${line}`;
|
||||
}
|
||||
|
||||
messages.push(`${currentMessage}\`\`\``);
|
||||
|
||||
for (let messageToSend of messages) {
|
||||
await message.channel.send(messageToSend).catch(() => {});
|
||||
}
|
||||
|
||||
await message.reactions.removeAll().catch(() => {});
|
||||
await message.react('✔').catch(() => {});
|
||||
}
|
||||
}
|
||||
67
command/changelog.ts
Normal file
67
command/changelog.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { GatewayMessageCreateDispatchData } from 'discord-api-types/v10';
|
||||
|
||||
import CommandCategory from '../model/command-category';
|
||||
import * as CommandPermission from '../model/command-permission';
|
||||
|
||||
import config from '../config';
|
||||
import { restClient } from '../bot';
|
||||
|
||||
import search from '../model/jira';
|
||||
|
||||
const MAX_CHARACTERS = 1950;
|
||||
|
||||
export default {
|
||||
aliases: ['changelog', 'change-log', 'cl'],
|
||||
category: CommandCategory.MODERATION,
|
||||
isAllowedForContext: CommandPermission.isMemberMod,
|
||||
description: 'Builds the changelog for a given version',
|
||||
process: async (message: GatewayMessageCreateDispatchData, args: string[]) => {
|
||||
const errorHandler = async (error: any) => {
|
||||
await restClient.deleteReactions(message.channel_id, message.id);
|
||||
await restClient.createReaction(message.channel_id, message.id, '❌');
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
await restClient.createReaction(message.channel_id, message.id, '⏳').catch(() => {});
|
||||
const issues = args.length > 0 && args[0] ? await search(args[0]).catch(errorHandler) : await search().catch(errorHandler);
|
||||
|
||||
const taskType = config.jira.issueTypes.task;
|
||||
const bugType = config.jira.issueTypes.bug;
|
||||
const features = issues?.filter(issue => parseInt(issue.fields.issuetype.id) === taskType) ?? [];
|
||||
const bugs = issues?.filter(issue => parseInt(issue.fields.issuetype.id) === bugType) ?? [];
|
||||
|
||||
if (features.length < 1 && bugs.length < 1) {
|
||||
await restClient.createMessage(message.channel_id, 'No issues found.');
|
||||
await restClient.deleteReactions(message.channel_id, message.id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const output = `${features.map(
|
||||
issue => `* Feature: ${issue.key} - ${issue.fields.summary}`
|
||||
).join('\n')}\n\n${bugs.map(
|
||||
issue => `* Fixed: ${issue.key} - ${issue.fields.summary}`
|
||||
).join('\n')}`.trim();
|
||||
const messages: string[] = [];
|
||||
let currentMessage = '```';
|
||||
|
||||
for (let line of output.split('\n')) {
|
||||
if (currentMessage.length + line.length >= MAX_CHARACTERS) {
|
||||
messages.push(`${currentMessage}\`\`\``);
|
||||
currentMessage = '```';
|
||||
}
|
||||
|
||||
currentMessage = `${currentMessage}\n${line}`;
|
||||
}
|
||||
|
||||
messages.push(`${currentMessage}\`\`\``);
|
||||
|
||||
for (let messageToSend of messages) {
|
||||
await restClient.createMessage(message.channel_id, messageToSend);
|
||||
}
|
||||
|
||||
await restClient.deleteReactions(message.channel_id, message.id).catch(() => {});
|
||||
await restClient.createReaction(message.channel_id, message.id, '✔');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
const Config = require('../config.json');
|
||||
const CommandCategory = require('../model/command-category');
|
||||
const CommandPermission = require('../model/command-permission');
|
||||
|
||||
module.exports = {
|
||||
aliases: ['clear', 'purge'],
|
||||
category: CommandCategory.MODERATION,
|
||||
isAllowedForContext: CommandPermission.isMemberMod,
|
||||
description: 'Deletes messages in bulk.',
|
||||
process: async (message, args) => {
|
||||
if (args.length > 0 && parseInt(args[0]) > 0) {
|
||||
await message.channel.bulkDelete(Math.min(parseInt(args[0]) + 1, 100));
|
||||
} else {
|
||||
message.reply(`You have to tell me how many messages I should clean. \`${Config.prefix}clean 10\` for example.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
command/clean.ts
Normal file
25
command/clean.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { APIMessage, GatewayMessageCreateDispatchData } from 'discord-api-types/v10';
|
||||
|
||||
import CommandCategory from '../model/command-category';
|
||||
import * as CommandPermission from '../model/command-permission';
|
||||
|
||||
import config from '../config';
|
||||
import { restClient } from '../bot';
|
||||
|
||||
export default {
|
||||
aliases: ['clean', 'clear', 'purge'],
|
||||
category: CommandCategory.MODERATION,
|
||||
isAllowedForContext: CommandPermission.isMemberMod,
|
||||
description: 'Deletes messages in bulk.',
|
||||
process: async (message: GatewayMessageCreateDispatchData, args: string[]) => {
|
||||
if (args.length > 0 && parseInt(args[0]) > 0) {
|
||||
// sometimes abstractions are nice...
|
||||
// await message.channel.bulkDelete(Math.min(parseInt(args[0]) + 1, 100));
|
||||
|
||||
const messages = await restClient.fetchMessages(message.channel_id, { limit: Math.min(parseInt(args[0]) + 1, 100) });
|
||||
restClient.bulkDeleteMessages(message.channel_id, messages.map((x: APIMessage) => x.id));
|
||||
} else {
|
||||
restClient.createMessage(message.channel_id, `You have to tell me how many messages I should clean. \`${config.prefix}clean 10\` for example.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
182
command/embed.js
182
command/embed.js
|
|
@ -1,182 +0,0 @@
|
|||
const { MessageEmbed, Message } = require('discord.js');
|
||||
const CommandCategory = require('../model/command-category');
|
||||
const CommandPermission = require('../model/command-permission');
|
||||
|
||||
const RGB_REGEX = /(\d{1,3})\D*,\D*(\d{1,3})\D*,\D*(\d{1,3})\D*/u;
|
||||
|
||||
class EmbedDialog
|
||||
{
|
||||
/**
|
||||
* @param {Message} message
|
||||
*/
|
||||
constructor(message) {
|
||||
this.embed = new MessageEmbed();
|
||||
this.message = message;
|
||||
this.channel = message.channel;
|
||||
this.destinationChannel = null;
|
||||
|
||||
this.prompt = (question, hideSkip = false) => {
|
||||
return this.channel.send(`${question}\n*${!hideSkip ? '`skip` to skip, ' : ''}\`cancel\` to cancel*`);
|
||||
};
|
||||
|
||||
this.isMessageSkip = message => message.content.toLowerCase() === 'skip';
|
||||
|
||||
this.messageFilter = testedMessage => {
|
||||
const byAuthor = testedMessage.author.id === message.author.id;
|
||||
const hasContent = testedMessage.cleanContent.trim().length > 0;
|
||||
|
||||
return byAuthor && hasContent;
|
||||
}
|
||||
|
||||
this.channelMessageFilter = testedMessage => {
|
||||
return this.messageFilter(testedMessage)
|
||||
&& (this.isMessageSkip(testedMessage) || testedMessage.mentions.channels.size > 0);
|
||||
}
|
||||
|
||||
this.returnFirstOfCollection = collection => collection && collection.size ? collection.first() : null;
|
||||
this.awaitOptions = { max: 1, time: 5 * MINUTE, errors: ['time'] };
|
||||
|
||||
/**
|
||||
* @param {function} filter
|
||||
* @returns {Promise<Message>}
|
||||
*/
|
||||
this.awaitMessage = (filter = this.messageFilter) => {
|
||||
return this.channel.awaitMessages(
|
||||
filter,
|
||||
this.awaitOptions
|
||||
).then(async collection => {
|
||||
let message = this.returnFirstOfCollection(collection);
|
||||
|
||||
if (message && message.content.toLowerCase() === 'cancel') {
|
||||
message = null;
|
||||
await this.channel.send('Cancelling embed creation.');
|
||||
}
|
||||
|
||||
return message;
|
||||
}).catch(async () => {
|
||||
await this.channel.send('Time out, cancelling embed creation.');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async execute() {
|
||||
let confirmation = true;
|
||||
|
||||
await this.prompt('#️⃣ In which **channel** would you like this embed to be posted?');
|
||||
const channelMessage = await this.awaitMessage(this.channelMessageFilter);
|
||||
|
||||
if (!channelMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isMessageSkip(channelMessage)) {
|
||||
this.destinationChannel = this.channel;
|
||||
confirmation = false;
|
||||
} else {
|
||||
this.destinationChannel = channelMessage.mentions.channels.first();
|
||||
}
|
||||
|
||||
await this.prompt('📰 What do you want the **title** of this embed to be (you can also ping someone so it appears as they are saying what is going to be the description)?');
|
||||
const titleMessage = await this.awaitMessage(this.messageFilter);
|
||||
|
||||
if (!titleMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.isMessageSkip(titleMessage)) {
|
||||
if (titleMessage.mentions.members.size > 0) {
|
||||
const member = titleMessage.mentions.members.first();
|
||||
this.embed.setAuthor(member.displayName, member.user.displayAvatarURL({dynamic: true}));
|
||||
} else {
|
||||
this.embed.setTitle(titleMessage.content);
|
||||
}
|
||||
}
|
||||
|
||||
await this.prompt('🎨 What do you want the **color** of this embed to be?');
|
||||
const colourMessage = await this.awaitMessage(this.messageFilter);
|
||||
|
||||
if (!colourMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isMessageSkip(colourMessage)) {
|
||||
this.embed.setColor(APP_MAIN_COLOUR);
|
||||
} else {
|
||||
if (colourMessage.content.startsWith('#')) {
|
||||
this.embed.setColor(parseInt(colourMessage.content.substr(1), 16));
|
||||
} else if (colourMessage.content.startsWith('0x')) {
|
||||
this.embed.setColor(parseInt(colourMessage.content.substr(2), 16));
|
||||
} else if (RGB_REGEX.test(colourMessage.content)) {
|
||||
const [, red, green, blue] = colourMessage.content.match(RGB_REGEX);
|
||||
this.embed.setColor([parseInt(red), parseInt(green), parseInt(blue)]);
|
||||
} else {
|
||||
this.embed.setColor(colourMessage.content.toUpperCase().replace(/[^A-Z]+/gu, '_'));
|
||||
}
|
||||
}
|
||||
|
||||
await this.prompt('💬 What do you want the **description** (contents) of this embed to be?', true);
|
||||
const descriptionMessage = await this.awaitMessage(this.messageFilter);
|
||||
|
||||
if (!descriptionMessage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.embed.setDescription(descriptionMessage.content);
|
||||
await this.destinationChannel.send(this.embed);
|
||||
|
||||
if (confirmation) {
|
||||
return this.channel.send(`✅ The embed has been posted in ${this.destinationChannel}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Embed
|
||||
{
|
||||
static instance = null;
|
||||
|
||||
constructor() {
|
||||
if (Embed.instance !== null) {
|
||||
return Embed.instance;
|
||||
}
|
||||
|
||||
this.aliases = [];
|
||||
this.category = CommandCategory.MODERATION;
|
||||
this.isAllowedForContext = CommandPermission.isMemberModOrHelper;
|
||||
this.description = 'Allows to post an embed';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @param {Array} args
|
||||
*/
|
||||
async process(message, args) {
|
||||
if (args.length > 0) {
|
||||
let destinationChannel = message.channel;
|
||||
let deleteMessage = true;
|
||||
|
||||
if (/<#\d+>/u.test(args[0])) {
|
||||
destinationChannel = message.mentions.channels.first();
|
||||
args.shift();
|
||||
deleteMessage = false;
|
||||
}
|
||||
|
||||
const embed = new MessageEmbed();
|
||||
|
||||
embed.setColor(APP_MAIN_COLOUR);
|
||||
embed.setDescription(args.join(' '));
|
||||
await destinationChannel.send(embed);
|
||||
|
||||
if (deleteMessage) {
|
||||
await message.delete();
|
||||
} else {
|
||||
await message.react('✅');
|
||||
}
|
||||
} else {
|
||||
const dialog = new EmbedDialog(message);
|
||||
|
||||
return dialog.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Embed();
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
const Discord = require('discord.js');
|
||||
const Logger = require('@lilywonhalf/pretty-logger');
|
||||
const Config = require('../config.json');
|
||||
const CommandCategory = require('../model/command-category');
|
||||
const CommandPermission = require('../model/command-permission');
|
||||
|
||||
// Including every model here so that it's ready to be used by the command
|
||||
const Guild = require('../model/guild');
|
||||
|
||||
const JAVASCRIPT_LOGO_URL = 'https://i.discord.fr/IEV8.png';
|
||||
|
||||
module.exports = {
|
||||
aliases: [],
|
||||
category: CommandCategory.BOT_MANAGEMENT,
|
||||
isAllowedForContext: CommandPermission.isMommy,
|
||||
process: async (message) => {
|
||||
const code = message.content
|
||||
.substr(Config.prefix.length + 'eval'.length)
|
||||
.trim()
|
||||
.replace(/(`{3})js\n(.+)\n\1/iu, '$2')
|
||||
.trim();
|
||||
|
||||
await message.react('✔');
|
||||
Logger.notice('Eval: ' + code);
|
||||
let output = null;
|
||||
|
||||
try {
|
||||
output = eval(`${code}`); // Spoopy! 🎃 🦇 👻 ☠ 🕷
|
||||
} catch (exception) {
|
||||
output = `**${exception.name}: ${exception.message}**\n${exception.stack}`;
|
||||
}
|
||||
|
||||
const embed = new Discord.MessageEmbed()
|
||||
.setAuthor('Eval', JAVASCRIPT_LOGO_URL)
|
||||
.setColor(0x00FF00)
|
||||
.setDescription(output);
|
||||
|
||||
message.channel.send(embed).catch(error => Logger.warning(error.toString()));
|
||||
}
|
||||
};
|
||||
40
command/eval.ts
Normal file
40
command/eval.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import CommandCategory from "../model/command-category";
|
||||
import * as CommandPermission from '../model/command-permission';
|
||||
|
||||
import config from "../config";
|
||||
import { APIMessage } from "discord-api-types/v10";
|
||||
import { restClient } from "../bot";
|
||||
|
||||
const JAVASCRIPT_LOGO_URL = 'https://i.discord.fr/IEV8.png';
|
||||
|
||||
export default {
|
||||
aliases: ['eval'],
|
||||
category: CommandCategory.BOT_MANAGEMENT,
|
||||
isAllowedForContext: CommandPermission.isMommy,
|
||||
process: async (message: APIMessage) => {
|
||||
const code = message.content
|
||||
.substr(config.prefix.length + 'eval'.length)
|
||||
.trim()
|
||||
.replace(/(`{3})js\n(.+)\n\1/iu, '$2')
|
||||
.trim();
|
||||
|
||||
console.log('Eval: ' + code);
|
||||
let output: string | null = null;
|
||||
|
||||
try {
|
||||
output = eval(`${code}`); // Spoopy! 🎃 🦇 👻 ☠ 🕷
|
||||
} catch (exception) {
|
||||
// @ts-expect-error
|
||||
output = `**${exception.name}: ${exception.message}**\n${exception.stack}`;
|
||||
}
|
||||
|
||||
restClient.createMessage(message.channel_id, { embed: {
|
||||
author: {
|
||||
name: 'Eval',
|
||||
iconUrl: JAVASCRIPT_LOGO_URL,
|
||||
},
|
||||
color: 0x00FF00,
|
||||
description: output!,
|
||||
}}).catch(error => console.warn(error.toString()));
|
||||
}
|
||||
};
|
||||
132
command/help.js
132
command/help.js
|
|
@ -1,132 +0,0 @@
|
|||
const CommandPermission = require('../model/command-permission');
|
||||
const CommandCategory = require('../model/command-category');
|
||||
|
||||
const commands = require('../model/command').commandList;
|
||||
const messages = require('../model/messages');
|
||||
|
||||
const cachelessRequire = (path) => {
|
||||
if (!typeof path === 'string') {
|
||||
delete require.cache[require.resolve(path)];
|
||||
}
|
||||
|
||||
return typeof path === 'string' ? require(path) : null;
|
||||
};
|
||||
|
||||
const cleanString = (str) => str === null || str === undefined ? null : str
|
||||
.replace('_', ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
|
||||
const mainPage = (id) => {
|
||||
const buttons = Object.keys(CommandCategory).map(c => ({
|
||||
type: 2,
|
||||
style: 1,
|
||||
label: cleanString(c),
|
||||
custom_id: `help-${id}-${c}`,
|
||||
}))
|
||||
|
||||
return {
|
||||
embeds: [{
|
||||
title: 'Help command',
|
||||
color: 0xA95B44,
|
||||
description:
|
||||
'Spot stands for **S**imply **P**lural b**ot**. This bot is mainly used to generate changelogs and help ' +
|
||||
'with the definition of features, so recurring questions can be answered more quickly.\n' +
|
||||
'\n' +
|
||||
'Commands are classified by categories because displaying every command in this small box would be ' +
|
||||
'confusing and would break Discord\'s character limit. Click on a buttom below to show the commands ' +
|
||||
'available in the corresponding category, or type `.help <category name>`.\n'
|
||||
}],
|
||||
components: [{
|
||||
'type': 1,
|
||||
'components': buttons,
|
||||
}]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const categoryPage = (cat, id, direct = false) => {
|
||||
const c = Object.values(CommandCategory).map(x => cleanString(x).toLowerCase());
|
||||
if (!c.includes(cat.toLowerCase()))
|
||||
return;
|
||||
|
||||
let data = {
|
||||
embeds: [{
|
||||
title: `Help command | ${cleanString(cat)}`,
|
||||
color: 0xA95B44,
|
||||
}],
|
||||
components: [{
|
||||
'type': 1,
|
||||
'components': [{
|
||||
type: 2,
|
||||
style: 2,
|
||||
label: 'Back',
|
||||
custom_id: `help-${id}-home`,
|
||||
}],
|
||||
}]
|
||||
};
|
||||
|
||||
if (direct) delete data.components;
|
||||
|
||||
if (cat.toLowerCase() === cleanString(CommandCategory.RESOURCE).toLowerCase())
|
||||
data.embeds[0].fields = messages.getList();
|
||||
else
|
||||
data.embeds[0].fields = Array.from(commands.keys()).map(x => {
|
||||
const cmd = cachelessRequire(commands.get(x));
|
||||
if (cleanString(cmd.category)?.toLowerCase() !== cat.toLowerCase()) return;
|
||||
return { name: x, value: cmd.description ?? 'No description.'};
|
||||
}).filter(x => x);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
aliases: ["commands"],
|
||||
category: CommandCategory.INFO,
|
||||
isAllowedForContext: () => true,
|
||||
description: 'Provides the list of commands.',
|
||||
process: async (message, args) => {
|
||||
if (!args[0])
|
||||
await bot.api.channels(message.channel.id).messages.post({ data: mainPage(message.author.id) });
|
||||
else
|
||||
await bot.api.channels(message.channel.id).messages.post({
|
||||
data:
|
||||
categoryPage(args.join(' '), message.author.id, true)
|
||||
?? {
|
||||
content: 'Category not found.',
|
||||
message_reference: { message_id: message.id, guild_id: message.guild?.id },
|
||||
allowed_mentions: { parse: [] },
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.interactionHandler = async (event) => {
|
||||
const user = event.member ? event.member.user.id : event.user.id;
|
||||
const customId = event.data.custom_id;
|
||||
|
||||
let ret;
|
||||
|
||||
if (!customId.startsWith(`help-${user}`)) {
|
||||
ret = {
|
||||
type: 4,
|
||||
data: {
|
||||
flags: 64,
|
||||
content: 'This help command was sent by someone else. Please run `.help` again.',
|
||||
},
|
||||
}
|
||||
} else if (customId.endsWith('home')) {
|
||||
ret = { type: 7, data: mainPage(user) };
|
||||
} else {
|
||||
let page = categoryPage(cleanString(customId.split(`help-${user}-`).join('')), user);
|
||||
|
||||
if (page) {
|
||||
ret = { type: 7, data: page };
|
||||
} else {
|
||||
ret = { type: 4, data: { flags: 64, content: 'Category not found.' }}
|
||||
}
|
||||
}
|
||||
|
||||
return await bot.api.interactions(event.id, event.token).callback.post({ data: ret })
|
||||
}
|
||||
125
command/help.ts
Normal file
125
command/help.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { restClient } from "../bot";
|
||||
import CommandCategory from "../model/command-category";
|
||||
import * as CommandPermission from '../model/command-permission';
|
||||
import * as types from 'discord-api-types/v10';
|
||||
|
||||
import { Command, commands } from "../messageHandler";
|
||||
import * as messages from "../model/messages";
|
||||
import { GatewayMessageCreateDispatchData } from "discord-api-types/v10";
|
||||
|
||||
const cleanString = (str: string | null | undefined) => (str === null || str === undefined) ? undefined : str
|
||||
.replace('_', ' ')
|
||||
.split(' ')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(' ');
|
||||
|
||||
const mainPage = (id: string) => {
|
||||
const buttons = Object.values(CommandCategory).filter(x => typeof x === 'string').map(c => ({
|
||||
type: 2,
|
||||
style: 1,
|
||||
label: cleanString(c),
|
||||
customId: `help-${id}-${c}`,
|
||||
}))
|
||||
|
||||
return {
|
||||
embeds: [{
|
||||
title: 'Help command',
|
||||
color: 0xA95B44,
|
||||
description:
|
||||
'Spot stands for **S**imply **P**lural b**ot**. This bot is mainly used to generate changelogs and help ' +
|
||||
'with the definition of features, so recurring questions can be answered more quickly.\n' +
|
||||
'\n' +
|
||||
'Commands are classified by categories because displaying every command in this small box would be ' +
|
||||
'confusing and would break Discord\'s character limit. Click on a buttom below to show the commands ' +
|
||||
'available in the corresponding category, or type `.help <category name>`.\n'
|
||||
}],
|
||||
components: [{
|
||||
'type': 1,
|
||||
'components': buttons,
|
||||
}]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const categoryPage = (cat: string, id: string, direct = false) => {
|
||||
const c = Object.values(CommandCategory).filter(x => typeof x === 'string').map(x => cleanString(x)?.toLowerCase());
|
||||
if (!c.includes(cat.toLowerCase()))
|
||||
return;
|
||||
|
||||
let data = {
|
||||
embeds: [{
|
||||
title: `Help command | ${cleanString(cat)}`,
|
||||
color: 0xA95B44,
|
||||
}],
|
||||
components: [{
|
||||
'type': 1,
|
||||
'components': [{
|
||||
type: 2,
|
||||
style: 2,
|
||||
label: 'Back',
|
||||
customId: `help-${id}-home`,
|
||||
}],
|
||||
}],
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
if (direct) delete data.components;
|
||||
|
||||
if (cat == CommandCategory.RESOURCE)
|
||||
// @ts-expect-error
|
||||
data.embeds[0].fields = messages.getList();
|
||||
else
|
||||
// @ts-expect-error
|
||||
data.embeds[0].fields = commands.map((cmd: Command) => {
|
||||
if (cmd.category !== cat) return;
|
||||
return { name: cmd.aliases[0], value: cmd.description ?? 'No description.'};
|
||||
}).filter(x => x);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export default {
|
||||
aliases: ['help', "commands", 'h'],
|
||||
category: CommandCategory.INFO,
|
||||
isAllowedForContext: (_: GatewayMessageCreateDispatchData) => true,
|
||||
description: 'Provides the list of commands.',
|
||||
process: async (message: GatewayMessageCreateDispatchData, args: string[]) => {
|
||||
if (!args[0])
|
||||
await restClient.createMessage(message.channel_id, mainPage(message.author.id) );
|
||||
else
|
||||
await restClient.createMessage(message.channel_id, categoryPage(args.join(' '), message.author.id, true)
|
||||
?? {
|
||||
content: 'Category not found.',
|
||||
messageReference: { messageId: message.id, guildId: message.guild_id },
|
||||
allowedMentions: { parse: [] },
|
||||
})
|
||||
},
|
||||
interactionHandler: async (event: types.APIMessageComponentInteraction) => {
|
||||
const user = event.member ? event.member.user.id : event.user!.id;
|
||||
const customId = event.data.custom_id;
|
||||
|
||||
let ret;
|
||||
|
||||
if (!customId.startsWith(`help-${user}`)) {
|
||||
ret = {
|
||||
type: 4,
|
||||
data: {
|
||||
flags: 64,
|
||||
content: 'This help command was sent by someone else. Please run `.help` again.',
|
||||
},
|
||||
}
|
||||
} else if (customId.endsWith('home')) {
|
||||
ret = { type: 7, data: mainPage(user) };
|
||||
} else {
|
||||
let page = categoryPage(cleanString(customId.split(`help-${user}-`).join(''))!, user);
|
||||
|
||||
if (page) {
|
||||
ret = { type: 7, data: page };
|
||||
} else {
|
||||
ret = { type: 4, data: { flags: 64, content: 'Category not found.' }}
|
||||
}
|
||||
}
|
||||
|
||||
return await restClient.createInteractionResponse(event.id, event.token, ret);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,36 +1,38 @@
|
|||
const CommandCategory = require('../model/command-category');
|
||||
const CommandPermission = require('../model/command-permission');
|
||||
import CommandCategory from "../model/command-category";
|
||||
import * as CommandPermission from '../model/command-permission';
|
||||
|
||||
const axios = require('axios');
|
||||
import axios from "axios";
|
||||
import { APIMessage, GatewayMessageCreateDispatchData } from "discord-api-types/v10";
|
||||
import { restClient } from "../bot";
|
||||
|
||||
module.exports = {
|
||||
aliases: [],
|
||||
export default {
|
||||
aliases: ['ticket'],
|
||||
category: CommandCategory.MODERATION,
|
||||
isAllowedForContext: CommandPermission.isMemberModOrHelper,
|
||||
process: async (message) => {
|
||||
if (message.reference == null)
|
||||
return message.reply("missing reply");
|
||||
process: async (message: GatewayMessageCreateDispatchData, args: string[]) => {
|
||||
if (message.message_reference == null)
|
||||
return restClient.createMessage(message.channel_id, `<@${message.author.id}>, missing reply`);
|
||||
|
||||
let reply = await message.channel.messages.fetch(message.reference.messageID);
|
||||
let reply: APIMessage = await restClient.fetchMessage(message.channel_id, message.message_reference.message_id!);
|
||||
|
||||
let user = null;
|
||||
|
||||
if (reply.webhookID != null) {
|
||||
if (reply.webhook_id != null) {
|
||||
let pkmsg = await axios(`https://api.pluralkit.me/v2/messages/${reply.id}`);
|
||||
user = pkmsg.data.sender;
|
||||
} else {
|
||||
user = reply.author.id;
|
||||
}
|
||||
|
||||
message.delete();
|
||||
message.client.api.channels(message.channel.id).messages.post({ data: {
|
||||
restClient.deleteMessage(message.channel_id, message.id).catch(() => {});
|
||||
restClient.createMessage(message.channel_id, {
|
||||
content: `<@${user}>: Please send us a support ticket for your specific issue using this link: <https://apparyllis.atlassian.net/servicedesk/customer/portal/3>\n`
|
||||
+ `You can also click the button below this message.\n\n`
|
||||
+ `Select the support type that best fits your issue. Make sure to fill out any information that is requested.\n\n`
|
||||
+ `Once you fill out the ticket, you will receive an email with a confirmation.\nWhen the developer answers the ticket, you will get another email, `
|
||||
+ `so please watch your email (and spam folder) for a reply.`,
|
||||
message_reference: { message_id: reply.id },
|
||||
messageReference: { messageId: reply.id },
|
||||
components: [{ type: 1, components: [{ type: 2, style: 5, label: "Submit ticket", url: "https://apparyllis.atlassian.net/servicedesk/customer/portals" }]}]
|
||||
}});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
export default {
|
||||
"prefix": ".",
|
||||
"guild": "793906174356619286",
|
||||
"mom": "840806601957965864",
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
const Guild = require('../model/guild');
|
||||
|
||||
module.exports = async (member) => {
|
||||
if ((member.guild === null || isRightGuild(member.guild.id)) && !member.user.bot) {
|
||||
Guild.guildMemberAddHandler(member);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
const Command = require('../model/command');
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
*/
|
||||
module.exports = async (message) => {
|
||||
const user = message.author;
|
||||
|
||||
if ((message.guild === null || isRightGuild(message.guild.id)) && !user.bot) {
|
||||
await Command.parseMessage(message);
|
||||
}
|
||||
};
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
const Logger = require('@lilywonhalf/pretty-logger');
|
||||
const Config = require('../config.json');
|
||||
const Guild = require('../model/guild');
|
||||
|
||||
module.exports = async () => {
|
||||
Logger.info('Logged in as ' + bot.user.username + '#' + bot.user.discriminator);
|
||||
|
||||
Logger.info('--------');
|
||||
|
||||
Logger.info('Syncing guilds...');
|
||||
await Guild.init();
|
||||
Logger.info('Guilds synced. Serving in ' + Guild.discordGuild.name);
|
||||
|
||||
Logger.info('--------');
|
||||
|
||||
if (process.argv[3] === '--reboot') {
|
||||
bot.users.cache.get(Config.mom).send(`I'm back :) !`);
|
||||
}
|
||||
};
|
||||
46
messageHandler.ts
Normal file
46
messageHandler.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import * as types from 'discord-api-types/v10';
|
||||
import CommandCategory from './model/command-category';
|
||||
import { restClient } from './bot';
|
||||
import config from './config';
|
||||
|
||||
import avatar from './command/avatar';
|
||||
import changelog from './command/changelog';
|
||||
import clean from './command/clean';
|
||||
import eval from './command/eval';
|
||||
import help from './command/help';
|
||||
import ticket from './command/ticket';
|
||||
|
||||
import * as customMessages from './model/messages';
|
||||
|
||||
export interface Command {
|
||||
aliases: string[];
|
||||
category: CommandCategory;
|
||||
isAllowedForContext(_: types.GatewayMessageCreateDispatchData): boolean;
|
||||
description?: string;
|
||||
process(_: types.GatewayMessageCreateDispatchData, __: string[]): Promise<void>;
|
||||
}
|
||||
|
||||
const privCommands: Record<string, Command> = {};
|
||||
|
||||
export const commands = [avatar, changelog, clean, eval, help, ticket];
|
||||
|
||||
|
||||
commands.map((x: Command) => {
|
||||
x.aliases.map(a => privCommands[a] = x);
|
||||
});
|
||||
|
||||
export default async function handleMessage(message: types.GatewayMessageCreateDispatchData) {
|
||||
if (!message.content.toLowerCase().startsWith(config.prefix))
|
||||
return false;
|
||||
|
||||
let args = message.content.substr(config.prefix.length).trim().split(' ');
|
||||
const calledCommand = args.shift()!.toLowerCase();
|
||||
|
||||
const embed = customMessages.getEmbed(calledCommand);
|
||||
if (embed != null)
|
||||
return await restClient.createMessage(message.channel_id, { embed });
|
||||
|
||||
const foundCommand = privCommands[calledCommand];
|
||||
if (foundCommand == null || !foundCommand.isAllowedForContext(message)) return;
|
||||
await foundCommand.process(message, args);
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"categories":
|
||||
{
|
||||
"categories": {
|
||||
"guides": {
|
||||
"name": "Guides",
|
||||
"desc": "Guides on how to do things in the app",
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
const CommandCategory = {
|
||||
MODERATION: 'moderation',
|
||||
BOT_MANAGEMENT: 'bot_management',
|
||||
FUN: 'fun',
|
||||
RESOURCE: 'resource'
|
||||
};
|
||||
|
||||
module.exports = CommandCategory;
|
||||
9
model/command-category.ts
Normal file
9
model/command-category.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
enum CommandCategory {
|
||||
INFO = 'Info',
|
||||
MODERATION = 'Moderation',
|
||||
BOT_MANAGEMENT = 'Bot Managenent',
|
||||
FUN = 'Fun',
|
||||
RESOURCE = 'Resource'
|
||||
}
|
||||
|
||||
export default CommandCategory;
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
const Config = require('../config.json');
|
||||
const Guild = require('./guild');
|
||||
|
||||
const CommandPermission = {
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @returns {Promise.<boolean>}
|
||||
*/
|
||||
isMommy: async (message) => {
|
||||
const member = await Guild.getMemberFromMessage(message);
|
||||
|
||||
return member.id === Config.mom;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @returns {Promise.<boolean>}
|
||||
*/
|
||||
isMemberMod: async (message) => {
|
||||
const member = await Guild.getMemberFromMessage(message);
|
||||
|
||||
return member.id === Config.mom || await Guild.isMemberMod(member);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @returns {Promise.<boolean>}
|
||||
*/
|
||||
isMemberModOrHelper: async (message) => {
|
||||
const member = await Guild.getMemberFromMessage(message);
|
||||
|
||||
return await CommandPermission.isMemberMod(message) || await Guild.isMemberHelper(member);
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = CommandPermission;
|
||||
9
model/command-permission.ts
Normal file
9
model/command-permission.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { GatewayMessageCreateDispatchData } from 'discord-api-types/v10';
|
||||
import config from '../config';
|
||||
|
||||
export const isMommy = (message: GatewayMessageCreateDispatchData) => message.author.id == config.mom;
|
||||
export const isMemberMod = (message: GatewayMessageCreateDispatchData) => message.member!.roles.includes(config.roles.mod);
|
||||
export const isMemberModOrHelper = (message: GatewayMessageCreateDispatchData) =>
|
||||
message.member!.roles.includes(config.roles.mod)
|
||||
|| message.member!.roles.includes(config.roles.helper);
|
||||
|
||||
105
model/command.js
105
model/command.js
|
|
@ -1,105 +0,0 @@
|
|||
const fs = require('fs');
|
||||
const Discord = require('discord.js');
|
||||
const Config = require('../config.json');
|
||||
const Guild = require('./guild');
|
||||
|
||||
const customMessages = require("./messages");
|
||||
|
||||
const cachelessRequire = (path) => {
|
||||
if (typeof path === 'string') {
|
||||
delete require.cache[require.resolve(path)];
|
||||
}
|
||||
|
||||
return typeof path === 'string' ? require(path) : null;
|
||||
};
|
||||
|
||||
const Command = {
|
||||
commandList: new Discord.Collection(),
|
||||
commandAliases: {},
|
||||
|
||||
init: () => {
|
||||
Command.commandList = new Discord.Collection();
|
||||
Command.commandAliases = {};
|
||||
|
||||
fs.readdirSync('command/').forEach(file => {
|
||||
if (file.substr(file.lastIndexOf('.')).toLowerCase() === '.js') {
|
||||
const commandPath = `../command/${file}`;
|
||||
const commandInstance = cachelessRequire(commandPath);
|
||||
|
||||
if (commandInstance !== null) {
|
||||
const commandName = file.substr(0, file.lastIndexOf('.')).toLowerCase();
|
||||
|
||||
Command.commandList.set(commandName, commandPath);
|
||||
|
||||
if (commandInstance.aliases !== undefined && commandInstance.aliases !== null) {
|
||||
commandInstance.aliases.forEach(alias => {
|
||||
Command.commandAliases[alias.toLowerCase()] = commandName;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @returns {boolean}
|
||||
*/
|
||||
parseMessage: async (message) => {
|
||||
if (!message.content.toLowerCase().startsWith(Config.prefix))
|
||||
return false;
|
||||
|
||||
let content = message.content.substr(Config.prefix.length).trim().split(' ');
|
||||
const calledCommand = content.shift().toLowerCase();
|
||||
|
||||
if (embed = customMessages.getEmbed(calledCommand))
|
||||
return await message.channel.send({embed});
|
||||
|
||||
if (!await Command.isValid(calledCommand, message)) return;
|
||||
|
||||
const member = await Guild.getMemberFromMessage(message);
|
||||
if (member === null)
|
||||
return message.reply('sorry, you do not seem to be on the server.');
|
||||
|
||||
let commandName = calledCommand;
|
||||
|
||||
if (Command.commandAliases.hasOwnProperty(calledCommand))
|
||||
commandName = Command.commandAliases[calledCommand];
|
||||
|
||||
const commandInstance = cachelessRequire(Command.commandList.get(commandName));
|
||||
|
||||
if (commandInstance !== null)
|
||||
commandInstance.process(message, content, Command);
|
||||
else
|
||||
Command.commandList.delete(commandName);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} command
|
||||
* @param {Message} message
|
||||
* @return {Promise.<boolean>}
|
||||
*/
|
||||
isValid: async (command, message) => {
|
||||
let canonicalCommand = command.toLowerCase();
|
||||
let valid = Command.commandList.has(canonicalCommand);
|
||||
|
||||
if (!valid && Command.commandAliases.hasOwnProperty(canonicalCommand)) {
|
||||
canonicalCommand = Command.commandAliases[command];
|
||||
valid = Command.commandList.has(canonicalCommand);
|
||||
}
|
||||
|
||||
const commandInstance = cachelessRequire(Command.commandList.get(canonicalCommand));
|
||||
|
||||
if (commandInstance === null) {
|
||||
Command.commandList.delete(canonicalCommand);
|
||||
}
|
||||
|
||||
valid = valid
|
||||
&& commandInstance !== null
|
||||
&& await commandInstance.isAllowedForContext(message);
|
||||
|
||||
return valid;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Command;
|
||||
173
model/guild.js
173
model/guild.js
|
|
@ -1,173 +0,0 @@
|
|||
const Logger = require('@lilywonhalf/pretty-logger');
|
||||
const Config = require('../config.json');
|
||||
const Discord = require('discord.js');
|
||||
|
||||
const cachelessRequire = (path) => {
|
||||
if (typeof path === 'string') {
|
||||
delete require.cache[require.resolve(path)];
|
||||
}
|
||||
|
||||
return typeof path === 'string' ? require(path) : null;
|
||||
};
|
||||
|
||||
const Guild = {
|
||||
/** {Guild} */
|
||||
discordGuild: null,
|
||||
|
||||
/** {TextChannel} */
|
||||
joinsChannel: null,
|
||||
|
||||
/** {Collection} */
|
||||
messagesCache: new Discord.Collection(),
|
||||
|
||||
init: async () => {
|
||||
Guild.discordGuild = bot.guilds.cache.find(guild => guild.id === Config.guild);
|
||||
Guild.joinsChannel = Guild.discordGuild.channels.cache.find(channel => channel.id === Config.channels.joins);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param message
|
||||
* @returns {Promise.<GuildMember|null>}
|
||||
*/
|
||||
getMemberFromMessage: async (message) => {
|
||||
return await Guild.discordGuild.members.fetch(message.author).catch(exception => {
|
||||
Logger.error(exception.toString());
|
||||
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {GuildMember} member
|
||||
*/
|
||||
isMemberMod: (member) => {
|
||||
return member !== undefined && member !== null && member.roles.cache.has(Config.roles.mod);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {GuildMember} member
|
||||
*/
|
||||
isMemberHelper: (member) => {
|
||||
return member !== undefined && member !== null && member.roles.cache.has(Config.roles.helper);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} roleName
|
||||
* @returns {Role|null}
|
||||
*/
|
||||
getRoleByName: (roleName) => {
|
||||
return roleName === undefined || roleName === null ? null : Guild.discordGuild.roles.cache.find(
|
||||
role => role.name.toLowerCase() === roleName.toLowerCase()
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {GuildMember} member
|
||||
* @param {Snowflake} snowflake - The Role snowflake.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
memberHasRole: (member, snowflake) => {
|
||||
return member !== undefined && member !== null && member.roles.cache.some(role => role.id === snowflake);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @returns {Discord.MessageEmbed}
|
||||
*/
|
||||
messageToEmbed: async (message) => {
|
||||
const member = await Guild.getMemberFromMessage(message);
|
||||
const suffix = member !== null && member.nickname !== null && member.nickname !== undefined ? ` aka ${member.nickname}` : '';
|
||||
const embeds = message.embeds.filter(embed => embed.author.name && embed.author.iconURL);
|
||||
|
||||
let authorName = `${message.author.username}#${message.author.discriminator}${suffix}`;
|
||||
let authorImage = message.author.displayAvatarURL({ dynamic: true });
|
||||
let description = message.content;
|
||||
let timestamp = message.createdTimestamp;
|
||||
let image = null;
|
||||
|
||||
if (message.attachments.size > 0) {
|
||||
image = message.attachments.first().url;
|
||||
}
|
||||
|
||||
if (description.length < 1 && embeds.length > 0) {
|
||||
const embed = embeds[0];
|
||||
description = embed.description ? embed.description.trim() : '';
|
||||
|
||||
if (message.author.bot) {
|
||||
if (embed.author) {
|
||||
authorName = embed.author.name;
|
||||
authorImage = embed.author.iconURL;
|
||||
}
|
||||
|
||||
if (embed.timestamp) {
|
||||
timestamp = embed.timestamp;
|
||||
}
|
||||
|
||||
if (embed.image) {
|
||||
image = embed.image.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Discord.MessageEmbed()
|
||||
.setAuthor(authorName, authorImage)
|
||||
.setColor(0x00FF00)
|
||||
.setDescription(description)
|
||||
.setTimestamp(timestamp)
|
||||
.setImage(image);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Message} message
|
||||
* @returns {{certain: boolean, foundMembers: Array}}
|
||||
*/
|
||||
findDesignatedMemberInMessage: (message) => {
|
||||
let foundMembers = [];
|
||||
let certain = true;
|
||||
const memberList = bot.users.cache.concat(Guild.discordGuild.members.cache);
|
||||
|
||||
if (message.mentions.members !== null && message.mentions.members.size > 0) {
|
||||
foundMembers = message.mentions.members.array();
|
||||
} else if (message.content.match(/[0-9]{18}/u) !== null) {
|
||||
const ids = message.content.match(/[0-9]{18}/gu);
|
||||
|
||||
ids.map(id => {
|
||||
if (memberList.has(id)) {
|
||||
foundMembers.push(memberList.get(id));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
certain = false;
|
||||
memberList.forEach(member => {
|
||||
const user = member.user === undefined ? member : member.user;
|
||||
|
||||
const hasNickname = member.nickname !== undefined && member.nickname !== null;
|
||||
const nickname = hasNickname ? `${member.nickname.toLowerCase()}#${user.discriminator}` : '';
|
||||
const username = `${user.username.toLowerCase()}#${user.discriminator}`;
|
||||
const content = message.cleanContent.toLowerCase().split(' ').splice(1).join(' ');
|
||||
|
||||
if (content.length > 0) {
|
||||
const contentInNickname = hasNickname ? nickname.indexOf(content) > -1 : false;
|
||||
const contentInUsername = username.indexOf(content) > -1;
|
||||
const nicknameInContent = hasNickname ? content.indexOf(nickname) > -1 : false;
|
||||
const usernameInContent = content.indexOf(username) > -1;
|
||||
|
||||
if (contentInNickname || contentInUsername || nicknameInContent || usernameInContent) {
|
||||
foundMembers.push(member);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
certain,
|
||||
foundMembers
|
||||
};
|
||||
},
|
||||
|
||||
guildMemberAddHandler: (member) => {
|
||||
Guild.joinsChannel.send(`Welcome, ${member}! If you joined for any specific support questions please check out <#863171642905591830> first to see if your issue is known, and make sure that your app is up-to-date before posting.`);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Guild;
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
const axios = require('axios');
|
||||
const Config = require('../config.json');
|
||||
|
||||
const ENDPOINTS = {
|
||||
search: '/search'
|
||||
};
|
||||
|
||||
class Jira
|
||||
{
|
||||
async search(version) {
|
||||
const params = {
|
||||
'jql': 'project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20updated%20%3E%3D%20-24h%20ORDER%20BY%20created%20DESC',
|
||||
'maxResults': 100,
|
||||
'startAt': 0
|
||||
};
|
||||
|
||||
if (version) {
|
||||
params.jql = `project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20%22Fixed%20In%20Version%5BNumber%5D%22%20%3D%20%22${version}%22%20ORDER%20BY%20created%20DESC`;
|
||||
}
|
||||
|
||||
const issues = [];
|
||||
let response = null;
|
||||
|
||||
do {
|
||||
const httpParams = Object.keys(params).map(key => `${key}=${params[key]}`).join('&');
|
||||
const url = `${Config.jira.baseUrl}${ENDPOINTS.search}?${httpParams}`;
|
||||
response = await axios(url);
|
||||
|
||||
issues.push(...response.data.issues);
|
||||
params.startAt += 100;
|
||||
} while (response.data.issues.length > 99 && response.data.total > 100);
|
||||
|
||||
return issues;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Jira();
|
||||
30
model/jira.ts
Normal file
30
model/jira.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import * as axios from 'axios';
|
||||
import config from '../config';
|
||||
|
||||
export default async function search(version?: string): Promise<any[]> {
|
||||
const params = {
|
||||
'jql': 'project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20updated%20%3E%3D%20-24h%20ORDER%20BY%20created%20DESC',
|
||||
'maxResults': 100,
|
||||
'startAt': 0
|
||||
};
|
||||
|
||||
if (version) {
|
||||
params.jql = `project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20%22Fixed%20In%20Version%5BNumber%5D%22%20%3D%20%22${version}%22%20ORDER%20BY%20created%20DESC`;
|
||||
}
|
||||
|
||||
const issues: any[] = [];
|
||||
let response: any | null = null;
|
||||
|
||||
do {
|
||||
// @ts-expect-error
|
||||
const httpParams = Object.keys(params).map(key => `${key}=${params[key]}`).join('&');
|
||||
const url = `${config.jira.baseUrl}/search?${httpParams}`;
|
||||
// what even is this
|
||||
response = JSON.parse((await new axios.Axios({}).get(url)).data);
|
||||
|
||||
issues.push(...response!.issues);
|
||||
params.startAt += 100;
|
||||
} while (response!.issues.length > 99 && response!.total > 100);
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
const fs = require("fs")
|
||||
const path = require('path');
|
||||
|
||||
const messages = {};
|
||||
|
||||
const load = () =>
|
||||
{
|
||||
const messagesFile = fs.readFileSync(path.join(__dirname, '..', 'messages.json'))
|
||||
const messagesJson = JSON.parse(messagesFile);
|
||||
|
||||
const messagesList = messagesJson["faq"];
|
||||
|
||||
messagesList.forEach((msg) => {
|
||||
messages[msg["title"]] = msg
|
||||
})
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
// interface Message {
|
||||
// names string[];
|
||||
// description string;
|
||||
// title string;
|
||||
// text string;
|
||||
// friendlyName string;
|
||||
// category string;
|
||||
// }
|
||||
|
||||
module.exports = {
|
||||
get names() {
|
||||
return names;
|
||||
},
|
||||
get messages() {
|
||||
return messages;
|
||||
},
|
||||
get: (name) => messages[name],
|
||||
getList: () => Object.keys(messages).map(msg => ({ name: messages[msg].names[0], value: messages[msg].description })),
|
||||
getEmbed: (name) => {
|
||||
|
||||
let foundMsg = null
|
||||
Object.keys(messages).forEach((msgKey) =>
|
||||
{
|
||||
if (foundMsg) return;
|
||||
|
||||
const msg = messages[msgKey]
|
||||
|
||||
if (msg.names.includes(name))
|
||||
{
|
||||
foundMsg = msg;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!foundMsg) return;
|
||||
|
||||
return {
|
||||
color: "A95B44",
|
||||
author: {
|
||||
name: foundMsg.title,
|
||||
iconURL: bot.user.displayAvatarURL({ dynamic: true }),
|
||||
},
|
||||
description: foundMsg.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
58
model/messages.ts
Normal file
58
model/messages.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { restClient } from "../bot";
|
||||
|
||||
interface Message {
|
||||
names: string[];
|
||||
description: string;
|
||||
title: string;
|
||||
text: string;
|
||||
friendlyName: string;
|
||||
category: string;
|
||||
}
|
||||
|
||||
export const messages: Record<string, Message> = {};
|
||||
let avatarUrl = "";
|
||||
|
||||
const load = async () =>
|
||||
{
|
||||
const messagesList = require('../messages.json')["faq"];
|
||||
messagesList.forEach((msg: any) => {
|
||||
messages[msg["title"]] = msg
|
||||
})
|
||||
|
||||
const userInfo = await restClient.fetchMe();
|
||||
avatarUrl = `https://cdn.discordapp.com/avatars/${userInfo.id}/${userInfo.avatar}.png`;
|
||||
console.log(avatarUrl);
|
||||
}
|
||||
|
||||
load();
|
||||
|
||||
|
||||
export const get = (name: string) => messages[name];
|
||||
export const getList = () => Object.keys(messages).map(msg => ({ name: messages[msg].names[0], value: messages[msg].description }));
|
||||
|
||||
export const getEmbed = (name: string) => {
|
||||
let foundMsg: Message | undefined;
|
||||
Object.keys(messages).forEach((msgKey) =>
|
||||
{
|
||||
if (foundMsg) return;
|
||||
|
||||
const msg = messages[msgKey]
|
||||
|
||||
if (msg.names.includes(name))
|
||||
{
|
||||
foundMsg = msg;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (foundMsg == null) return;
|
||||
|
||||
return {
|
||||
color: 0xA95B44,
|
||||
author: {
|
||||
name: foundMsg.title,
|
||||
iconUrl: avatarUrl,
|
||||
},
|
||||
description: foundMsg.text,
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,12 @@
|
|||
"author": "Lily Wonhalf <lilywonhalf@gmail.com>",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@lilywonhalf/pretty-logger": "^1.1.3",
|
||||
"axios": "^1.1.3",
|
||||
"discord.js": "^14.6.0",
|
||||
"dotenv": "^16.0.3"
|
||||
"detritus-client-rest": "git+https://github.com/Tupperbox/DiscordRest",
|
||||
"detritus-client-socket": "git+https://github.com/Tupperbox/DiscordSocket",
|
||||
"discord-api-types": "^0.37.14",
|
||||
"dotenv": "^16.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
334
yarn.lock
334
yarn.lock
|
|
@ -2,138 +2,250 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@discordjs/collection@^0.1.6":
|
||||
"integrity" "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ=="
|
||||
"resolved" "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz"
|
||||
"version" "0.1.6"
|
||||
|
||||
"@discordjs/form-data@^3.0.1":
|
||||
"integrity" "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg=="
|
||||
"resolved" "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz"
|
||||
"version" "3.0.1"
|
||||
"@cspotcode/source-map-support@^0.8.0":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
|
||||
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
|
||||
dependencies:
|
||||
"asynckit" "^0.4.0"
|
||||
"combined-stream" "^1.0.8"
|
||||
"mime-types" "^2.1.12"
|
||||
"@jridgewell/trace-mapping" "0.3.9"
|
||||
|
||||
"@lilywonhalf/pretty-logger@^1.1.3":
|
||||
"integrity" "sha512-DcEGjCrQV2j6Hl29G4zD5M/84hzrt1xw7uPOZtZLWFzrCB5OWS/64RWl/y5/om0walTIYf7Nv00hCzfRS8gw4A=="
|
||||
"resolved" "https://registry.npmjs.org/@lilywonhalf/pretty-logger/-/pretty-logger-1.1.3.tgz"
|
||||
"version" "1.1.3"
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
||||
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
||||
|
||||
"abort-controller@^3.0.0":
|
||||
"integrity" "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="
|
||||
"resolved" "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz"
|
||||
"version" "3.0.0"
|
||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||
version "1.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
|
||||
"@jridgewell/trace-mapping@0.3.9":
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
|
||||
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
|
||||
dependencies:
|
||||
"event-target-shim" "^5.0.0"
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"asynckit@^0.4.0":
|
||||
"integrity" "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
"resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
||||
"version" "0.4.0"
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
|
||||
integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
|
||||
|
||||
"axios@^0.21.1":
|
||||
"integrity" "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg=="
|
||||
"resolved" "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz"
|
||||
"version" "0.21.4"
|
||||
"@tsconfig/node12@^1.0.7":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
|
||||
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
|
||||
|
||||
"@tsconfig/node14@^1.0.0":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
|
||||
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
|
||||
|
||||
"@tsconfig/node16@^1.0.2":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
|
||||
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
|
||||
|
||||
"@types/node@^14.17.1":
|
||||
version "14.18.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.32.tgz#8074f7106731f1a12ba993fe8bad86ee73905014"
|
||||
integrity sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==
|
||||
|
||||
acorn-walk@^8.1.1:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
|
||||
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
|
||||
|
||||
acorn@^8.4.1:
|
||||
version "8.8.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
|
||||
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
|
||||
|
||||
arg@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
|
||||
integrity "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
|
||||
axios@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35"
|
||||
integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==
|
||||
dependencies:
|
||||
"follow-redirects" "^1.14.0"
|
||||
follow-redirects "^1.15.0"
|
||||
form-data "^4.0.0"
|
||||
proxy-from-env "^1.1.0"
|
||||
|
||||
"combined-stream@^1.0.8":
|
||||
"integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="
|
||||
"resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
||||
"version" "1.0.8"
|
||||
combined-stream@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
|
||||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
|
||||
dependencies:
|
||||
"delayed-stream" "~1.0.0"
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
"delayed-stream@~1.0.0":
|
||||
"integrity" "sha1-3zrhmayt+31ECqrgsp4icrJOxhk= sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
"resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
"version" "1.0.0"
|
||||
create-require@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
||||
|
||||
"discord.js@^12.2.0":
|
||||
"integrity" "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw=="
|
||||
"resolved" "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz"
|
||||
"version" "12.5.3"
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
|
||||
integrity "sha1-3zrhmayt+31ECqrgsp4icrJOxhk= sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||
|
||||
"detritus-client-rest@git+https://github.com/Tupperbox/DiscordRest":
|
||||
version "0.10.5"
|
||||
resolved "git+https://github.com/Tupperbox/DiscordRest#15c7aa2d1782c55c30a60b53f754b6938e658346"
|
||||
dependencies:
|
||||
"@discordjs/collection" "^0.1.6"
|
||||
"@discordjs/form-data" "^3.0.1"
|
||||
"abort-controller" "^3.0.0"
|
||||
"node-fetch" "^2.6.1"
|
||||
"prism-media" "^1.2.9"
|
||||
"setimmediate" "^1.0.5"
|
||||
"tweetnacl" "^1.0.3"
|
||||
"ws" "^7.4.4"
|
||||
detritus-rest "^0.7.0"
|
||||
detritus-utils "^0.4.0"
|
||||
|
||||
"dotenv@^16.0.3":
|
||||
"integrity" "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
|
||||
"resolved" "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz"
|
||||
"version" "16.0.3"
|
||||
|
||||
"event-target-shim@^5.0.0":
|
||||
"integrity" "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
|
||||
"resolved" "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz"
|
||||
"version" "5.0.1"
|
||||
|
||||
"follow-redirects@^1.14.0":
|
||||
"integrity" "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
|
||||
"resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
|
||||
"version" "1.15.2"
|
||||
|
||||
"mime-db@1.50.0":
|
||||
"integrity" "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A=="
|
||||
"resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz"
|
||||
"version" "1.50.0"
|
||||
|
||||
"mime-types@^2.1.12":
|
||||
"integrity" "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g=="
|
||||
"resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz"
|
||||
"version" "2.1.33"
|
||||
"detritus-client-socket@git+https://github.com/Tupperbox/DiscordSocket":
|
||||
version "0.8.3"
|
||||
resolved "git+https://github.com/Tupperbox/DiscordSocket#44c6815e2e0cb0c6fdf42bc77ff91127413b5c58"
|
||||
dependencies:
|
||||
"mime-db" "1.50.0"
|
||||
"@types/node" "^14.17.1"
|
||||
detritus-utils "^0.4.0"
|
||||
ws "^7.4.6"
|
||||
|
||||
"node-fetch@^2.6.1":
|
||||
"integrity" "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="
|
||||
"resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
|
||||
"version" "2.6.7"
|
||||
detritus-rest@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/detritus-rest/-/detritus-rest-0.7.0.tgz#15d7a98c7038935de7ba2a88eeed83aba1c88ed7"
|
||||
integrity sha512-T5FNXpyv5F69ZXEz6z8mWo5yiz7Mhrg+HQsZumih/uDeqf3+1AvZgNI4XLscG1D56F0o5DSyXkoOxDgd2oeTgA==
|
||||
dependencies:
|
||||
"whatwg-url" "^5.0.0"
|
||||
form-data "^3.0.0"
|
||||
node-fetch "^2.6.1"
|
||||
|
||||
"prism-media@^1.2.9":
|
||||
"integrity" "sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g=="
|
||||
"resolved" "https://registry.npmjs.org/prism-media/-/prism-media-1.3.2.tgz"
|
||||
"version" "1.3.2"
|
||||
detritus-utils@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/detritus-utils/-/detritus-utils-0.4.0.tgz#42c01e029bd716c5be30a82ee4c621136f64ef35"
|
||||
integrity sha512-XO9crk1eCOecajzOg4WMsvcgXV/3GAltV0nt+kXXmglwcKyh/5DpQ7jmQ/2HycEN0dGBdgLCLPzYMxrWPN9gpw==
|
||||
|
||||
"setimmediate@^1.0.5":
|
||||
"integrity" "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
|
||||
"resolved" "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
|
||||
"version" "1.0.5"
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
"tr46@~0.0.3":
|
||||
"integrity" "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
"resolved" "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
|
||||
"version" "0.0.3"
|
||||
discord-api-types@^0.37.14:
|
||||
version "0.37.14"
|
||||
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.14.tgz#0fb10a5bb3ff9415afac12d7eaecc68b430d372a"
|
||||
integrity sha512-byBH7SfDCMJwxdqeS8k5sihltH88/YPhuwx+vF2cftSxFLdxyHyU/ZxDL3bq+LB2c4ls/TymE76/ISlLfniUXg==
|
||||
|
||||
"tweetnacl@^1.0.3":
|
||||
"integrity" "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
||||
"resolved" "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz"
|
||||
"version" "1.0.3"
|
||||
dotenv@^16.0.3:
|
||||
version "16.0.3"
|
||||
resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz"
|
||||
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
|
||||
|
||||
"webidl-conversions@^3.0.0":
|
||||
"integrity" "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
"resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz"
|
||||
"version" "3.0.1"
|
||||
follow-redirects@^1.15.0:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||
|
||||
"whatwg-url@^5.0.0":
|
||||
"integrity" "sha1-lmRU6HZUYuN2RNNib2dCzotwll0= sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="
|
||||
"resolved" "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz"
|
||||
"version" "5.0.0"
|
||||
form-data@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
|
||||
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
|
||||
dependencies:
|
||||
"tr46" "~0.0.3"
|
||||
"webidl-conversions" "^3.0.0"
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
"ws@^7.4.4":
|
||||
"integrity" "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w=="
|
||||
"resolved" "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz"
|
||||
"version" "7.5.5"
|
||||
form-data@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
make-error@^1.1.1:
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
||||
|
||||
mime-db@1.50.0:
|
||||
version "1.50.0"
|
||||
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz"
|
||||
integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==
|
||||
|
||||
mime-types@^2.1.12:
|
||||
version "2.1.33"
|
||||
resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz"
|
||||
integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==
|
||||
dependencies:
|
||||
mime-db "1.50.0"
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.7"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
|
||||
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
ts-node@^10.9.1:
|
||||
version "10.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
|
||||
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support" "^0.8.0"
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
"@tsconfig/node12" "^1.0.7"
|
||||
"@tsconfig/node14" "^1.0.0"
|
||||
"@tsconfig/node16" "^1.0.2"
|
||||
acorn "^8.4.1"
|
||||
acorn-walk "^8.1.1"
|
||||
arg "^4.1.0"
|
||||
create-require "^1.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
typescript@^4.8.4:
|
||||
version "4.8.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
|
||||
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
|
||||
whatwg-url@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
|
||||
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
||||
dependencies:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
ws@^7.4.6:
|
||||
version "7.5.9"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
|
||||
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue