diff --git a/classes/ContentValidator.js b/classes/ContentValidator.js index dae734f..269541d 100644 --- a/classes/ContentValidator.js +++ b/classes/ContentValidator.js @@ -1,46 +1,63 @@ -const {Message} = require('discord.js') +const {Message, MessageAttachment} = require('discord.js') const Tesseract = require('tesseract.js') const axios = require('axios'); -const {responses, urls, images} = require('../config') -const urlExpresion = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:\/?#[\]@!\$&'\(\)\*\+,;=.]+$/mg +const {urls, images} = require('../config') +const RegexParser = require("regex-parser"); +const urlExpression = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:\/?#[\]@!\$&'\(\)\*\+,;=.]+$/mg class ContentValidator { /** + * @param {array} responses + */ + constructor(responses) { + this.responses = responses.map(response => { + return { + key : RegexParser(response.key), + content : response.content + } + }) + } + + /** + * @description determine the content of the message * @param {Message} message * @return {Promise<{key: RegExp, content: string}|null>} */ validateContent(message) { return new Promise(async (resolve, reject) => { - //check if content is hastebin url + //check if message is url if (urls.allowed_urls.findIndex(url => message.content.startsWith(url)) !== -1) { - if (message.content.match(urlExpresion)) { + if (message.content.match(urlExpression)) { let text = await this.parseUrl(message).catch(reject); - return resolve(this.parseMessage(text)); + return resolve(this.checkMatches(text)); } } - //check if content has image + //check if message has image if (message.attachments.size > 0) { let attachment = message.attachments.first() if (attachment.size < images.max_size_in_bytes && this.attachIsImage(attachment)) { let text = await this.parseImage(message).catch(reject) - return resolve(this.parseMessage(text)); + return resolve(this.checkMatches(text)); } } - return resolve(this.parseMessage(message.content)) + //treat message as regular text message + return resolve(this.checkMatches(message.content)) }) - - } + /** + * @description check if message attachment is an image + * @param {MessageAttachment} msgAttach + */ attachIsImage(msgAttach) { let url = msgAttach.url; return url.indexOf("png", url.length - "png".length /*or 3*/) !== -1; } - /** + * @description parse image to text, using tesseract * @param {Message} message * @return {Promise} */ @@ -48,16 +65,15 @@ class ContentValidator { let attachment = message.attachments.first() return new Promise(async (resolve, reject) => { + console.log('[PARSER] parsing image to text.') await message.react(images.message_reaction) - Tesseract.recognize(attachment.url, images.parse_language) + await Tesseract.recognize(attachment.url, images.parse_language) .then(async ({data: {text}}) => { await message.reactions.removeAll() return resolve(text) }) .catch(reject) - - }) } @@ -67,6 +83,7 @@ class ContentValidator { * @return {Promise} */ parseUrl(message) { + console.log('[PARSER] parsing url to text.') return new Promise(async (resolve, reject) => { let response = await axios.get(message.content, { maxContentLength: urls.max_content_size_in_bytes @@ -79,15 +96,23 @@ class ContentValidator { } /** - * @description search for a match, return the response or null + * @description search for a match in the provided text, return the response or null * @param {string} text * @return {{key: RegExp, content: string}|null} */ - parseMessage(text) { - let index = responses.findIndex(response => text.match(response.key)) - return index !== -1 ? responses[index] : null; + checkMatches(text) { + console.log('[PARSER] text: \n' + text) + let index = this.responses.findIndex(response => text.match(response.key)) + + if (index !== -1) { + console.log('[PARSER] match found!') + return this.responses[index]; + } else { + console.log('[PARSER] no match found.') + return null; + } } } -module.exports = new ContentValidator() +module.exports = ContentValidator; diff --git a/config.example.js b/config.example.js index 65d343c..c15bb11 100644 --- a/config.example.js +++ b/config.example.js @@ -1,7 +1,9 @@ module.exports = { token: 'YOUR_BOT_TOKEN', - bot: { // https://discordjs.guide/popular-topics/faq.html#how-do-i-check-if-a-guild-member-has-a-specific-role + // check discord.js docs on valid bot properties + // https://discordjs.guide/popular-topics/faq.html#how-do-i-check-if-a-guild-member-has-a-specific-role + bot: { activity: 'PLAYING', activity_message: 'Smart Support', activity_status: 'online', @@ -15,7 +17,8 @@ module.exports = { //configured allowed urls urls: { allowed_urls: [ - 'https://pastebin.com' + 'https://pastebin.com', + 'https://termbin.com' ], max_content_size_in_bytes: 314572, //0.3mb }, @@ -26,12 +29,4 @@ module.exports = { parse_language: 'eng', message_reaction: '👀' }, - - //all keys have to be valid regex - responses: [ - { - key: /^(?=.*hello)(?=.*world).*$/mgi, - content: `Hello World!` - }, - ] } diff --git a/index.js b/index.js index e35237f..7826548 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,10 @@ const {Client, Intents} = require('discord.js'); -const {token, bot, responses, support_channels} = require('./config'); -const respond = require('./respond') -const contentValidator = require('./classes/ContentValidator') +const {token, bot, support_channels} = require('./config'); +const responses = require('./responses.json') +const ContentValidator = require('./classes/ContentValidator') // Create a new client instance +const contentValidator = new ContentValidator(responses); const client = new Client({intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES]}); // When the client is ready, run this code (only once) @@ -12,19 +13,19 @@ client.once('ready', async () => { await client.user.setStatus(bot.activity_status); console.log(`Found '${responses.length}' responses!`) - console.log('Ready!'); + console.log('Smart-Support-bot is ready! \n'); }); client.on('messageCreate', async (message) => { if (message.author.bot) return; if (!support_channels.includes(message.channelId)) return; + let response = await contentValidator.validateContent(message) + if (!response) return; - if (response) { - respond(message, response) - .then(message => console.log(`responded to '${message.attachments.size > 0 ? 'image' : message.content}'`)) - .catch(message => console.log(`failed to respond to '${message.attachments.size > 0 ? 'image' : message.content}'`)) - } + message.reply(response) + .then(message => console.log(`[BOT] responded to '${message.attachments.size > 0 ? 'image' : message.content}'`)) + .catch(message => console.log(`[BOT] failed to respond to '${message.attachments.size > 0 ? 'image' : message.content}'`)) }) diff --git a/package-lock.json b/package-lock.json index 0718fdc..14545df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "dotenv": "^10.0.0", "nanoid": "^3.1.29", "node-cache": "^5.1.2", + "regex-parser": "^2.2.11", "tesseract.js": "^2.1.5" } }, @@ -568,6 +569,11 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" + }, "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -1097,6 +1103,11 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, + "regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", diff --git a/package.json b/package.json index ebf40ad..51608f7 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dotenv": "^10.0.0", "nanoid": "^3.1.29", "node-cache": "^5.1.2", + "regex-parser": "^2.2.11", "tesseract.js": "^2.1.5" } } diff --git a/readme.md b/readme.md index a3b87c4..c523a68 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,6 @@ -# Smart support bot -A simple discord chat bot with an easy to use configuration. Written using discord.js +# Automated support bot +A simple discord chatbot with an easy-to-use configuration. Written using discord.js + reads text from: - text diff --git a/respond.js b/respond.js deleted file mode 100644 index e1d0494..0000000 --- a/respond.js +++ /dev/null @@ -1,12 +0,0 @@ -const {Message} = require('discord.js') - -/** - * @param {Message} message - * @param response - */ -module.exports = (message, response) => { - return new Promise(async (resolve, reject) => { - await message.reply(response.content).catch(reject) - resolve(message) - }) -} diff --git a/responses.json b/responses.json new file mode 100644 index 0000000..2f127c1 --- /dev/null +++ b/responses.json @@ -0,0 +1,6 @@ +[ + { + "key": "/^(?=.*hello)(?=.*world).*$/mgi", + "content": "Hello World!" + } +] \ No newline at end of file