mirror of
https://github.com/DarthKilroy/Spot.git
synced 2026-02-04 02:26: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,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" }]}]
|
||||
}});
|
||||
});
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue