diff --git a/README.md b/README.md index dd49730..ef2dc3b 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,22 @@ SUPER= https://www.prisma.io/docs/reference/database-reference/supported-databases -![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close and slash/move \ No newline at end of file +![](https://static.eartharoid.me/k/22/08/02185801.png) - for user/create, slash/force-close and slash/move + +menu question max length cannot be higher than question options + +- TODO: post stats +- TODO: settings bundle download +- TODO: update notifications +- TODO: check inline to-dos + + +creation requires an interaction: +- /new -> category? -> topic or questions -> create +- user:create(self) -> category? -> topic or questions -> create +- user:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create +- message:create(self) -> category? -> topic or questions -> create +- message:create(staff) -> category? -> DM (channel fallback) button -> topic or questions -> create +- DM -> guild? -> category? -> topic or questions -> create +- panel(interaction) -> topic or questions -> create +- panel(message) -> DM (channel fallback) button -> topic or questions -> create \ No newline at end of file diff --git a/package.json b/package.json index b0e911b..ad5ee6f 100644 --- a/package.json +++ b/package.json @@ -34,18 +34,18 @@ "node": ">=18.0" }, "dependencies": { - "@eartharoid/dbf": "^0.3.2", + "@eartharoid/dbf": "^0.3.3", "@eartharoid/dtf": "^2.0.1", "@eartharoid/i18n": "^1.0.4", "@fastify/cookie": "^6.0.0", - "@fastify/cors": "^8.0.0", + "@fastify/cors": "^8.1.0", "@fastify/jwt": "^5.0.1", - "@fastify/oauth2": "^5.0.0", - "@prisma/client": "^4.1.0", + "@fastify/oauth2": "^5.1.0", + "@prisma/client": "^4.1.1", "cryptr": "^6.0.3", - "discord.js": "^14.0.2", + "discord.js": "^14.1.2", "dotenv": "^16.0.1", - "fastify": "^4.2.1", + "fastify": "^4.3.0", "figlet": "^1.5.2", "fs-extra": "^10.1.0", "keyv": "^4.3.3", @@ -55,14 +55,14 @@ "node-dir": "^0.1.17", "node-emoji": "^1.11.0", "object-diffy": "^1.0.4", - "prisma": "^4.1.0", + "prisma": "^4.1.1", "semver": "^7.3.7", "terminal-link": "^2.1.1", "yaml": "^1.10.2" }, "devDependencies": { "all-contributors-cli": "^6.20.0", - "eslint": "^8.20.0", + "eslint": "^8.21.0", "eslint-plugin-unused-imports": "^2.0.0", "nodemon": "^2.0.19" } diff --git a/scripts/keygen.js b/scripts/keygen.js index bb91e25..4a6964a 100644 --- a/scripts/keygen.js +++ b/scripts/keygen.js @@ -2,7 +2,7 @@ const { randomBytes } = require('crypto'); const { short } = require('leeks.js'); console.log(short( - 'Set the "ENCRYPTION_KEY" environment variable to: \n&1&!f' + + 'Set the "ENCRYPTION_KEY" environment variable to: \n&!b' + randomBytes(24).toString('hex') + '&r\n\n&0&!e WARNING &r &e&lIf you lose the encryption key, most of the data in the database will become unreadable, requiring a new key and a full reset.', )); \ No newline at end of file diff --git a/src/buttons/create.js b/src/buttons/create.js index be7a97f..2e7d02c 100644 --- a/src/buttons/create.js +++ b/src/buttons/create.js @@ -8,5 +8,15 @@ module.exports = class CreateButton extends Button { }); } - async run(id, interaction) { } + /** + * @param {*} id + * @param {import("discord.js").ButtonInteraction} interaction + */ + async run(id, interaction) { + await this.client.tickets.create({ + categoryId: id.target, + interaction, + topic: id.topic, + }); + } }; \ No newline at end of file diff --git a/src/client.js b/src/client.js index bec0b96..f058fbb 100644 --- a/src/client.js +++ b/src/client.js @@ -1,11 +1,14 @@ const { FrameworkClient } = require('@eartharoid/dbf'); -const { GatewayIntentBits } = require('discord.js'); +const { + GatewayIntentBits, Partials, +} = require('discord.js'); const { PrismaClient } = require('@prisma/client'); const Keyv = require('keyv'); const I18n = require('@eartharoid/i18n'); const fs = require('fs'); const { join } = require('path'); const YAML = require('yaml'); +const TicketManager = require('./lib/tickets/manager'); const encryptionMiddleware = require('./lib/middleware/prisma-encryption'); const sqliteMiddleware = require('./lib/middleware/prisma-sqlite'); @@ -13,10 +16,19 @@ module.exports = class Client extends FrameworkClient { constructor(config, log) { super({ intents: [ + GatewayIntentBits.DirectMessages, + GatewayIntentBits.DirectMessageReactions, + GatewayIntentBits.DirectMessageTyping, + GatewayIntentBits.MessageContent, GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers, GatewayIntentBits.GuildMessages, ], + partials: [ + Partials.Message, + Partials.Channel, + Partials.Reaction, + ], }); const locales = {}; @@ -30,6 +42,8 @@ module.exports = class Client extends FrameworkClient { /** @type {I18n} */ this.i18n = new I18n('en-GB', locales); + /** @type {TicketManager} */ + this.tickets = new TicketManager(this); this.config = config; this.log = log; this.supers = (process.env.SUPER ?? '').split(','); diff --git a/src/i18n/en-GB.yml b/src/i18n/en-GB.yml index a25acfd..5eb9901 100644 --- a/src/i18n/en-GB.yml +++ b/src/i18n/en-GB.yml @@ -2,6 +2,9 @@ test: | line 1 line 2 buttons: + confirm_open: + emoji: ✅ + text: Create ticket create: emoji: 🎫 text: Create a ticket @@ -120,6 +123,9 @@ commands: user: create: name: Create a ticket for user +dm: + confirm_open: + title: 'Do you want to open a ticket with the following topic?' log: admin: changes: Changes @@ -145,5 +151,15 @@ log: update: updated tickets: menus: - create: - placeholder: Select a ticket category \ No newline at end of file + category: + placeholder: Select a ticket category + guild: + placeholder: Select a server +modals: + feedback: + title: 'Feedback' + topic: 'Topic' +misc: + no_categories: + description: No ticket categories have been configured. + title: ❌ There are no ticket categories \ No newline at end of file diff --git a/src/lib/strings.js b/src/lib/strings.js deleted file mode 100644 index 12c79f1..0000000 --- a/src/lib/strings.js +++ /dev/null @@ -1 +0,0 @@ -module.exports.capitalise = string => string.charAt(0).toUpperCase() + string.slice(1); \ No newline at end of file diff --git a/src/lib/tickets/manager.js b/src/lib/tickets/manager.js new file mode 100644 index 0000000..6554bfe --- /dev/null +++ b/src/lib/tickets/manager.js @@ -0,0 +1,146 @@ +const { + ActionRowBuilder, + ModalBuilder, + SelectMenuBuilder, + SelectMenuOptionBuilder, + TextInputBuilder, + TextInputStyle, +} = require('discord.js'); +const emoji = require('node-emoji'); + +module.exports = class TicketManager { + constructor(client) { + /** @type {import("client")} */ + this.client = client; + } + + /** + * @param {object} data + * @param {string} data.category + * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} data.interaction + * @param {string?} [data.topic] + */ + async create({ + categoryId, interaction, topic, reference, + }) { + const category = await this.client.prisma.category.findUnique({ + include: { + guild: true, + questions: true, + }, + where: { id: Number(categoryId) }, + }); + + // TODO: if member !required roles -> stop + + // TODO: if discordCategory has 50 channels -> stop + + // TODO: if category has max channels -> stop + + // TODO: if member has max -> stop + + // TODO: if cooldown -> stop + + const getMessage = this.client.i18n.getLocale(category.guild.locale); + + if (category.questions.length >= 1) { + await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'questions', + categoryId, + reference, + })) + .setTitle(category.name) + .setComponents( + category.questions + .sort((a, b) => a.order - b.order) + .map(q => { + if (q.type === 'TEXT') { + return new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId(q.id) + .setLabel(q.label) + .setStyle(q.style) + .setMaxLength(q.maxLength) + .setMinLength(q.minLength) + .setPlaceholder(q.placeholder) + .setRequired(q.required) + .setValue(q.value), + ); + } else if (q.type === 'MENU') { + return new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(q.id) + .setPlaceholder(q.placeholder || q.label) + .setMaxValues(q.maxLength) + .setMinValues(q.minLength) + .setOptions( + q.options.map((o, i) => { + const builder = new SelectMenuOptionBuilder() + .setValue(String(i)) + .setLabel(o.label); + if (o.description) builder.setDescription(o.description); + if (o.emoji) builder.setEmoji(emoji.hasEmoji(o.emoji) ? emoji.get(o.emoji) : { id: o.emoji }); + return builder; + }), + ), + ); + } + }), + ), + ); + } else if (category.requireTopic && !topic) { + await interaction.showModal( + new ModalBuilder() + .setCustomId(JSON.stringify({ + action: 'topic', + categoryId, + reference, + })) + .setTitle(category.name) + .setComponents( + new ActionRowBuilder() + .setComponents( + new TextInputBuilder() + .setCustomId('topic') + .setLabel(getMessage('modals.topic')) + .setStyle(TextInputStyle.Long), + ), + ), + ); + } else { + await this.postQuestions({ + categoryId, + interaction, + topic, + }); + } + } + + /** + * @param {object} data + * @param {string} data.category + * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction|import("discord.js").ModalSubmitInteraction} data.interaction + * @param {string?} [data.topic] + */ + async postQuestions({ + categoryId, interaction, topic, reference, + }) { + await interaction.deferReply({ ephemeral: true }); + console.log(require('util').inspect(interaction, { + colors: true, + depth: 10, + })); + if (interaction.isModalSubmit()) { + + } + + interaction.editReply({ + components: [], + embeds: [], + }); + } +}; \ No newline at end of file diff --git a/src/lib/users.js b/src/lib/users.js new file mode 100644 index 0000000..f116db3 --- /dev/null +++ b/src/lib/users.js @@ -0,0 +1,29 @@ +/** + * + * @param {import("client")} client + * @param {string} userId + * @returns {Promise} + */ +module.exports.getCommonGuilds = async (client, userId) => await client.guilds.cache.filter(async guild => { + const member = await guild.members.fetch(userId); + return !!member; +}); + +/** + * + * @param {import("discord.js").Guild} guild + * @param {string} userId + * @returns {Promise} + */ +module.exports.isStaff = async (guild, userId) => { + /** @type {import("client")} */ + const client = guild.client; + if (guild.client.supers.includes(userId)) return true; + const guildMember = await guild.members.fetch(userId); + if (guildMember?.permissions.has('MANAGE_GUILD')) return true; + const { categories } = await client.prisma.guild.findUnique({ + select: { categories: true }, + where: { id: guild.id }, + }); + return categories.some(cat => cat.roles.some(r => guildMember.roles.cache.has(r))); +}; \ No newline at end of file diff --git a/src/listeners/client/error.js b/src/listeners/client/error.js new file mode 100644 index 0000000..d101a43 --- /dev/null +++ b/src/listeners/client/error.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'error', + }); + } + + run(error) { + this.client.log.error(error); + } +}; diff --git a/src/listeners/client/guildCreate.js b/src/listeners/client/guildCreate.js new file mode 100644 index 0000000..5cfbb4c --- /dev/null +++ b/src/listeners/client/guildCreate.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'guildCreate', + }); + } + + run(guild) { + this.client.log.success(`Added to guild "${guild.name}"`); + } +}; diff --git a/src/listeners/client/guildDelete.js b/src/listeners/client/guildDelete.js new file mode 100644 index 0000000..01eb59c --- /dev/null +++ b/src/listeners/client/guildDelete.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'guildDelete', + }); + } + + run(guild) { + this.client.log.info(`Removed from guild "${guild.name}"`); + } +}; diff --git a/src/listeners/client/guildMemberRemove.js b/src/listeners/client/guildMemberRemove.js new file mode 100644 index 0000000..8e40061 --- /dev/null +++ b/src/listeners/client/guildMemberRemove.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'guildMemberRemove', + }); + } + + run(member) { + // TODO: close tickets + } +}; diff --git a/src/listeners/client/messageCreate.js b/src/listeners/client/messageCreate.js new file mode 100644 index 0000000..64bcd37 --- /dev/null +++ b/src/listeners/client/messageCreate.js @@ -0,0 +1,184 @@ +const { Listener } = require('@eartharoid/dbf'); +const { + ActionRowBuilder, + ButtonBuilder, + ButtonStyle: { Success }, + ChannelType, + ComponentType, + EmbedBuilder, + SelectMenuBuilder, + SelectMenuOptionBuilder, +} = require('discord.js'); +const { getCommonGuilds } = require('../../lib/users'); +const ms = require('ms'); +const emoji = require('node-emoji'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'messageCreate', + }); + } + + /** + * @param {string} guildId + * @param {import("discord.js").ButtonInteraction|import("discord.js").SelectMenuInteraction} interaction + */ + async useGuild(settings, interaction, topic) { + const getMessage = this.client.i18n.getLocale(settings.locale); + if (settings.categories.length === 0) { + interaction.editReply({ + components: [], + embeds: [ + new EmbedBuilder() + .setColor(settings.errorColour) + .setTitle(getMessage('misc.no_categories.title')) + .setDescription(getMessage('misc.no_categories.description')), + ], + }); + } else if (settings.categories.length === 1) { + await this.client.tickets.create({ + categoryId: settings.categories[0].id, + interaction, + topic, + }); + } else { + const sent = await interaction.editReply({ + components: [ + new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(JSON.stringify({ + action: 'create', + topic, + })) + .setPlaceholder(getMessage('menus.category.placeholder')) + .setOptions( + settings.categories.map(category => + new SelectMenuOptionBuilder() + .setValue(String(category.id)) + .setLabel(category.name) + .setDescription(category.description) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ), + ), + ], + }); + sent.awaitMessageComponent({ + componentType: ComponentType.SelectMenu, + filter: () => true, + time: ms('30s'), + }) + .then(async () => { + await sent.delete(); + }) + .catch(error => { + if (error) this.client.log.error(error); + sent.delete(); + }); + } + + } + + /** + * @param {import("discord.js").Message} message + */ + async run(message) { + /** @type {import("client")} */ + const client = this.client; + + if (message.channel.type === ChannelType.DM) { + if (message.author.bot) return false; + const commonGuilds = await getCommonGuilds(this.client, message.author.id); + if (commonGuilds.size === 0) { + return false; + } else if (commonGuilds.size === 1) { + const settings = await client.prisma.guild.findUnique({ + select: { + categories: true, + errorColour: true, + locale: true, + primaryColour: true, + }, + where: { id: commonGuilds.at(0).id }, + }); + const getMessage = this.client.i18n.getLocale(settings.locale); + const sent = await message.reply({ + components: [ + new ActionRowBuilder() + .setComponents( + new ButtonBuilder() + .setCustomId(message.id) + .setStyle(Success) + .setLabel(getMessage('buttons.confirm_open.text')) + .setEmoji(getMessage('buttons.confirm_open.emoji')), + ), + ], + embeds: [ + new EmbedBuilder() + .setColor(settings.primaryColour) + .setTitle(getMessage('dm.confirm_open.title')) + .setDescription(message.content), + ], + }); + sent.awaitMessageComponent({ + componentType: ComponentType.Button, + filter: interaction => interaction.deferUpdate(), + time: ms('30s'), + }) + .then(async interaction => await this.useGuild(settings, interaction, message.content)) + .catch(error => { + if (error) this.client.log.error(error); + sent.delete(); + }); + } else { + const getMessage = this.client.i18n.getLocale(); + const sent = await message.reply({ + components: [ + new ActionRowBuilder() + .setComponents( + new SelectMenuBuilder() + .setCustomId(message.id) + .setPlaceholder(getMessage('menus.guild.placeholder')) + .setOptions( + commonGuilds.map(g => + new SelectMenuOptionBuilder() + .setValue(String(g.id)) + .setLabel(g.name), + ), + ), + ), + + ], + }); + sent.awaitMessageComponent({ + componentType: ComponentType.SelectMenu, + filter: interaction => interaction.deferUpdate(), + time: ms('30s'), + }) + .then(async interaction => { + const settings = await client.prisma.guild.findUnique({ + select: { + categories: true, + errorColour: true, + locale: true, + primaryColour: true, + }, + where: { id: interaction.values[0] }, + }); + await this.useGuild(settings, interaction, message.content); + }) + .catch(error => { + if (error) this.client.log.error(error); + sent.delete(); + }); + } + } else { + // TODO: archive messages in tickets + // TODO: auto tag + } + } +}; diff --git a/src/listeners/client/messageDelete.js b/src/listeners/client/messageDelete.js new file mode 100644 index 0000000..11504a0 --- /dev/null +++ b/src/listeners/client/messageDelete.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'messageDelete', + }); + } + + run(message) { + // TODO: archive messages in tickets + } +}; diff --git a/src/listeners/client/messageUpdate.js b/src/listeners/client/messageUpdate.js new file mode 100644 index 0000000..59a2a63 --- /dev/null +++ b/src/listeners/client/messageUpdate.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'messageUpdate', + }); + } + + run(oldMessage, newMessage) { + // TODO: archive messages in tickets + } +}; diff --git a/src/listeners/client/warn.js b/src/listeners/client/warn.js new file mode 100644 index 0000000..b37bf6c --- /dev/null +++ b/src/listeners/client/warn.js @@ -0,0 +1,15 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client, + event: 'warn', + }); + } + + run(warn) { + this.client.log.warn(warn); + } +}; diff --git a/src/listeners/commands/error.js b/src/listeners/commands/error.js new file mode 100644 index 0000000..6383585 --- /dev/null +++ b/src/listeners/commands/error.js @@ -0,0 +1,19 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.commands, + event: 'error', + }); + } + + run({ + command, + error, + }) { + this.client.log.error.commands(`"${command.name}" command execution error:`, error); + return true; + } +}; diff --git a/src/listeners/commands/run.js b/src/listeners/commands/run.js new file mode 100644 index 0000000..94649a1 --- /dev/null +++ b/src/listeners/commands/run.js @@ -0,0 +1,24 @@ +const { Listener } = require('@eartharoid/dbf'); + +module.exports = class extends Listener { + constructor(client, options) { + super(client, { + ...options, + emitter: client.commands, + event: 'run', + }); + } + + run({ + command, + interaction, + }) { + const types = { + 1: 'slash', + 2: 'user', + 3: 'message', + }; + this.client.log.info.commands(`${interaction.user.tag} used the "${command.name}" ${types[command.type]} command`); + return true; + } +}; diff --git a/src/menus/create.js b/src/menus/create.js index a0a0dfa..887068e 100644 --- a/src/menus/create.js +++ b/src/menus/create.js @@ -8,5 +8,15 @@ module.exports = class CreateMenu extends Menu { }); } - async run(id, interaction) { } + /** + * @param {*} id + * @param {import("discord.js").SelectMenuInteraction} interaction + */ + async run(id, interaction) { + await this.client.tickets.create({ + categoryId: interaction.values[0], + interaction, + topic: id.topic, + }); + } }; \ No newline at end of file diff --git a/src/modals/questions.js b/src/modals/questions.js index 5554551..f3063f5 100644 --- a/src/modals/questions.js +++ b/src/modals/questions.js @@ -8,5 +8,13 @@ module.exports = class QuestionsModal extends Modal { }); } - async run(id, interaction) { } + async run(id, interaction) { + console.log(id); + console.log(require('util').inspect(interaction, { + colors: true, + depth: 10, + })); + + // TODO: custom topic + } }; \ No newline at end of file diff --git a/src/modals/topic.js b/src/modals/topic.js new file mode 100644 index 0000000..3e98ba1 --- /dev/null +++ b/src/modals/topic.js @@ -0,0 +1,18 @@ +const { Modal } = require('@eartharoid/dbf'); + +module.exports = class TopicModal extends Modal { + constructor(client, options) { + super(client, { + ...options, + id: 'topic', + }); + } + + async run(id, interaction) { + console.log(id); + console.log(require('util').inspect(interaction, { + colors: true, + depth: 10, + })); + } +}; \ No newline at end of file diff --git a/src/routes/api/admin/guilds/[guild]/panels.js b/src/routes/api/admin/guilds/[guild]/panels.js index 7b39f1a..c1d928a 100644 --- a/src/routes/api/admin/guilds/[guild]/panels.js +++ b/src/routes/api/admin/guilds/[guild]/panels.js @@ -84,36 +84,35 @@ module.exports.post = fastify => ({ .setLabel(getMessage('buttons.create.text')) .setEmoji(getMessage('buttons.create.emoji')), ); + } else if (data.type === 'BUTTON') { + components.push( + ...categories.map(category => + new ButtonBuilder() + .setCustomId(JSON.stringify({ + action: 'create', + target: category.id, + })) + .setStyle(Secondary) + .setLabel(category.name) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), + ), + ); } else { - if (data.type === 'BUTTON') { - components.push( - ...categories.map(category => - new ButtonBuilder() - .setCustomId(JSON.stringify({ - action: 'create', - target: category.id, - })) - .setStyle(Secondary) - .setLabel(category.name) - .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), - ), - ); - } else { - components.push( - new SelectMenuBuilder() - .setCustomId('create') - .setPlaceholder(getMessage('menus.create.placeholder')) - .setOptions( - categories.map(category => - new SelectMenuOptionBuilder() - .setValue(String(category.id)) - .setLabel(category.name) - .setDescription(category.description) - .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), - ), + components.push( + new SelectMenuBuilder() + .setCustomId(JSON.stringify({ action: 'create' })) + .setPlaceholder(getMessage('menus.category.placeholder')) + .setOptions( + categories.map(category => + new SelectMenuOptionBuilder() + .setValue(String(category.id)) + .setLabel(category.name) + .setDescription(category.description) + .setEmoji(emoji.hasEmoji(category.emoji) ? emoji.get(category.emoji) : { id: category.emoji }), ), - ); - } + ), + ); + } await channel.send({ diff --git a/src/schemas/settings.js b/src/schemas/settings.js new file mode 100644 index 0000000..e4af137 --- /dev/null +++ b/src/schemas/settings.js @@ -0,0 +1,24 @@ +module.exports = joi.object({ + archive: joi.boolean().optional(), + autoClose: joi.number().min(3600000).optional(), + autoTag: [joi.array(), joi.string().valid('ticket', '!ticket', 'all')].optional(), + blocklist: joi.array().optional(), + createdAt: joi.string().optional(), + errorColour: joi.string().optional(), + footer: joi.string().optional(), + id: joi.string().optional(), + logChannel: joi.string().optional(), + primaryColour: joi.string().optional(), + staleAfter: joi.number().min(60000).optional(), + successColour: joi.string().optional(), + workingHours: joi.array().length(8).items( + joi.string(), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + joi.array().items(joi.string().required(), joi.string().required()), + ).optional(), +}); \ No newline at end of file diff --git a/user/example.config.yml b/user/example.config.yml index 3519d93..9a5da22 100644 --- a/user/example.config.yml +++ b/user/example.config.yml @@ -11,7 +11,7 @@ ## |_| |_| \___| |_|\_\ \___| \__| |___/ ## ## ## ## Documentation: https://discordtickets.app ## -## Support: https://go.eartharoid.me/discord ## +## Support: https://lnk.earth/discord ## ##################################################### logs: