From 7e4df84b150fad01d46a9f52d5b4cdfd82b71c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tina=C3=ABl=20DEVRESSE?= Date: Sat, 16 Oct 2021 19:49:52 +0200 Subject: [PATCH 1/3] feat: Support DJS13 --- README.md | 41 +- example/index.js | 111 ++- package-lock.json | 1030 ++++++++++++++++++++++++++-- package.json | 14 +- src/MailboxClient.ts | 36 + src/MailboxManager.ts | 244 +++++++ src/MailboxManagerEvents.ts | 19 + src/handlers/index.ts | 2 +- src/handlers/message.ts | 118 ---- src/handlers/messageCreate.ts | 145 ++++ src/handlers/messageReactionAdd.ts | 80 ++- src/handlers/ticketClose.ts | 49 +- src/handlers/ticketLog.ts | 37 +- src/index.ts | 226 +----- src/types/LogsOptions.ts | 69 +- src/types/MailboxManagerOptions.ts | 179 ++--- src/types/Ticket.ts | 330 +++++++-- src/utils/MessageUtils.ts | 23 + src/utils/StringUtils.ts | 10 +- src/utils/TicketUtils.ts | 121 ---- 20 files changed, 2044 insertions(+), 840 deletions(-) create mode 100644 src/MailboxClient.ts create mode 100644 src/MailboxManager.ts create mode 100644 src/MailboxManagerEvents.ts delete mode 100644 src/handlers/message.ts create mode 100644 src/handlers/messageCreate.ts create mode 100644 src/utils/MessageUtils.ts delete mode 100644 src/utils/TicketUtils.ts diff --git a/README.md b/README.md index 8b843d0..8ae9e1c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ npm version # Discord Mailbox + Discord Mailbox is a framework to easily add a mailbox inside your bot. The feature is also fully customizable. -- Supports multiple tickets per users -- Logs everything like you want it -- Emits events like `ticketCreate`, `ticketUpdate` and **6 more** -- Allow full customization of the embed (you can add image, thumbnail, etc) -- And much more! +- Supports multiple tickets per users +- Logs everything like you want it +- Emits events like `ticketCreate`, `ticketUpdate` and **6 more** +- Allow full customization of the embed (you can add image, thumbnail, etc) +- And much more! ## Installation @@ -16,30 +17,39 @@ npm install --save @hunteroi/discord-mailbox ``` ## Examples + See [./example/index.js](example/index.js). ![IMAGE](assets/example.gif) ## Events + ```ts -manager.on('ticketCreate', (ticket: Ticket) => {}); +manager.on(MailboxManagerEvents.ticketCreate, (ticket: Ticket) => {}); -manager.on('ticketUpdate', (ticket: Ticket) => {}); +manager.on(MailboxManagerEvents.ticketUpdate, (ticket: Ticket) => {}); -manager.on('ticketLog', (ticket: Ticket) => {}); +manager.on(MailboxManagerEvents.ticketLog, (ticket: Ticket) => {}); -manager.on('ticketClose', (ticket: Ticket) => {}); +manager.on(MailboxManagerEvents.ticketClose, (ticket: Ticket) => {}); -manager.on('ticketForceClose', (ticket: Ticket, user: User | PartialUser) => {}); +manager.on( + MailboxManagerEvents.ticketForceClose, + (ticket: Ticket, user: User | PartialUser) => {} +); -manager.on('ticketDelete', (ticket: Ticket) => {}); +manager.on(MailboxManagerEvents.ticketDelete, (ticket: Ticket) => {}); -manager.on('replySent', (message: Message, answer: Message) => {}); +manager.on( + MailboxManagerEvents.replySent, + (message: Message, answer: Message) => {} +); -manager.on('replyDelete', (message: Message) => {}); +manager.on(MailboxManagerEvents.replyDelete, (message: Message) => {}); ``` ## Contribution + Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated. 1. Fork the Project @@ -47,3 +57,8 @@ Contributions are what make the open source community such an amazing place to b 3. Commit your Changes: `git commit -m 'Add some amazing work'` 4. Push to the Branch: `git push origin patch/YourAmazingWork` 5. Open a Pull Request + +## Todo + +- auto reply when a ticket is opened +- warn when a ticket is closed/force closed diff --git a/example/index.js b/example/index.js index 1721cad..4f36dc8 100644 --- a/example/index.js +++ b/example/index.js @@ -1,47 +1,82 @@ -const { Client } = require('discord.js'); -const { MailboxManager } = require('../lib'); +const { Client, Intents } = require('discord.js'); -const client = new Client(); +const { MailboxManager, MailboxManagerEvents } = require('../lib'); + +const client = new Client({ + intents: [ + Intents.FLAGS.GUILDS, + Intents.FLAGS.GUILD_MESSAGES, + Intents.FLAGS.GUILD_MESSAGE_REACTIONS, + Intents.FLAGS.GUILD_MEMBERS, + Intents.FLAGS.DIRECT_MESSAGES, + ], +}); const manager = new MailboxManager(client, { - forceCloseEmoji: 'āŒ', - replySentEmoji: 'šŸ“¤', - loggingOptions: { - generateFilename: ticket => `log-ticket-${ticket.id}.txt`, - format: msg => `[${new Date(msg.createdTimestamp)}] ${msg.author.username} | ${msg.cleanContent}`, - generateMessage: ticket => `Logs for ticket ${ticket.id} - closed at ${new Date(ticket.closedAt)}`, - sendToRecipient: false, - channel: 'LOG_CHANNEL_ID', - showName: false - }, - mailboxChannel: 'MAIL_CHANNEL_ID', - deleteReplies: true, - cronTime: '* * * * *', // run each minute - closeTicketAfter: 60, // in seconds - maxOngoingTicketsPerUser: 3, - notAllowedToPing: 'You are not allowed to mention @everyone or @here in a mail!', - replyMessage: 'Please use the "reply" feature to send an answer to this message.', - tooMuchTickets: 'You have too much tickets that are not closed! Please wait for your tickets to be closed before submitting new ones.', - ticketClose: nbTickets => `This ticket has been closed due to inactivity or manually by the receiver. You now have ${nbTickets} tickets left opened.`, - replyMessageInFooter: true, - embedOptions: { - send: true, - color: 12272523 - }, - formatTitle: id => `[Ticket] ${id}` + forceCloseEmoji: 'āŒ', + replySentEmoji: 'šŸ“¤', + loggingOptions: { + generateFilename: (ticket) => `log-ticket-${ticket.id}.txt`, + format: (msg) => + `[${new Date(msg.createdTimestamp)}] ${msg.author.username} | ${ + msg.cleanContent + }`, + generateMessage: (ticket) => + `Logs for ticket ${ticket.id} - closed at ${new Date( + ticket.closedAt + )}`, + sendToRecipient: false, + channel: 'TEXT_CHANNEL_ID', + showName: false, + }, + mailboxChannel: 'TEXT_CHANNEL_ID', + deleteReplies: true, + cronTime: '* * * * *', // run each minute + closeTicketAfter: 60, // in seconds + maxOngoingTicketsPerUser: 3, + notAllowedToPing: + 'You are not allowed to mention @everyone or @here in a mail!', + replyMessage: + 'Please use the "reply" feature to send an answer to this message.', + tooMuchTickets: + 'You have too much tickets that are not closed! Please wait for your tickets to be closed before submitting new ones.', + ticketClose: (nbTickets) => + `This ticket has been closed due to inactivity or manually by the receiver. You now have ${nbTickets} tickets left opened.`, + replyMessageInFooter: true, + embedOptions: { + send: true, + color: 12272523, + }, + formatTitle: (id) => `[Ticket] ${id}`, }); client.on('ready', () => console.log('Connected!')); -client.on('message', message => { - if (message.content === 'show me the tickets collection') { - message.reply(`\`\`\`js\n${JSON.stringify(manager.userTickets, null, 2)}\n\`\`\``); - } -}) +client.on('messageCreate', (message) => { + console.log(message); + if (message.content === 'show me the tickets collection') { + message.reply( + `\`\`\`js\n${JSON.stringify(manager.userTickets, null, 2)}\n\`\`\`` + ); + } +}); -manager.on('ticketCreate', ticket => console.log(`${ticket.id} has been created!`)); -manager.on('ticketUpdate', ticket => console.log(`${ticket.id} has been updated with a new message.`)); -manager.on('ticketLog', (ticket) => console.log(`${ticket.id} got logged.`)); -manager.on('ticketClose', ticket => console.log(`${ticket.id} got closed!`)); -manager.on('ticketDelete', ticket => console.log(`${ticket.id} got deleted.`)); +manager.on(MailboxManagerEvents.ticketCreate, (ticket) => + console.log(`${ticket.id} has been created!`) +); +manager.on(MailboxManagerEvents.ticketUpdate, (ticket) => + console.log(`${ticket.id} has been updated with a new message.`) +); +manager.on(MailboxManagerEvents.ticketLog, (ticket) => + console.log(`${ticket.id} got logged.`) +); +manager.on(MailboxManagerEvents.ticketClose, (ticket) => + console.log(`${ticket.id} got closed!`) +); +manager.on(MailboxManagerEvents.ticketForceClose, (ticket, user) => + console.log(`${user.username} forced closed ticket ${ticket.id}.`) +); +manager.on(MailboxManagerEvents.ticketDelete, (ticket) => + console.log(`${ticket.id} got deleted.`) +); client.login('TOKEN'); diff --git a/package-lock.json b/package-lock.json index 4de51df..7201a94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,799 @@ { "name": "@hunteroi/discord-mailbox", - "version": "1.2.2", - "lockfileVersion": 1, + "version": "2.0.0", + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "@hunteroi/discord-mailbox", + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "cron": "^1.8.2", + "discord.js": "^13.2.0", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/cron": "^1.7.3", + "@types/moment": "^2.13.0", + "@types/node": "^16.11.0", + "@types/uuid": "^8.3.1", + "@types/ws": "^8.2.0", + "tslint": "^6.1.0", + "tslint-config-prettier": "^1.18.0", + "typescript": "^4.4.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.12.13" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==", + "dev": true + }, + "node_modules/@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@discordjs/builders": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.6.0.tgz", + "integrity": "sha512-mH3Gx61LKk2CD05laCI9K5wp+a3NyASHDUGx83DGJFkqJlRlSV5WMJNY6RS37A5SjqDtGMF4wVR9jzFaqShe6Q==", + "dependencies": { + "@sindresorhus/is": "^4.0.1", + "discord-api-types": "^0.22.0", + "ow": "^0.27.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@discordjs/builders/node_modules/discord-api-types": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz", + "integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@discordjs/builders/node_modules/tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + }, + "node_modules/@discordjs/collection": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz", + "integrity": "sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@discordjs/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.5.tgz", + "integrity": "sha512-NQ8GeTBeOkeAylVYTnO9zfEZO74iMNGCRrR3uIRnCrhkyPC+nsewyQtTamjSDWxXFTf+xGSJ9khiY2p56k/bMA==", + "engines": { + "node": ">=v14.18.0", + "npm": ">=7.24.1" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@types/cron": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.3.tgz", + "integrity": "sha512-iPmUXyIJG1Js+ldPYhOQcYU3kCAQ2FWrSkm1FJPoii2eYSn6wEW6onPukNTT0bfiflexNSRPl6KWmAIqS+36YA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "moment": ">=2.14.0" + } + }, + "node_modules/@types/moment": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz", + "integrity": "sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=", + "deprecated": "This is a stub types definition for Moment (https://github.com/moment/moment). Moment provides its own type definitions, so you don't need @types/moment installed!", + "dev": true, + "dependencies": { + "moment": "*" + } + }, + "node_modules/@types/node": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.0.tgz", + "integrity": "sha512-8MLkBIYQMuhRBQzGN9875bYsOhPnf/0rgXGo66S2FemHkhbn9qtsz9ywV1iCG+vbjigE4WUNVvw37Dx+L0qsPg==" + }, + "node_modules/@types/uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz", + "integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/combined-stream": { + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "dependencies": { + "moment-timezone": "^0.5.x" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/discord-api-types": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.23.1.tgz", + "integrity": "sha512-igWmn+45mzXRWNEPU25I/pr8MwxHb767wAr51oy3VRLRcTlp5ADBbrBR0lq3SA1Rfw3MtM4TQu1xo3kxscfVdQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/discord.js": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.2.0.tgz", + "integrity": "sha512-nyxUvL8wuQG38zx13wUMkpcA8koFszyiXdkSLwwM9opKW2LC2H5gD0cTZxImeJ6GtEnKPWT8xBiE8lLBmbNIhw==", + "dependencies": { + "@discordjs/builders": "^0.6.0", + "@discordjs/collection": "^0.2.1", + "@discordjs/form-data": "^3.0.1", + "@sapphire/async-queue": "^1.1.5", + "@types/ws": "^8.2.0", + "discord-api-types": "^0.23.1", + "node-fetch": "^2.6.1", + "ws": "^8.2.3" + }, + "engines": { + "node": ">=16.6.0", + "npm": ">=7.0.0" + } + }, + "node_modules/discord.js/node_modules/@types/ws": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz", + "integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, + "node_modules/mime-db": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "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" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.33", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.33.tgz", + "integrity": "sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-fetch": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ow": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz", + "integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==", + "dependencies": { + "@sindresorhus/is": "^4.0.1", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "type-fest": "^1.2.1", + "vali-date": "^1.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "dependencies": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/ts-mixer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.0.tgz", + "integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==" + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint-config-prettier": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.18.0.tgz", + "integrity": "sha512-xPw9PgNPLG3iKRxmK7DWr+Ea/SzrvfHtjFt5LBl61gk2UBG/DB9kCXRjv+xyIU1rUtnayLeMUVJBcMX8Z17nDg==", + "dev": true, + "bin": { + "tslint-config-prettier-check": "bin/check.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.12.13", @@ -30,10 +821,34 @@ "js-tokens": "^4.0.0" } }, + "@discordjs/builders": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.6.0.tgz", + "integrity": "sha512-mH3Gx61LKk2CD05laCI9K5wp+a3NyASHDUGx83DGJFkqJlRlSV5WMJNY6RS37A5SjqDtGMF4wVR9jzFaqShe6Q==", + "requires": { + "@sindresorhus/is": "^4.0.1", + "discord-api-types": "^0.22.0", + "ow": "^0.27.0", + "ts-mixer": "^6.0.0", + "tslib": "^2.3.1" + }, + "dependencies": { + "discord-api-types": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz", + "integrity": "sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg==" + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, "@discordjs/collection": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz", - "integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz", + "integrity": "sha512-vhxqzzM8gkomw0TYRF3tgx7SwElzUlXT/Aa41O7mOcyN6wIJfj5JmDWaO5XGKsGSsNx7F3i5oIlrucCCWV1Nog==" }, "@discordjs/form-data": { "version": "3.0.1", @@ -45,10 +860,20 @@ "mime-types": "^2.1.12" } }, + "@sapphire/async-queue": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.5.tgz", + "integrity": "sha512-NQ8GeTBeOkeAylVYTnO9zfEZO74iMNGCRrR3uIRnCrhkyPC+nsewyQtTamjSDWxXFTf+xGSJ9khiY2p56k/bMA==" + }, + "@sindresorhus/is": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", + "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" + }, "@types/cron": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.2.tgz", - "integrity": "sha512-AEpNLRcsVSc5AdseJKNHpz0d4e8+ow+abTaC0fKDbAU86rF1evoFF0oC2fV9FdqtfVXkG2LKshpLTJCFOpyvTg==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.3.tgz", + "integrity": "sha512-iPmUXyIJG1Js+ldPYhOQcYU3kCAQ2FWrSkm1FJPoii2eYSn6wEW6onPukNTT0bfiflexNSRPl6KWmAIqS+36YA==", "dev": true, "requires": { "@types/node": "*", @@ -65,33 +890,25 @@ } }, "@types/node": { - "version": "14.14.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz", - "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==" + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.0.tgz", + "integrity": "sha512-8MLkBIYQMuhRBQzGN9875bYsOhPnf/0rgXGo66S2FemHkhbn9qtsz9ywV1iCG+vbjigE4WUNVvw37Dx+L0qsPg==" }, "@types/uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==", "dev": true }, "@types/ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-ISCK1iFnR+jYv7+jLNX0wDqesZ/5RAeY3wUx6QaphmocphU61h+b+PHjS18TF4WIPTu/MMzxIq2PHr32o2TS5Q==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz", + "integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==", "dev": true, "requires": { "@types/node": "*" } }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "requires": { - "event-target-shim": "^5.0.0" - } - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -137,6 +954,11 @@ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -202,19 +1024,42 @@ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true }, + "discord-api-types": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.23.1.tgz", + "integrity": "sha512-igWmn+45mzXRWNEPU25I/pr8MwxHb767wAr51oy3VRLRcTlp5ADBbrBR0lq3SA1Rfw3MtM4TQu1xo3kxscfVdQ==" + }, "discord.js": { - "version": "12.5.3", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz", - "integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.2.0.tgz", + "integrity": "sha512-nyxUvL8wuQG38zx13wUMkpcA8koFszyiXdkSLwwM9opKW2LC2H5gD0cTZxImeJ6GtEnKPWT8xBiE8lLBmbNIhw==", "requires": { - "@discordjs/collection": "^0.1.6", + "@discordjs/builders": "^0.6.0", + "@discordjs/collection": "^0.2.1", "@discordjs/form-data": "^3.0.1", - "abort-controller": "^3.0.0", + "@sapphire/async-queue": "^1.1.5", + "@types/ws": "^8.2.0", + "discord-api-types": "^0.23.1", "node-fetch": "^2.6.1", - "prism-media": "^1.2.9", - "setimmediate": "^1.0.5", - "tweetnacl": "^1.0.3", - "ws": "^7.4.4" + "ws": "^8.2.3" + }, + "dependencies": { + "@types/ws": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.2.0.tgz", + "integrity": "sha512-cyeefcUCgJlEk+hk2h3N+MqKKsPViQgF5boi9TTHSK+PoR9KWBb/C5ccPcDyAqgsbAYHTwulch725DV84+pSpg==", + "requires": { + "@types/node": "*" + } + } + } + }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "requires": { + "is-obj": "^2.0.0" } }, "escape-string-regexp": { @@ -229,11 +1074,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -300,6 +1140,11 @@ "has": "^1.0.3" } }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -316,17 +1161,22 @@ "esprima": "^4.0.0" } }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz", + "integrity": "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" }, "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "version": "2.1.33", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz", + "integrity": "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==", "requires": { - "mime-db": "1.47.0" + "mime-db": "1.50.0" } }, "minimatch": { @@ -367,9 +1217,12 @@ } }, "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", + "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", + "requires": { + "whatwg-url": "^5.0.0" + } }, "once": { "version": "1.4.0", @@ -380,6 +1233,19 @@ "wrappy": "1" } }, + "ow": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.27.0.tgz", + "integrity": "sha512-SGnrGUbhn4VaUGdU0EJLMwZWSupPmF46hnTRII7aCLCrqixTAC5eKo8kI4/XXf1eaaI8YEVT+3FeGNJI9himAQ==", + "requires": { + "@sindresorhus/is": "^4.0.1", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "type-fest": "^1.2.1", + "vali-date": "^1.0.0" + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -387,16 +1253,11 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "prism-media": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz", - "integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==" - }, "resolve": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", @@ -413,11 +1274,6 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -433,6 +1289,16 @@ "has-flag": "^3.0.0" } }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "ts-mixer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.0.tgz", + "integrity": "sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==" + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -475,15 +1341,15 @@ "tslib": "^1.8.1" } }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==" }, "typescript": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.4.tgz", - "integrity": "sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true }, "uuid": { @@ -491,6 +1357,25 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, + "vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -498,9 +1383,10 @@ "dev": true }, "ws": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz", - "integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==" + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} } } } diff --git a/package.json b/package.json index 056c3f7..a1f45fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hunteroi/discord-mailbox", - "version": "1.2.2", + "version": "2.0.0", "description": "A framework to integrate a mailbox inside your Discord bot built with DiscordJS", "main": "lib/index.js", "scripts": { @@ -28,18 +28,18 @@ "homepage": "https://github.com/hunteroi/discord-mailbox#readme", "dependencies": { "cron": "^1.8.2", - "discord.js": "^12.5.1", + "discord.js": "^13.2.0", "uuid": "^8.3.2" }, "devDependencies": { - "@types/cron": "^1.7.2", + "@types/cron": "^1.7.3", "@types/moment": "^2.13.0", - "@types/uuid": "^8.3.0", - "@types/node": "^14.14.16", - "@types/ws": "^7.4.0", + "@types/uuid": "^8.3.1", + "@types/node": "^16.11.0", + "@types/ws": "^8.2.0", "tslint": "^6.1.0", "tslint-config-prettier": "^1.18.0", - "typescript": "^4.1.3" + "typescript": "^4.4.4" }, "directories": { "lib": "lib" diff --git a/src/MailboxClient.ts b/src/MailboxClient.ts new file mode 100644 index 0000000..6229a52 --- /dev/null +++ b/src/MailboxClient.ts @@ -0,0 +1,36 @@ +import { Client, ClientOptions } from 'discord.js'; + +import { MailboxManagerOptions } from './types'; +import { MailboxManager } from './MailboxManager'; + +/** + * A wrapper class for {@link Client} that carries a {@link MailboxManager} instance. + * + * @export + * @class MailboxClient + * @extends {Client} + */ +export class MailboxClient extends Client { + /** + * The mailbox manager. + * + * @type {MailboxManager} + * @memberof MailboxClient + */ + public readonly mailboxManager: MailboxManager; + + /** + *Creates an instance of MailboxClient. + * @param {ClientOptions} [options] + * @param {MailboxManagerOptions} [mailboxOptions] + * @memberof MailboxClient + */ + constructor( + mailboxOptions: MailboxManagerOptions, + options?: ClientOptions + ) { + super(options); + + this.mailboxManager = new MailboxManager(this, mailboxOptions); + } +} diff --git a/src/MailboxManager.ts b/src/MailboxManager.ts new file mode 100644 index 0000000..d6bbf4b --- /dev/null +++ b/src/MailboxManager.ts @@ -0,0 +1,244 @@ +import { Client, Collection, Intents, Snowflake } from 'discord.js'; +import { EventEmitter } from 'events'; +import { CronJob } from 'cron'; + +import { + handleMessage, + handleReaction, + handleClosing, + handleLog, +} from './handlers'; +import { MailboxManagerOptions, Ticket } from './types'; +import { MailboxManagerEvents } from '.'; + +/** + * The manager of the mailbox feature + * + * @export + * @class MailboxManager + * @extends {EventEmitter} + */ +export class MailboxManager extends EventEmitter { + /** + * The configuration of the mailbox manager. + * + * @private + * @type {MailboxManagerOptions} + */ + public readonly options: MailboxManagerOptions; + + /** + * The collection of tickets per user. + * + * @type {Collection>} + * @memberof MailboxManager + */ + public readonly userTickets: Collection< + Snowflake, + Collection + >; + + /** + * The client that instantiated this Manager + * @name MailboxManager#client + * @type {Client} + * @readonly + */ + public readonly client: Client; + + /** + * The cron job to check if tickets need to be closed because outdated. + * + * @private + * @type {CronJob} + */ + private job?: CronJob; + + /** + * Whether the logging options is set with a format method or not + * + * @type {boolean} + */ + public canFormatLogs: boolean; + + /** + * Creates an instance of MailboxManager. + * @param {Client} client + * @param {MailboxManagerOptions} [options] + */ + constructor( + client: Client, + options: MailboxManagerOptions = { + tooMuchTickets: 'You have too much opened tickets!', + notAllowedToPing: + 'You are not allowed to mention @everyone and @here.', + replyMessage: 'Use the "reply" feature to respond.', + ticketClose: (nbUserTicketsLeft) => + `This ticket has been closed. You now have ${nbUserTicketsLeft} tickets that are still opened.`, + maxOngoingTicketsPerUser: 3, + closeTicketAfter: 60, + formatTitle: (id) => `Ticket ${id}`, + cronTime: '* * * * *', + mailboxChannel: null, + } + ) { + super(); + + const intents = new Intents(client.options.intents); + if (!intents.has(Intents.FLAGS.GUILDS)) { + throw new Error('GUILDS intent is required to use this package!'); + } + if (!intents.has(Intents.FLAGS.GUILD_MESSAGES)) { + throw new Error( + 'GUILD_MESSAGES intent is required to use this package!' + ); + } + if ( + options.forceCloseEmoji && + !intents.has(Intents.FLAGS.GUILD_MESSAGE_REACTIONS) + ) { + throw new Error( + 'GUILD_MESSAGE_REACTIONS intent is required to use this package!' + ); + } + if (!intents.has(Intents.FLAGS.DIRECT_MESSAGES)) { + throw new Error( + 'DIRECT_MESSAGES intent is required to use this package!' + ); + } + + if (!options.mailboxChannel) { + throw new Error( + 'Please define the mailbox channel in the options!' + ); + } + + this.client = client; + this.options = options; + this.userTickets = new Collection(); + this.canFormatLogs = + this.options.loggingOptions && !!this.options.loggingOptions.format; + + this.client.on('messageCreate', async (message) => { + handleMessage(this, message); + }); + if (this.options.forceCloseEmoji) { + this.client.on( + 'messageReactionAdd', + async (messageReaction, user) => { + await handleReaction(this, messageReaction, user); + } + ); + } + + this.on(MailboxManagerEvents.ticketClose, async (ticket: Ticket) => + handleClosing(this, ticket) + ); + this.on(MailboxManagerEvents.ticketLog, async (ticket: Ticket) => + handleLog(this, ticket) + ); + + this.job = new CronJob( + this.options.cronTime, + () => this.checkTickets(), + null, + null, + null, + this + ); + this.job.start(); + } + + checkTickets() { + this.userTickets.each((userTickets) => { + userTickets.each((ticket) => { + if (ticket.isOutdated()) { + this.emit(MailboxManagerEvents.ticketClose, ticket); + } + }); + }); + } +} + +/** + * Emitted when a new ticket is created by a user. + * @event MailboxManager#ticketCreate + * @param {Ticket} ticket The ticket + * @example + * manager.on(MailboxManagerEvents.ticketCreate, (ticket) => { + * console.log(`${ticket.id} has been created`); + * }); + */ + +/** + * Emitted when a ticket is updated. A ticket update is basically a new message sent or received. + * @event MailboxManager#ticketUpdate + * @param {Ticket} ticket The ticket + * @example + * manager.on(MailboxManagerEvents.ticketUpdate, (ticket) => { + * console.log(`${ticket.id} has been updated`); + * }); + */ + +/** + * Emitted when a ticket is logged. Always emitted when the ticket is getting closed. + * @event MailboxManager#ticketLog + * @param {Ticket} ticket The ticket + * @example + * manager.on(MailboxManagerEvents.ticketLog, (ticket) => { + * console.log(`${ticket.id} has been logged`); + * }); + */ + +/** + * Emitted when a ticket is closed. + * @event MailboxManager#ticketClose + * @param {Ticket} ticket The ticket + * @example + * manager.on(MailboxManagerEvents.ticketClose, (ticket) => { + * console.log(`${ticket.id} has been closed`); + * }); + */ + +/** + * Emitted when a ticket is force closed by someone. + * @event MailboxManager#ticketForceClose + * @param {Ticket} ticket The ticket + * @param {Discord.User | Discord.PartialUser} user The user who force closed the ticket + * @example + * manager.on(MailboxManagerEvents.ticketForceClose, (ticket, user) => { + * console.log(`${ticket.id} has been force closed by ${user.username}`); + * }); + */ + +/** + * Emitted when a ticket is removed from the collection. Always emitted when a ticket is closed. + * @event MailboxManager#ticketDelete + * @param {Ticket} ticket The ticket + * @example + * manager.on(MailboxManagerEvents.ticketDelete, (ticket) => { + * console.log(`${ticket.id} has been deleted`); + * }); + */ + +/** + * Emitted when a reply is sent from a guild. Always emitted when a ticket is updated. + * @event MailboxManager#replySent + * @param {Discord.Message} message The message + * @param {Discord.Message} answer The answer + * @example + * manager.on(MailboxManagerEvents.replySent, (message, answer) => { + * console.log(message.id); + * console.log(answer.id); + * }); + */ + +/** + * Emitted when the original reply message is removed from the channel. + * @event MailboxManager#replyDelete + * @param {Discord.Message} message The message + * @example + * manager.on(MailboxManagerEvents.replyDelete, (message) => { + * console.log(message.id); + * }); + */ diff --git a/src/MailboxManagerEvents.ts b/src/MailboxManagerEvents.ts new file mode 100644 index 0000000..6c941ca --- /dev/null +++ b/src/MailboxManagerEvents.ts @@ -0,0 +1,19 @@ +/** + * Events fired by the {@link MailboxManager} + * + * @export + * @enum {string} + */ +export enum MailboxManagerEvents { + ticketCreate = 'ticketCreate', + ticketUpdate = 'ticketUpdate', + ticketLog = 'ticketLog', + ticketClose = 'ticketClose', + ticketForceClose = 'ticketForceClose', + ticketDelete = 'ticketDelete', + + replySent = 'replySent', + replyDelete = 'replyDelete', + + error = 'error', +} diff --git a/src/handlers/index.ts b/src/handlers/index.ts index 306090d..1fac2cc 100644 --- a/src/handlers/index.ts +++ b/src/handlers/index.ts @@ -1,4 +1,4 @@ -export * from './message'; +export * from './messageCreate'; export * from './messageReactionAdd'; export * from './ticketClose'; export * from './ticketLog'; diff --git a/src/handlers/message.ts b/src/handlers/message.ts deleted file mode 100644 index 377f317..0000000 --- a/src/handlers/message.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { arrowUp } from './../utils/constants'; -import { Collection, Message, Snowflake, TextChannel } from 'discord.js'; -import * as uuid from 'uuid'; - -import { Ticket } from './../types'; -import { MailboxManager } from '..'; -import { generateMessage } from '../utils/TicketUtils'; - -export const handleMessage = async (manager: MailboxManager, message: Message) => { - if (message.author.bot) return; - - if (message.mentions.everyone) { - return message.author.send(manager.options.notAllowedToPing); - } - - const isFromDM = message.channel.type === 'dm'; - let authorId = message.author.id; - let ticket: Ticket; - let botMessage: Message; - let answer: Message; - - if (!!message.reference) { - botMessage = await message.channel.messages.fetch(message.reference.messageID); - - let messageId: Snowflake; - if (manager.options.embedOptions) { - messageId = botMessage.embeds[0].footer?.text?.replace('ID: ', ''); - } else { - const array = botMessage.content.split('\n\nā€‹'); - const footer = array[array.length - 1]; - messageId = footer.replace('ID: ', ''); - } - - if (!messageId) return; - - const userTicket = manager.userTickets.find(userTickets => userTickets.some(t => t.messages.last().id === messageId)); - if (!userTicket) return; - - ticket = userTicket.find(t => t.messages.last().id === messageId); - if (!ticket) return; - - const ticketIsOutdated = (Date.now() - ticket.lastMessageAt) >= (manager.options.closeTicketAfter * 1000); - if (ticketIsOutdated && isFromDM) { - return manager.emit('ticketClose', ticket); - } - - ticket.lastMessageAt = message.createdTimestamp; - ticket.messages.set(message.id, message); - if (manager.canFormatLogs) { - ticket.logs.push(manager.options.loggingOptions.format(message)); - } - ticket.messages.sort((m1, m2) => m1.createdTimestamp - m2.createdTimestamp); - - manager.emit('ticketUpdate', ticket); - } else { - if (!isFromDM) return; - - ticket = { - id: uuid.v4(), - - createdAt: message.createdTimestamp, - createdBy: authorId, - - messages: new Collection().set(message.id, message), - logs: manager.canFormatLogs ? [manager.options.loggingOptions.format(message)] : [], - lastMessageAt: message.createdTimestamp, - }; - - const userTickets = manager.userTickets.get(ticket.createdBy); - if (userTickets) { - if (userTickets.size === manager.options.maxOngoingTicketsPerUser) { - return message.author.send(manager.options.tooMuchTickets); - } - } else { - manager.userTickets.set(ticket.createdBy, new Collection()); - } - manager.userTickets.get(ticket.createdBy).set(ticket.id, ticket); - manager.emit('ticketCreate', ticket); - } - - const msg = generateMessage(manager, ticket, message); - - switch(message.channel.type) { - case 'dm': { - const mailboxChannel = await message.client.channels.fetch(manager.options.mailboxChannel); - answer = await (mailboxChannel as TextChannel).send(msg); - - if (manager.options.forceCloseEmoji) { - await answer.react(manager.options.forceCloseEmoji); - } - return; - } - - case 'text': { - if (botMessage) { - const embed = botMessage.embeds[0]; - if (embed) { - embed.setAuthor(embed.author.name, arrowUp); - await botMessage.edit({ content: botMessage.content, embed }); - } - - if (manager.options.replySentEmoji) { - await botMessage.react(manager.options.replySentEmoji); - } - } - - if (manager.options.deleteReplies) { - await message.delete(); - manager.emit('replyDelete', message); - } - - answer = await ticket.messages.last(2)[0].author.send(msg); - return manager.emit('replySent', message, answer); - } - - default: break; - } -}; diff --git a/src/handlers/messageCreate.ts b/src/handlers/messageCreate.ts new file mode 100644 index 0000000..ab9b03b --- /dev/null +++ b/src/handlers/messageCreate.ts @@ -0,0 +1,145 @@ +import { + Collection, + Message, + Snowflake, + TextChannel, + Constants, + MessageEmbed, +} from 'discord.js'; + +import { Ticket } from '../types'; +import { MailboxManager, MailboxManagerEvents } from '..'; +import { extractMessageId } from '../utils/MessageUtils'; +import { arrowUp } from '../utils/constants'; + +export const handleMessage = async ( + manager: MailboxManager, + message: Message +) => { + console.log(message); + + if (message.author.bot) return; + + if (message.mentions.everyone) { + return message.author.send(manager.options.notAllowedToPing); + } + + console.log('2', message); + + const isFromDM = + message.channel.type === + Constants.ChannelTypes[Constants.ChannelTypes.DM]; + let userTickets: Collection; + let ticket: Ticket; + let botMessage: Message; + let answer: Message; + + const isExistingTicket = !!message.reference; + if (isExistingTicket) { + let messageId: Snowflake; + + botMessage = await message.channel.messages.fetch( + message.reference.messageId + ); + + messageId = extractMessageId( + botMessage, + !!manager.options.embedOptions + ); + if (!messageId) return; + + userTickets = manager.userTickets.find((userTickets) => + userTickets.some((t) => t.messages.last().id === messageId) + ); + if (!userTickets || userTickets.size === 0) return; + + ticket = userTickets.find((t) => t.messages.last().id === messageId); + if (!ticket) return; + + if (isFromDM && ticket.isOutdated()) { + return manager.emit(MailboxManagerEvents.ticketClose, ticket); + } + + ticket.addMessage(message, manager.canFormatLogs); + manager.emit(MailboxManagerEvents.ticketUpdate, ticket); + } else { + console.log('new message'); + if (!isFromDM) return; + + ticket = new Ticket(message, manager.options.loggingOptions.format); + userTickets = manager.userTickets.get(ticket.createdBy); + + if ( + userTickets && + userTickets.size === manager.options.maxOngoingTicketsPerUser + ) { + return message.author.send(manager.options.tooMuchTickets); + } else { + if (!userTickets) { + manager.userTickets.set(ticket.createdBy, new Collection()); + } + manager.userTickets.get(ticket.createdBy).set(ticket.id, ticket); + manager.emit(MailboxManagerEvents.ticketCreate, ticket); + } + } + + const ticketMessage = ticket.generateMessage(manager, message); + switch (message.channel.type) { + case Constants.ChannelTypes[Constants.ChannelTypes.DM]: { + const mailboxChannel = await message.client.channels.fetch( + manager.options.mailboxChannel + ); + answer = await (mailboxChannel as TextChannel).send( + ticketMessage instanceof MessageEmbed + ? { embeds: [ticketMessage] } + : ticketMessage + ); + + if (manager.options.forceCloseEmoji) { + await answer.react(manager.options.forceCloseEmoji); + } + return; + } + + case Constants.ChannelTypes[Constants.ChannelTypes.GUILD_TEXT]: { + if (botMessage) { + const embed = botMessage.embeds[0]; + if (embed) { + embed.setAuthor(embed.author.name, arrowUp); + await botMessage.edit({ + content: botMessage.content, + embeds: [embed], + }); + } + + if (manager.options.replySentEmoji) { + await botMessage.react(manager.options.replySentEmoji); + } + } + + if (manager.options.deleteReplies) { + await message.delete(); + manager.emit(MailboxManagerEvents.replyDelete, message); + } + + answer = await ticket.messages + .last() + .author.send( + ticketMessage instanceof MessageEmbed + ? { embeds: [ticketMessage] } + : ticketMessage + ); + return manager.emit( + MailboxManagerEvents.replySent, + message, + answer + ); + } + + default: + console.error( + `${message.channel.type} is not an authorized channel type.` + ); + break; + } +}; diff --git a/src/handlers/messageReactionAdd.ts b/src/handlers/messageReactionAdd.ts index 0def978..7e7e13c 100644 --- a/src/handlers/messageReactionAdd.ts +++ b/src/handlers/messageReactionAdd.ts @@ -1,34 +1,50 @@ +import { + MessageReaction, + PartialUser, + User, + Snowflake, + PartialMessageReaction, + PartialMessage, + Collection, + Message, +} from 'discord.js'; + +import { extractMessageId } from '../utils/MessageUtils'; +import { Ticket } from '../types'; import { checked } from './../utils/constants'; -import { MailboxManager } from '..'; -import { MessageReaction, PartialUser, User, Snowflake } from 'discord.js'; - -export const handleReaction = async (manager: MailboxManager, messageReaction: MessageReaction, user: User | PartialUser) => { - if (user.bot) return; - if (messageReaction.emoji.name !== manager.options.forceCloseEmoji) return; - - const botMessage = messageReaction.message; - - let messageId: Snowflake; - if (manager.options.embedOptions) { - messageId = botMessage.embeds[0].footer.text.replace('ID: ', ''); - } else { - const array = botMessage.content.split('\n\nā€‹'); - const footer = array[array.length - 1]; - messageId = footer.replace('ID: ', ''); - } - - const userTicket = manager.userTickets.find(userTickets => userTickets.some(t => t.messages.last().id === messageId)); - if (!userTicket) return; - - const ticket = userTicket.find(t => t.messages.last().id === messageId); - if (!ticket) return; - - const embed = botMessage.embeds[0]; - if (embed) { - embed.setAuthor(embed.author.name, checked); - await botMessage.edit({ content: botMessage.content, embed }); - } - - manager.emit('ticketForceClose', ticket, user); - return manager.emit('ticketClose', ticket); +import { MailboxManager, MailboxManagerEvents } from '..'; + +export const handleReaction = async ( + manager: MailboxManager, + messageReaction: MessageReaction | PartialMessageReaction, + user: User | PartialUser +) => { + let botMessage: Message | PartialMessage; + let messageId: Snowflake; + let userTickets: Collection; + let ticket: Ticket; + + if (user.bot) return; + if (messageReaction.emoji.name !== manager.options.forceCloseEmoji) return; + + botMessage = messageReaction.message; + messageId = extractMessageId(botMessage, !!manager.options.embedOptions); + if (!messageId) return; + + userTickets = manager.userTickets.find((userTickets) => + userTickets.some((t) => t.messages.last().id === messageId) + ); + if (!userTickets) return; + + ticket = userTickets.find((t) => t.messages.last().id === messageId); + if (!ticket) return; + + const embed = botMessage.embeds && botMessage.embeds[0]; + if (embed) { + embed.setAuthor(embed.author.name, checked); + await botMessage.edit({ content: botMessage.content, embeds: [embed] }); + } + + manager.emit(MailboxManagerEvents.ticketForceClose, ticket, user); + return manager.emit(MailboxManagerEvents.ticketClose, ticket); }; diff --git a/src/handlers/ticketClose.ts b/src/handlers/ticketClose.ts index 0249ada..95bfb21 100644 --- a/src/handlers/ticketClose.ts +++ b/src/handlers/ticketClose.ts @@ -1,28 +1,33 @@ -import { MailboxManager } from '..'; +import { MessageEmbed } from 'discord.js'; + +import { MailboxManager, MailboxManagerEvents } from '..'; import { Ticket } from '../types'; -import { generateDescription, generateEmbedOrString, generateHeader } from '../utils/TicketUtils'; -export const handleClosing = async (manager : MailboxManager, ticket: Ticket) => { - ticket.closedAt = Date.now(); +export const handleClosing = async ( + manager: MailboxManager, + ticket: Ticket +) => { + ticket.closedAt = Date.now(); + + const userTickets = manager.userTickets.get(ticket.createdBy); + if (userTickets) { + userTickets.delete(ticket.id); + if (userTickets.size === 0) + manager.userTickets.delete(ticket.createdBy); - const userTickets = manager.userTickets.get(ticket.createdBy); - if (userTickets) { - - userTickets.delete(ticket.id); - if (userTickets.size === 0) manager.userTickets.delete(ticket.createdBy); + if (manager.options.ticketClose) { + const user = await manager.client.users.fetch(ticket.createdBy); - if (manager.options.ticketClose) { - const user = await manager.client.users.fetch(ticket.createdBy); - - const text = manager.options.ticketClose(userTickets?.size ?? 0); - const header = generateHeader(manager, ticket.id); - const description = generateDescription(manager, text); - const message = generateEmbedOrString(manager, header, description, '', false); - - user.send(message); - } + const text = manager.options.ticketClose(userTickets?.size ?? 0); + const message = ticket.generateMessage(manager, text); + user.send( + message instanceof MessageEmbed + ? { embeds: [message] } + : message + ); + } - manager.emit('ticketDelete', ticket); - manager.emit('ticketLog', ticket); - } + manager.emit(MailboxManagerEvents.ticketDelete, ticket); + manager.emit(MailboxManagerEvents.ticketLog, ticket); + } }; diff --git a/src/handlers/ticketLog.ts b/src/handlers/ticketLog.ts index dc1c0fa..4d5b196 100644 --- a/src/handlers/ticketLog.ts +++ b/src/handlers/ticketLog.ts @@ -1,25 +1,28 @@ import { TextChannel } from 'discord.js'; + import { MailboxManager } from '..'; import { Ticket } from '../types'; export const handleLog = async (manager: MailboxManager, ticket: Ticket) => { - if (!manager.options.loggingOptions) return; - - const logMessage = { - content: manager.options.loggingOptions.generateMessage(ticket), - files: [ - { - attachment: Buffer.from(ticket.logs.join('\n')), - name: manager.options.loggingOptions.generateFilename(ticket), - }, - ], - }; + if (!manager.options.loggingOptions) return; + + const logMessage = { + content: manager.options.loggingOptions.generateMessage(ticket), + files: [ + { + attachment: Buffer.from(ticket.logs.join('\n')), + name: manager.options.loggingOptions.generateFilename(ticket), + }, + ], + }; - if (manager.options.loggingOptions.sendToRecipient) { - const user = await manager.client.users.fetch(ticket.createdBy); - await user.send(logMessage); - } + if (manager.options.loggingOptions.sendToRecipient) { + const user = await manager.client.users.fetch(ticket.createdBy); + await user.send(logMessage); + } - const channel = await manager.client.channels.fetch(manager.options.loggingOptions.channel); - await(channel as TextChannel).send(logMessage); + const channel = await manager.client.channels.fetch( + manager.options.loggingOptions.channel + ); + await (channel as TextChannel).send(logMessage); }; diff --git a/src/index.ts b/src/index.ts index 023607d..2eda80d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,223 +1,3 @@ -import { Client, ClientOptions, Collection, Snowflake } from 'discord.js'; -import { EventEmitter } from 'events'; -import { CronJob } from 'cron'; - -import { handleMessage, handleReaction, handleClosing, handleLog} from './handlers'; -import { MailboxManagerOptions, Ticket } from './types'; - -/** - * - * @export - * @class MailboxManager - * @extends {EventEmitter} - */ -export class MailboxManager extends EventEmitter { - /** - * The configuration of the mailbox manager. - * - * @private - * @type {MailboxManagerOptions} - */ - public readonly options: MailboxManagerOptions; - - /** - * The collection of tickets per user. - * - * @type {Collection>} - * @memberof MailboxManager - */ - public readonly userTickets: Collection>; - - /** - * The client that instantiated this Manager - * @name MailboxManager#client - * @type {Client} - * @readonly - */ - public readonly client: Client; - - /** - * The cron job to check if tickets need to be closed because outdated. - * - * @private - * @type {CronJob} - */ - private job?: CronJob; - - /** - * Whether the logging options is set with a format method or not - * - * @type {boolean} - */ - public canFormatLogs: boolean; - - /** - *Creates an instance of MailboxManager. - * @param {Client} client - * @param {MailboxManagerOptions} [options={ - * tooMuchTickets: 'You have too much opened tickets!', - * notAllowedToPing: 'You are not allowed to mention @everyone and @here.', - * replyMessage: 'Use the "reply" feature to respond.', - * maxOngoingTicketsPerUser: 3, - * closeTicketAfter: 60, - * formatTitle: id => `Ticket ${id}`, - * cronTime: '* * * * *', - * mailboxChannel: null - * }] - */ - constructor(client: Client, options: MailboxManagerOptions = { - tooMuchTickets: 'You have too much opened tickets!', - notAllowedToPing: 'You are not allowed to mention @everyone and @here.', - replyMessage: 'Use the "reply" feature to respond.', - ticketClose: nbUserTicketsLeft => `This ticket has been closed. You now have ${nbUserTicketsLeft} tickets that are still opened.`, - maxOngoingTicketsPerUser: 3, - closeTicketAfter: 60, - formatTitle: id => `Ticket ${id}`, - cronTime: '* * * * *', - mailboxChannel: null - }) { - super(); - - if (!options.mailboxChannel) throw new Error('Please define the mailbox channel in the options!'); - - this.client = client; - this.options = options; - this.userTickets = new Collection(); - this.canFormatLogs = this.options.loggingOptions && !!this.options.loggingOptions.format; - - this.client.on('message', async (message) => handleMessage(this, message)); - if (this.options.forceCloseEmoji) { - this.client.on('messageReactionAdd', async (messageReaction, user) => handleReaction(this, messageReaction, user)); - } - - this.on('ticketClose', async (ticket: Ticket) => handleClosing(this, ticket)); - this.on('ticketLog', async (ticket: Ticket) => handleLog(this, ticket)); - - this.job = new CronJob(this.options.cronTime, () => this.checkTickets(), null, null, null, this); - this.job.start(); - - } - - checkTickets() { - this.userTickets.each(userTickets => { - userTickets.each(ticket => { - const isOutdated = Date.now() - ticket.lastMessageAt > this.options.closeTicketAfter * 1000; - if (isOutdated) { - this.emit('ticketClose', ticket); - } - }); - }); - } -} - -/** - * A wrapper class for {@link Client} that carries a {@link MailboxManager} instance. - * - * @export - * @class MailboxClient - * @extends {Client} - */ -export class MailboxClient extends Client { - /** - * The mailbox manager. - * - * @type {MailboxManager} - * @memberof MailboxClient - */ - public readonly mailboxManager: MailboxManager; - - /** - *Creates an instance of MailboxClient. - * @param {ClientOptions} [options] - * @param {MailboxManagerOptions} [mailboxOptions] - * @memberof MailboxClient - */ - constructor(mailboxOptions: MailboxManagerOptions, options?: ClientOptions) { - super(options); - - this.mailboxManager = new MailboxManager(this, mailboxOptions); - } -} - -/** - * Emitted when a new ticket is created by a user. - * @event MailboxManager#ticketCreate - * @param {Ticket} ticket The ticket - * @example - * manager.on('ticketCreate', (ticket) => { - * console.log(`${ticket.id} has been created`); - * }); - */ - -/** - * Emitted when a ticket is updated. A ticket update is basically a new message sent or received. - * @event MailboxManager#ticketUpdate - * @param {Ticket} ticket The ticket - * @example - * manager.on('ticketUpdate', (ticket) => { - * console.log(`${ticket.id} has been updated`); - * }); - */ - -/** - * Emitted when a ticket is logged. Always emitted when the ticket is getting closed. - * @event MailboxManager#ticketLog - * @param {Ticket} ticket The ticket - * @example - * manager.on('ticketLog', (ticket) => { - * console.log(`${ticket.id} has been logged`); - * }); - */ - -/** - * Emitted when a ticket is closed. - * @event MailboxManager#ticketClose - * @param {Ticket} ticket The ticket - * @example - * manager.on('ticketClose', (ticket) => { - * console.log(`${ticket.id} has been closed`); - * }); - */ - -/** - * Emitted when a ticket is force closed by someone. - * @event MailboxManager#ticketForceClose - * @param {Ticket} ticket The ticket - * @param {Discord.User | Discord.PartialUser} user The user who force closed the ticket - * @example - * manager.on('ticketForceClose', (ticket, user) => { - * console.log(`${ticket.id} has been force closed by ${user.username}`); - * }); - */ - -/** - * Emitted when a ticket is removed from the collection. Always emitted when a ticket is closed. - * @event MailboxManager#ticketDelete - * @param {Ticket} ticket The ticket - * @example - * manager.on('ticketDelete', (ticket) => { - * console.log(`${ticket.id} has been deleted`); - * }); - */ - -/** - * Emitted when a reply is sent from a guild. Always emitted when a ticket is updated. - * @event MailboxManager#replySent - * @param {Discord.Message} message The message - * @param {Discord.Message} answer The answer - * @example - * manager.on('replySent', (message, answer) => { - * console.log(message.id); - * console.log(answer.id); - * }); - */ - -/** - * Emitted when the original reply message is removed from the channel. - * @event MailboxManager#replyDelete - * @param {Discord.Message} message The message - * @example - * manager.on('replyDelete', (message) => { - * console.log(message.id); - * }); - */ \ No newline at end of file +export * from './MailboxClient'; +export * from './MailboxManager'; +export * from './MailboxManagerEvents'; diff --git a/src/types/LogsOptions.ts b/src/types/LogsOptions.ts index d61b778..11b1009 100644 --- a/src/types/LogsOptions.ts +++ b/src/types/LogsOptions.ts @@ -1,4 +1,5 @@ import { Message, Snowflake } from 'discord.js'; + import { Ticket } from './Ticket'; /** @@ -7,43 +8,43 @@ import { Ticket } from './Ticket'; * @interface LogsOptions */ export interface LogsOptions { - /** - * Whether to show the name of the person who replies or not. - * - * @type {boolean} - * @memberof LogsOptions - */ - showName: boolean; + /** + * Whether to show the name of the person who replies or not. + * + * @type {boolean} + * @memberof LogsOptions + */ + showName: boolean; - /** - * Generate the file name. - * - */ - generateFilename: (ticket: Ticket) => string; + /** + * Generate the file name. + * + */ + generateFilename: (ticket: Ticket) => string; - /** - * The format to print the message logged. - * - */ - format: (message: Message) => string; + /** + * The format to print the message logged. + * + */ + format: (message: Message) => string; - /** - * The channel in which the logs should be sent. - * - * @type {Snowflake} - */ - channel: Snowflake; + /** + * The channel in which the logs should be sent. + * + * @type {Snowflake} + */ + channel: Snowflake; - /** - * Whether the logs should be sent via DM to the recipient or not. - * - * @type {boolean} - */ - sendToRecipient: boolean; + /** + * Whether the logs should be sent via DM to the recipient or not. + * + * @type {boolean} + */ + sendToRecipient: boolean; - /** - * Generates the message to send with the file in the channel and optionally to the recipients. - * - */ - generateMessage: (ticket: Ticket) => string; + /** + * Generates the message to send with the file in the channel and optionally to the recipients. + * + */ + generateMessage: (ticket: Ticket) => string; } diff --git a/src/types/MailboxManagerOptions.ts b/src/types/MailboxManagerOptions.ts index 9a0cf19..0f277ac 100644 --- a/src/types/MailboxManagerOptions.ts +++ b/src/types/MailboxManagerOptions.ts @@ -1,8 +1,9 @@ -import { EmbedOptions } from './EmbedOptions'; -import { LogsOptions } from './LogsOptions'; import { EmojiResolvable, Snowflake } from 'discord.js'; import { Moment } from 'moment'; +import { EmbedOptions } from './EmbedOptions'; +import { LogsOptions } from './LogsOptions'; + /** * The mailbox manager options. * @@ -10,104 +11,104 @@ import { Moment } from 'moment'; * @interface MailboxManagerOptions */ export interface MailboxManagerOptions { - /** - * A method to generate the message to return to the user when their ticket is closed. - * - * @type {string} - */ - ticketClose: (numberOfTickets: number) => string; + /** + * A method to generate the message to return to the user when their ticket is closed. + * + * @type {string} + */ + ticketClose: (numberOfTickets: number) => string; - /** - * The message to return when a user has too much not-closed tickets and is trying to create a new one. - * - * @type {string} - * @memberof MailboxManagerOptions - */ - tooMuchTickets: string; + /** + * The message to return when a user has too much not-closed tickets and is trying to create a new one. + * + * @type {string} + * @memberof MailboxManagerOptions + */ + tooMuchTickets: string; - /** - * The message to return when a ticket message contains @everyone or @here - * - * @type {string} - */ - notAllowedToPing: string; + /** + * The message to return when a ticket message contains @everyone or @here + * + * @type {string} + */ + notAllowedToPing: string; - /** - * The text under each embed that says "reply to continue messaging with this ticket". - * - * @type {string} - */ - replyMessage: string; + /** + * The text under each embed that says "reply to continue messaging with this ticket". + * + * @type {string} + */ + replyMessage: string; - /** - * The maximum of possibly not-closed tickets per user. - * - * @type {number} - * @memberof MailboxManagerOptions - */ - maxOngoingTicketsPerUser: number; + /** + * The maximum of possibly not-closed tickets per user. + * + * @type {number} + * @memberof MailboxManagerOptions + */ + maxOngoingTicketsPerUser: number; - /** - * The channel in which the tickets' messages are sent. - * - * @type {Snowflake} - */ - mailboxChannel: Snowflake; + /** + * The channel in which the tickets' messages are sent. + * + * @type {Snowflake} + */ + mailboxChannel: Snowflake; - /** - * Whether the replies in the mailbox channel should get deleted or not. - * - * @type {boolean} - */ - deleteReplies?: boolean; + /** + * Whether the replies in the mailbox channel should get deleted or not. + * + * @type {boolean} + */ + deleteReplies?: boolean; - /** - * Seconds after which, if no interaction for a ticket, should it be closed. - * - * @type {number} - */ - closeTicketAfter: number; + /** + * Seconds after which, if no interaction for a ticket, should it be closed. + * + * @type {number} + */ + closeTicketAfter: number; - /** - * Format of the ticket title. The ticket id must be present in the returned string. - * - * @required - */ - formatTitle: (ticketId: string) => string; + /** + * Format of the ticket title. The ticket id must be present in the returned string. + * + * @required + */ + formatTitle: (ticketId: string) => string; - /** - * The scheduled time where all tickets are verified for time out. - * - * @type {(string | Date)} - * @see {cron} https://www.npmjs.com/package/cron - */ - cronTime: string | Date | Moment; + /** + * The scheduled time where all tickets are verified for time out. + * + * @type {(string | Date)} + * @see {cron} https://www.npmjs.com/package/cron + */ + cronTime: string | Date | Moment; - /** - * The emoji added to mails to trigger the force close. - * - * @type {EmojiResolvable} - */ - forceCloseEmoji?: EmojiResolvable; + /** + * The emoji added to mails to trigger the force close. + * + * @type {EmojiResolvable} + */ + forceCloseEmoji?: EmojiResolvable; - /** - * The emoji to add as reaction to a ticket when a reply has been sent already. - * - * @type {EmojiResolvable} - */ - replySentEmoji?: EmojiResolvable; + /** + * The emoji to add as reaction to a ticket when a reply has been sent already. + * + * @type {EmojiResolvable} + */ + replySentEmoji?: EmojiResolvable; - /** - * The logging options. - * - * @type {LogsOptions} - */ - loggingOptions?: LogsOptions; + /** + * The logging options. + * + * @type {LogsOptions} + */ + loggingOptions?: LogsOptions; - /** - * The embed options. - * - * @type {EmbedOptions} - */ - embedOptions?: EmbedOptions; + /** + * The embed options. + * + * @type {EmbedOptions} + */ + embedOptions?: EmbedOptions; } diff --git a/src/types/Ticket.ts b/src/types/Ticket.ts index 114e49e..83378ee 100644 --- a/src/types/Ticket.ts +++ b/src/types/Ticket.ts @@ -1,57 +1,285 @@ -import { Collection, Message, Snowflake } from 'discord.js'; +import { + Collection, + Constants, + Message, + MessageEmbed, + Snowflake, +} from 'discord.js'; +import * as uuid from 'uuid'; + +import { MailboxManager } from '..'; +import { isNullOrWhiteSpaces } from '../utils/StringUtils'; +import { arrowDown } from '../utils/constants'; /** + * * * @export - * @interface Ticket + * @class Ticket */ -export interface Ticket { - /** - * The identifier of the ticket. - * - * @type {string} - */ - id: string; - - /** - * The messages related to that ticket. - * - * @type {Collection} - */ - messages: Collection; - - /** - * The generated logs for that ticket. - * - * @type {Array} - */ - logs: Array; - - /** - * The time in milliseconds at which the last message of the ticket has been sent. - * - * @type {number} - */ - lastMessageAt: number; - - /** - * The identifier of the user who initiated the ticket. - * - * @type {Snowflake} - */ - createdBy: Snowflake; - - /** - * The time in milliseconds at which the ticket was created. - * - * @type {number} - */ - createdAt: number; - - /** - * The time in milliseconds at which the ticket was closed. - * - * @type {number} - */ - closedAt?: number; +export class Ticket { + //#region Fields + /** + * The identifier of the ticket. + * + * @type {string} + */ + id: string; + + /** + * The messages related to that ticket. + * + * @type {Collection} + */ + messages: Collection; + + /** + * The generated logs for that ticket. + * + * @type {Array} + */ + logs: Array; + + /** + * The time in milliseconds at which the last message of the ticket has been sent. + * + * @type {number} + */ + lastMessageAt: number; + + /** + * The identifier of the user who initiated the ticket. + * + * @type {Snowflake} + */ + createdBy: Snowflake; + + /** + * The time in milliseconds at which the ticket was created. + * + * @type {number} + */ + createdAt: number; + + /** + * The time in milliseconds at which the ticket was closed. + * + * @type {number} + */ + closedAt?: number; + + /** + * The method that formats a message before adding it to the logs. + * + * @memberof Ticket + */ + formatLogs?: (message: Message) => string; + + /** + * Seconds after which, if no interaction with the ticket, it should be closed. + * + * @type {number} + * @memberof Ticket + */ + closeAfter: number; + //#endregion + + /** + * Creates an instance of Ticket. + * @param {Message} firstMessage + * @param {(message: Message) => string} [formatLogs=null] + * @memberof Ticket + */ + constructor( + firstMessage: Message, + formatLogs: (message: Message) => string = null, + closeAfter: number = 60 + ) { + this.id = uuid.v4(); + this.messages = new Collection(); + this.logs = []; + this.formatLogs = formatLogs; + this.closeAfter = closeAfter; + this.createdAt = firstMessage.createdTimestamp; + this.createdBy = firstMessage.author.id; + + this.addMessage(firstMessage); + } + + /** + * Add a message to the collection of messages of a ticket. + * + * @param {Message} message + * @memberof Ticket + */ + addMessage(message: Message, shouldFormatLogs: boolean = false) { + this.messages.set(message.id, message); + this.messages.sort( + (message1, message2) => + message1.createdTimestamp - message2.createdTimestamp + ); + this.lastMessageAt = message.createdTimestamp; + + this.addLogs(message, shouldFormatLogs); + } + + /** + * Add a message to the logs of the ticket. + * + * @private + * @param {Message} message + * @param {bool} shouldFormatLogs + * @memberof Ticket + */ + private addLogs(message: Message, shouldFormatLogs: boolean = false) { + if (shouldFormatLogs && this.formatLogs) { + this.logs.push(this.formatLogs(message)); + } else { + this.logs.push(message.cleanContent); + } + } + + /** + * Whether a ticket is supposed to be closed or not due to [out of date]. + * + * @return {boolean} if the ticket is outdated or not + * @memberof Ticket + */ + isOutdated(): boolean { + return Date.now() - this.lastMessageAt >= this.closeAfter * 1000; + } + + /** + * Generates the header, description and footer texts then return the generated message or embed. + * + * @param {MailboxManager} manager + * @param {Message | string} messageOrText + * @memberof Ticket + */ + generateMessage(manager: MailboxManager, messageOrText: Message | string) { + const isSentToAdmin = + messageOrText instanceof Message + ? messageOrText.channel.type === + Constants.ChannelTypes[Constants.ChannelTypes.DM] + : false; + + const header = this.generateHeader(this.id, manager); + const description = + messageOrText instanceof Message + ? this.generateDescription( + messageOrText, + isSentToAdmin || manager.options.loggingOptions.showName + ? null + : `${messageOrText.member.user.username}:\n`, + `\n\n**${manager.options.replyMessage}**` + ) + : this.generateDescription(messageOrText); + const footer = + messageOrText instanceof Message + ? this.generateFooter(messageOrText.id) + : ''; + + return this.generateEmbedOrString( + manager, + header, + description, + footer, + isSentToAdmin + ); + } + + /** + * Generates the header text. + * + * @export + * @param {MailboxManager} manager + * @param {string} ticketId + * @returns + */ + private generateHeader(ticketId: string, manager: MailboxManager) { + const header = manager.options.formatTitle(ticketId); + if (isNullOrWhiteSpaces(header) || !header.includes(ticketId)) + throw new Error('Ticket title must at least contain the ticket id'); + + return header; + } + + /** + * Generates the description text. + * + * @export + * @param {Message | string} messageOrText + * @param {string} [prefix] + * @param {string} [suffix] + * @returns + */ + private generateDescription( + messageOrText: Message | string, + prefix?: string, + suffix?: string + ) { + const stringBuilder: string[] = []; + + if (prefix && !isNullOrWhiteSpaces(prefix)) { + stringBuilder.push(prefix); + } + + if (messageOrText instanceof Message) { + stringBuilder.push(messageOrText.cleanContent); + } else { + stringBuilder.push(messageOrText); + } + + if (suffix && !isNullOrWhiteSpaces(suffix)) { + stringBuilder.push(suffix); + } + + return stringBuilder.join(''); + } + + /** + * Generates the footer text. + * + * @export + * @param {string} messageId + * @returns + */ + private generateFooter(messageId: string) { + return `ID: ${messageId}`; + } + + /** + * Based on the provider header, description & footer texts, generate an embed or a plaintext message. + * + * @export + * @param {MailboxManager} manager + * @param {string} header + * @param {string} description + * @param {string} footer + * @param {boolean} isSentToAdmin + * @returns + */ + private generateEmbedOrString( + manager: MailboxManager, + header: string, + description: string, + footer: string, + isSentToAdmin: boolean + ) { + if (manager.options.embedOptions?.send) { + const embed = new MessageEmbed(manager.options.embedOptions) + .setAuthor(header) + .setDescription(description) + .setFooter(footer) + .setTimestamp(); + + if (isSentToAdmin) { + embed.setAuthor(embed.author.name, arrowDown); + } + + return embed; + } + + return `${header}\n\nā€‹${description}\n\nā€‹${footer}`; + } } diff --git a/src/utils/MessageUtils.ts b/src/utils/MessageUtils.ts new file mode 100644 index 0000000..52ea93e --- /dev/null +++ b/src/utils/MessageUtils.ts @@ -0,0 +1,23 @@ +import { Message, PartialMessage, Snowflake } from 'discord.js'; + +/** + * Extract the message ID from a message content or an embed's footer. + * + * @export + * @param {(Message | PartialMessage)} message + * @param {boolean} embedsAreActive + * @return {Snowflake} messageId + */ +export function extractMessageId( + message: Message | PartialMessage, + embedsAreActive: boolean +): Snowflake { + let messageId: Snowflake; + if (embedsAreActive) { + messageId = message.embeds[0].footer.text.replace('ID: ', ''); + } else { + const footer = message.content.split('\n\nā€‹').pop(); + messageId = footer.replace('ID: ', ''); + } + return messageId; +} diff --git a/src/utils/StringUtils.ts b/src/utils/StringUtils.ts index 154088d..60eb76b 100644 --- a/src/utils/StringUtils.ts +++ b/src/utils/StringUtils.ts @@ -1,4 +1,10 @@ +/** + * Checks that a string is either undefined, null, empty or only contains whitespaces. + * + * @export + * @param {string} str + * @return {*} + */ export function isNullOrWhiteSpaces(str: string) { - return str === null || str.match(/^ *$/) !== null; + return str === undefined || str === null || str.match(/^ *$/) !== null; } - diff --git a/src/utils/TicketUtils.ts b/src/utils/TicketUtils.ts deleted file mode 100644 index 50e5623..0000000 --- a/src/utils/TicketUtils.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Message, MessageEmbed } from 'discord.js'; -import { MailboxManager } from '..'; -import { Ticket } from '../types'; -import { arrowDown } from './constants'; -import { isNullOrWhiteSpaces } from './StringUtils'; - -/** - * Generates the header, description and footer texts then return the generated message or embed. - * - * @export - * @param {MailboxManager} manager - * @param {Ticket} ticket - * @param {Message} message - * @returns - */ -export function generateMessage(manager: MailboxManager, ticket: Ticket, message: Message) { - const isSentToAdmin = message.channel.type === 'dm'; - - const header = generateHeader(manager, ticket.id); - const description = isSentToAdmin || manager.options.loggingOptions.showName - ? generateDescription(manager, message, null, `\n\n**${manager.options.replyMessage}**`) - : generateDescription(manager, message, `${message.author.username}:\n`, `\n\n**${manager.options.replyMessage}**`); - const footer = generateFooter(message.id); - - return generateEmbedOrString(manager, header, description, footer, isSentToAdmin); - -} - -/** - * Generates the header text. - * - * @export - * @param {MailboxManager} manager - * @param {string} ticketId - * @returns - */ -export function generateHeader(manager: MailboxManager, ticketId: string) { - const header = manager.options.formatTitle(ticketId); - if (isNullOrWhiteSpaces(header) || !header.includes(ticketId)) throw new Error('Ticket title must at least contain the ticket id'); - - return header; -} - -/** - * Generates the description text. - * - * @export - * @param {MailboxManager} manager - * @param {(Message | string)} message - * @param {string} [prefix] - * @returns - */ -export function generateDescription(manager: MailboxManager, message: Message | string, prefix?: string, suffix?: string) { - const stringBuilder: string[] = []; - - if (prefix && !isNullOrWhiteSpaces(prefix)) { - stringBuilder.push(prefix); - } - - if (message instanceof Message) { - stringBuilder.push(message.cleanContent); - } else { - stringBuilder.push(message); - } - - if (suffix && !isNullOrWhiteSpaces(suffix)) { - stringBuilder.push(suffix); - } - - return stringBuilder.join(''); -} - -/** - * Generates the footer text. - * - * @export - * @param {string} messageId - * @returns - */ -export function generateFooter(messageId: string) { - return `ID: ${messageId}`; -} - -/** - * Based on the provider header, description & footer texts, generate an embed or a plaintext message. - * - * @export - * @param {MailboxManager} manager - * @param {string} header - * @param {string} description - * @param {string} footer - * @param {boolean} isSentToAdmin - * @returns - */ -export function generateEmbedOrString(manager: MailboxManager, header: string, description: string, footer: string, isSentToAdmin: boolean) { - if (manager.options.embedOptions && manager.options.embedOptions.send) { - const embed = new MessageEmbed() - .setAuthor(header) - .setDescription(description) - .setFooter(footer) - .setTimestamp(); - - if (manager.options.embedOptions.thumbnail && manager.options.embedOptions.thumbnail.url) { - embed.setThumbnail(manager.options.embedOptions.thumbnail.url); - } - if (manager.options.embedOptions.image && manager.options.embedOptions.image.url) { - embed.setImage(manager.options.embedOptions.image.url); - } - if (manager.options.embedOptions.color) { - embed.setColor(manager.options.embedOptions.color); - } - - if (isSentToAdmin) { - embed.setAuthor(embed.author.name, arrowDown); - } - - return embed; - } - - return `${header}\n\nā€‹${description}\n\nā€‹${footer}`; -} \ No newline at end of file From 0488bb6da1e1557857865887e492187cdecbc9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tina=C3=ABl=20DEVRESSE?= Date: Sun, 17 Oct 2021 02:05:40 +0200 Subject: [PATCH 2/3] fix: correct issue when sending a reply from guild --- example/index.js | 5 ++--- src/MailboxManager.ts | 18 +++++++++++----- src/handlers/messageCreate.ts | 21 ++++++++++++------ src/handlers/messageReactionAdd.ts | 5 ++++- src/types/Ticket.ts | 34 +++++++++++++++++++----------- src/types/TicketNamedParameter.ts | 8 +++++++ 6 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 src/types/TicketNamedParameter.ts diff --git a/example/index.js b/example/index.js index 4f36dc8..774ad26 100644 --- a/example/index.js +++ b/example/index.js @@ -1,4 +1,4 @@ -const { Client, Intents } = require('discord.js'); +const { Client, Intents, Constants } = require('discord.js'); const { MailboxManager, MailboxManagerEvents } = require('../lib'); @@ -7,9 +7,9 @@ const client = new Client({ Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES, Intents.FLAGS.GUILD_MESSAGE_REACTIONS, - Intents.FLAGS.GUILD_MEMBERS, Intents.FLAGS.DIRECT_MESSAGES, ], + partials: [Constants.PartialTypes.CHANNEL, Constants.PartialTypes.MESSAGE], }); const manager = new MailboxManager(client, { forceCloseEmoji: 'āŒ', @@ -52,7 +52,6 @@ const manager = new MailboxManager(client, { client.on('ready', () => console.log('Connected!')); client.on('messageCreate', (message) => { - console.log(message); if (message.content === 'show me the tickets collection') { message.reply( `\`\`\`js\n${JSON.stringify(manager.userTickets, null, 2)}\n\`\`\`` diff --git a/src/MailboxManager.ts b/src/MailboxManager.ts index d6bbf4b..64fa284 100644 --- a/src/MailboxManager.ts +++ b/src/MailboxManager.ts @@ -1,4 +1,4 @@ -import { Client, Collection, Intents, Snowflake } from 'discord.js'; +import { Client, Collection, Intents, Snowflake, Constants } from 'discord.js'; import { EventEmitter } from 'events'; import { CronJob } from 'cron'; @@ -93,6 +93,11 @@ export class MailboxManager extends EventEmitter { 'GUILD_MESSAGES intent is required to use this package!' ); } + if (!intents.has(Intents.FLAGS.DIRECT_MESSAGES)) { + throw new Error( + 'DIRECT_MESSAGES intent is required to use this package!' + ); + } if ( options.forceCloseEmoji && !intents.has(Intents.FLAGS.GUILD_MESSAGE_REACTIONS) @@ -101,10 +106,13 @@ export class MailboxManager extends EventEmitter { 'GUILD_MESSAGE_REACTIONS intent is required to use this package!' ); } - if (!intents.has(Intents.FLAGS.DIRECT_MESSAGES)) { - throw new Error( - 'DIRECT_MESSAGES intent is required to use this package!' - ); + + const partials = client.options.partials; + if (!partials.includes(Constants.PartialTypes.CHANNEL)) { + throw new Error('CHANNEL partial is required to use this package!'); + } + if (!partials.includes(Constants.PartialTypes.MESSAGE)) { + throw new Error('MESSAGE partial is required to use this package!'); } if (!options.mailboxChannel) { diff --git a/src/handlers/messageCreate.ts b/src/handlers/messageCreate.ts index ab9b03b..f98302e 100644 --- a/src/handlers/messageCreate.ts +++ b/src/handlers/messageCreate.ts @@ -5,6 +5,7 @@ import { TextChannel, Constants, MessageEmbed, + PartialMessage, } from 'discord.js'; import { Ticket } from '../types'; @@ -14,9 +15,12 @@ import { arrowUp } from '../utils/constants'; export const handleMessage = async ( manager: MailboxManager, - message: Message + msg: Message | PartialMessage ) => { - console.log(message); + let message: Message; + if (msg.partial) { + message = await message.fetch(true); + } else message = msg as Message; if (message.author.bot) return; @@ -24,8 +28,6 @@ export const handleMessage = async ( return message.author.send(manager.options.notAllowedToPing); } - console.log('2', message); - const isFromDM = message.channel.type === Constants.ChannelTypes[Constants.ChannelTypes.DM]; @@ -63,10 +65,14 @@ export const handleMessage = async ( ticket.addMessage(message, manager.canFormatLogs); manager.emit(MailboxManagerEvents.ticketUpdate, ticket); } else { - console.log('new message'); if (!isFromDM) return; - ticket = new Ticket(message, manager.options.loggingOptions.format); + ticket = new Ticket({ + firstMessage: message, + formatLogs: manager.options.loggingOptions.format, + closeAfter: manager.options.closeTicketAfter, + shouldFormatLog: true, + }); userTickets = manager.userTickets.get(ticket.createdBy); if ( @@ -107,12 +113,13 @@ export const handleMessage = async ( if (embed) { embed.setAuthor(embed.author.name, arrowUp); await botMessage.edit({ - content: botMessage.content, + content: botMessage.content || null, embeds: [embed], }); } if (manager.options.replySentEmoji) { + await botMessage.reactions.removeAll(); await botMessage.react(manager.options.replySentEmoji); } } diff --git a/src/handlers/messageReactionAdd.ts b/src/handlers/messageReactionAdd.ts index 7e7e13c..a2fb7c9 100644 --- a/src/handlers/messageReactionAdd.ts +++ b/src/handlers/messageReactionAdd.ts @@ -42,7 +42,10 @@ export const handleReaction = async ( const embed = botMessage.embeds && botMessage.embeds[0]; if (embed) { embed.setAuthor(embed.author.name, checked); - await botMessage.edit({ content: botMessage.content, embeds: [embed] }); + await botMessage.edit({ + content: botMessage.content || null, + embeds: [embed], + }); } manager.emit(MailboxManagerEvents.ticketForceClose, ticket, user); diff --git a/src/types/Ticket.ts b/src/types/Ticket.ts index 83378ee..f78dd10 100644 --- a/src/types/Ticket.ts +++ b/src/types/Ticket.ts @@ -10,6 +10,7 @@ import * as uuid from 'uuid'; import { MailboxManager } from '..'; import { isNullOrWhiteSpaces } from '../utils/StringUtils'; import { arrowDown } from '../utils/constants'; +import { TicketNamedParameter } from './TicketNamedParameter'; /** * @@ -86,24 +87,33 @@ export class Ticket { /** * Creates an instance of Ticket. - * @param {Message} firstMessage - * @param {(message: Message) => string} [formatLogs=null] + * @param {TicketNamedParameter} [parameters={ + * formatLogs: null, + * closeAfter: 60, + * shouldFormatLog: false, + * }] * @memberof Ticket */ constructor( - firstMessage: Message, - formatLogs: (message: Message) => string = null, - closeAfter: number = 60 + parameters: TicketNamedParameter = { + firstMessage: null, + formatLogs: null, + closeAfter: 60, + shouldFormatLog: false, + } ) { + if (!parameters.firstMessage) + throw new Error('A first message is mandatory!'); + this.id = uuid.v4(); this.messages = new Collection(); this.logs = []; - this.formatLogs = formatLogs; - this.closeAfter = closeAfter; - this.createdAt = firstMessage.createdTimestamp; - this.createdBy = firstMessage.author.id; + this.formatLogs = parameters.formatLogs; + this.closeAfter = parameters.closeAfter; + this.createdAt = parameters.firstMessage.createdTimestamp; + this.createdBy = parameters.firstMessage.author.id; - this.addMessage(firstMessage); + this.addMessage(parameters.firstMessage, parameters.shouldFormatLog); } /** @@ -169,8 +179,8 @@ export class Ticket { ? this.generateDescription( messageOrText, isSentToAdmin || manager.options.loggingOptions.showName - ? null - : `${messageOrText.member.user.username}:\n`, + ? `${messageOrText.author.username}:\n` + : null, `\n\n**${manager.options.replyMessage}**` ) : this.generateDescription(messageOrText); diff --git a/src/types/TicketNamedParameter.ts b/src/types/TicketNamedParameter.ts new file mode 100644 index 0000000..d39ed11 --- /dev/null +++ b/src/types/TicketNamedParameter.ts @@ -0,0 +1,8 @@ +import { Message } from 'discord.js'; + +export interface TicketNamedParameter { + firstMessage: Message; + formatLogs?: (message: Message) => string; + closeAfter?: number; + shouldFormatLog?: boolean; +} From 8c016c214a0867997da3a34275ce4f6ce661e26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tina=C3=ABl=20DEVRESSE?= Date: Sun, 17 Oct 2021 02:06:13 +0200 Subject: [PATCH 3/3] docs: specify that djs13 requires upgrade of node --- README.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ae9e1c..bdf2a06 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,23 @@ Discord Mailbox is a framework to easily add a mailbox inside your bot. The feat - Allow full customization of the embed (you can add image, thumbnail, etc) - And much more! +![IMAGE](./assets/example.gif) + +## Prerequisites āš ļø + +Starting at **v2.0.0**, you must use **NodeJS v16.6.0 or higher** to run a bot with this library. + +You also must not forget to include [mandatory intents and partials](#mandatory-intents-and-partials) as well as give your bot the rights to read messages. + +### Mandatory intents and partials + +- GUILDS: used to access guild content such as channels. +- GUILD_MESSAGES: used to read guild messages. +- GUILD_MESSAGE_REACTIONS: used to access guild messages reactions. +- DIRECT_MESSAGES: used to access direct messages to the bot. +- CHANNEL: used to receive events when the bot is DMed. +- MESSAGE: used to read the messages even if incomplete. + ## Installation ```sh @@ -20,8 +37,6 @@ npm install --save @hunteroi/discord-mailbox See [./example/index.js](example/index.js). -![IMAGE](assets/example.gif) - ## Events ```ts @@ -61,4 +76,4 @@ Contributions are what make the open source community such an amazing place to b ## Todo - auto reply when a ticket is opened -- warn when a ticket is closed/force closed +- support thread