diff --git a/commands.js b/src/commands.js similarity index 79% rename from commands.js rename to src/commands.js index a3244b9..c1848d6 100644 --- a/commands.js +++ b/src/commands.js @@ -8,11 +8,12 @@ const cmds = new Map(); cmds.set('member', { description: enums.help.SHORT_DESC_MEMBER, async execute(message, client, args) { - const reply = await memberHelper.parseMemberCommand(message.author, args, message.attachments[0] = null).catch(e => message.reply(e)); + const authorFull = `${message.author.username}#${message.author.discriminator}` + const reply = await memberHelper.parseMemberCommand(message.author.id, authorFull, args, message.attachments[0] = null).catch(e => throw e); if (typeof reply === 'string') { - await message.reply(reply); + return await message.reply(reply); } - await message.reply({embeds: [embed.toJSON()]}) + await message.reply({embeds: [reply.toJSON()]}) } }) diff --git a/helpers/memberHelper.js b/src/helpers/memberHelper.js similarity index 75% rename from helpers/memberHelper.js rename to src/helpers/memberHelper.js index d67df24..2e83cee 100644 --- a/helpers/memberHelper.js +++ b/src/helpers/memberHelper.js @@ -29,9 +29,9 @@ mh.parseMemberCommand = async function(author, args, attachmentUrl){ case '--help': return enums.help.MEMBER; case 'add': - return await addNewMember(authorId, args); + return await mh.addNewMember(authorId, args).catch((e) => throw e); case 'remove': - return await removeMember(authorId, args); + return await mh.removeMember(authorId, args).catch((e) => throw e); case 'name': return enums.help.NAME; case 'displayname': @@ -40,22 +40,20 @@ mh.parseMemberCommand = async function(author, args, attachmentUrl){ return enums.help.PROXY; case 'propic': return enums.help.PROPIC; - case 'listall': - return await getAllMembersInfo(authorId, authorFull); + case 'list': + return await mh.getAllMembersInfo(authorId, authorFull).catch((e) => throw e); case '': return enums.help.MEMBER; } switch(args[1]) { - case '--help': - return enums.help.MEMBER; case 'name': - return await updateName(authorId, args); + return await mh.updateName(authorId, args).catch((e) => throw e); case 'displayname': - return await updateDisplayName(authorId, args); + return await mh.updateDisplayName(authorId, args).catch((e) => throw e); case 'proxy': - return await updateProxy(authorId, args); + return await mh.updateProxy(authorId, args).catch((e) => throw e); case 'propic': - return await updatePropic(authorId, args, attachmentUrl) + return await mh.updatePropic(authorId, args, attachmentUrl).catch((e) => throw e); default: return member; } @@ -64,21 +62,22 @@ mh.parseMemberCommand = async function(author, args, attachmentUrl){ /** * Adds a member. * + * @async * @param {string} authorId - The author of the message * @param {string[]} args - The message arguments * @returns {Promise} A successful addition. * @throws {Error} When the member exists, or creating a member doesn't work. */ -async function addNewMember(authorId, args) { +mh.addNewMember = async function(authorId, args) { if (args[1] && args[1] === "--help" || !args[1]) { return enums.help.ADD; } const memberName = args[1]; const displayName = args[2]; - return await mh.addFullMember(authorId, memberName, displayName).then((m) => { - let success = `Member was successfully added.\nName: ${m.dataValues.name}` - success += displayName ? `\nDisplay name: ${m.dataValues.displayname}` : ""; + return await mh.addFullMember(authorId, memberName, displayName).then((member) => { + let success = `Member was successfully added.\nName: ${member.dataValues.name}` + success += displayName ? `\nDisplay name: ${member.dataValues.displayname}` : ""; return success; }).catch(e => { throw new Error(`${enums.err.ADD_ERROR}: ${e.message}`) @@ -88,12 +87,13 @@ async function addNewMember(authorId, args) { /** * Updates the name for a member. * + * @async * @param {string} authorId - The author of the message * @param {string[]} args - The message arguments * @returns {Promise} A successful update. * @throws {RangeError} When the name doesn't exist. */ -async function updateName(authorId, args) { +mh.updateName = async function (authorId, args) { if (args[1] && args[1] === "--help" || !args[1]) { return enums.help.DISPLAY_NAME; } @@ -103,18 +103,19 @@ async function updateName(authorId, args) { if (!name || trimmedName === null) { throw new RangeError(`Display name ${enums.err.NO_VALUE}`); } - return updateMemberField(authorId, args); + return await mh.updateMemberField(authorId, args).catch((e) => throw e); } /** * Updates the display name for a member. * + * @async * @param {string} authorId - The author of the message * @param {string[]} args - The message arguments * @returns {Promise} A successful update. * @throws {RangeError} When the display name is too long or doesn't exist. */ -async function updateDisplayName(authorId, args) { +mh.updateDisplayName = async function(authorId, args) { if (args[1] && args[1] === "--help" || !args[1]) { return enums.help.DISPLAY_NAME; } @@ -124,33 +125,38 @@ async function updateDisplayName(authorId, args) { const trimmedName = displayName ? displayName.trim() : null; if (!displayName || trimmedName === null ) { - let member = await mh.getMemberByName(authorId, memberName); - if (member.displayname) { - return `Display name for ${memberName} is: \"${member.displayname}\".`; - } - throw new RangeError(`Display name ${enums.err.NO_VALUE}`); + return await mh.getMemberByName(authorId, memberName).then((member) => { + if (member.displayname) { + return `Display name for ${memberName} is: \"${member.displayname}\".`; + } + throw new RangeError(`Display name ${enums.err.NO_VALUE}`); + }).catch((e) => throw e); + } else if (displayName.length > 32) { throw new RangeError(enums.err.DISPLAY_NAME_TOO_LONG); } - return updateMemberField(authorId, args); + return await mh.updateMemberField(authorId, args).catch((e) => throw e); } /** * Updates the proxy for a member, first checking that no other members attached to the author have the tag. * + * @async * @param {string} authorId - The author of the message * @param {string[]} args - The message arguments * @returns {Promise } A successful update. * @throws {RangeError | Error} When an empty proxy was provided, or no proxy exists. */ -async function updateProxy(authorId, args) { +mh.updateProxy = async function(authorId, args) { if (args[1] && args[1] === "--help" || !args[1]) { return enums.help.PROXY; } - const proxyExists = await checkIfProxyExists(authorId, proxy); + const proxyExists = await mh.checkIfProxyExists(authorId, args[2]).then((proxyExists) => { + return proxyExists; + }).catch((e) => throw e); if (!proxyExists) { - return updateMemberField(authorId, args); + return await mh.updateMemberField(authorId, args).catch((e) => throw e); } } @@ -162,7 +168,7 @@ async function updateProxy(authorId, args) { * @returns {Promise } Whether the proxy exists. * @throws {Error} When an empty proxy was provided, or no proxy exists. */ -async function checkIfProxyExists(authorId, proxy) { +mh.checkIfProxyExists = async function(authorId, proxy) { const trimmedProxy = proxy ? proxy.trim() : null; if (trimmedProxy == null) throw new RangeError(`Proxy ${enums.err.NO_VALUE}`); @@ -170,23 +176,26 @@ async function checkIfProxyExists(authorId, proxy) { if(splitProxy.length < 2) throw new Error(enums.err.NO_TEXT_FOR_PROXY); if(!splitProxy[0] && !splitProxy[1]) throw new Error(enums.err.NO_PROXY_WRAPPER); - const members = await mh.getMembersByAuthor(authorId); - const proxyExists = members.some(member => member.proxy === proxy); - if (proxyExists) { - throw new Error(enums.err.PROXY_EXISTS); - } - return false; + const members = await mh.getMembersByAuthor(authorId).then(() => { + const proxyExists = members.some(member => member.proxy === proxy); + if (proxyExists) { + throw new Error(enums.err.PROXY_EXISTS); + } + return false; + }).catch(e => throw e); + } /** * Updates the profile pic for a member, based on either the attachment or the args provided. * + * @async * @param {string} authorId - The author of the message * @param {string[]} args - The message arguments * @param {string} attachment - The url of the first attachment in the message * @returns {Promise} A successful update. * @throws {Error} When loading the profile picture from a URL doesn't work. */ -async function updatePropic(authorId, args, attachment) { +mh.updatePropic = async function(authorId, args, attachment) { if (args[1] && args[1] === "--help") { return enums.help.PROPIC; } @@ -202,22 +211,26 @@ async function updatePropic(authorId, args, attachment) { img = updatedArgs[2]; } - return await loadImage(img).then(() => { - return updateMemberField(authorId, updatedArgs); + const loadedImage = await loadImage(img).then((li) => { + return li; }).catch((err) => { throw new Error(`${enums.err.PROPIC_CANNOT_LOAD}: ${err.message}`); }); + if (loadedImage) { + return await mh.updateMemberField(authorId, updatedArgs).catch((e) => throw e); + } } /** * Removes a member. * + * @async * @param {string} authorId - The author of the message * @param {string[]} args - The message arguments * @returns {Promise} A successful removal. * @throws {EmptyResultError} When there is no member to remove. */ -async function removeMember(authorId, args) { +mh.removeMember = async function(authorId, args) { if (args[1] && args[1] === "--help" || !args[1]) { return enums.help.REMOVE; } @@ -235,15 +248,16 @@ async function removeMember(authorId, args) { /** * Adds a member with full details, first checking that there is no member of that name associated with the author. * + * @async * @param {string} authorId - The author of the message * @param {string} memberName - The name of the member. * @param {string} displayName - The display name of the member. - * @param {string} proxy - The proxy tag of the member. - * @param {string} propic - The profile picture URL of the member. + * @param {string | null} proxy - The proxy tag of the member. + * @param {string | null} propic - The profile picture URL of the member. * @returns {Promise} A successful addition. * @throws {Error | RangeError} When the member already exists, there are validation errors, or adding a member doesn't work. */ -mh.addFullMember = async function(authorId, memberName, displayName, proxy, propic) { +mh.addFullMember = async function(authorId, memberName, displayName, proxy = null, propic= null) { const member = await mh.getMemberByName(authorId, memberName); if (member) { throw new Error(`Can't add ${memberName}. ${enums.err.MEMBER_EXISTS}`); @@ -274,12 +288,13 @@ mh.addFullMember = async function(authorId, memberName, displayName, proxy, prop /** * Updates one fields for a member in the database. * + * @async * @param {string} authorId - The author of the message * @param {string[]} args - The message arguments * @returns {Promise} A successful update. * @throws {EmptyResultError | Error} When the member is not found, or catchall error. */ -async function updateMemberField(authorId, args) { +mh.updateMemberField = async function(authorId, args) { const memberName = args[0]; const columnName = args[1]; const value = args[2]; @@ -288,7 +303,7 @@ async function updateMemberField(authorId, args) { // indicates that an attachment was uploaded on Fluxer directly if (columnName === "propic" && args[3]) { console.log(args); - fluxerPropicWarning = setExpirationWarning(args[3]); + fluxerPropicWarning = mh.setExpirationWarning(args[3]); } return await db.members.update({[columnName]: value}, { where: { name: memberName, userid: authorId } }).then(() => { return `Updated ${columnName} for ${memberName} to "${value}"${fluxerPropicWarning ?? ''}.`; @@ -308,7 +323,7 @@ async function updateMemberField(authorId, args) { * @param {string} expirationString - An expiration date string. * @returns {string} A description of the expiration, interpolating the expiration string. */ -function setExpirationWarning(expirationString) { +mh.setExpirationWarning = async function(expirationString) { let expirationDate = new Date(expirationString); if (!isNaN(expirationDate.valueOf())) { expirationDate = expirationDate.toDateString(); @@ -319,34 +334,37 @@ function setExpirationWarning(expirationString) { /** * Gets the details for a member. * + * @async * @param {string} authorId - The author of the message * @param {string} memberName - The message arguments * @returns {Promise} The member's info. - * @throws { EmptyResultError } When the member is not found. + * @throws {Error} */ -async function getMemberInfo(authorId, memberName) { - let member = mh.getMemberByName(authorId, memberName); - let member_info = new EmbedBuilder() - .setTitle(memberName) - .setDescription(`Details for ${memberName}`) - .addFields( - {name: 'Display name', value: member.displayname, inline:true}, - {name: 'Proxy tag', value: member.proxy, inline:true}, - ) - .setImage(member.propic); - return member_info; +mh.getMemberInfo = async function(authorId, memberName) { + return await mh.getMemberByName(authorId, memberName).then((member) => { + return new EmbedBuilder() + .setTitle(memberName) + .setDescription(`Details for ${memberName}`) + .addFields( + {name: 'Display name: ', value: member.displayname ?? 'unset', inline: true}, + {name: 'Proxy tag: ', value: member.proxy ?? 'unset', inline: true}, + ) + .setImage(member.propic); + }).catch((e) => throw e) } /** * Gets all members for an author. * + * @async * @param {string} authorId - The id of the message author * @param {string} authorName - The id name the message author * @returns {Promise} The info for all members. + * @throws {Error} */ -async function getAllMembersInfo(authorId, authorName) { - const members = await mh.getMembersByAuthor(authorId); - const fields = [...members.entries()].map(([name, member]) => ({ +mh.getAllMembersInfo = async function(authorId, authorName) { + const members = await mh.getMembersByAuthor(authorId).catch(e => throw e); + const fields = [...members.entries()].map(([member]) => ({ name: member.name, value: `(Proxy: \`${member.proxy}\`)`, inline: true, @@ -359,6 +377,7 @@ async function getAllMembersInfo(authorId, authorName) { /** * Gets a member based on the author and proxy tag. * + * @async * @param {string} authorId - The author of the message. * @param {string} memberName - The member's name. * @returns {Promise} The member object. @@ -373,6 +392,7 @@ mh.getMemberByName = async function(authorId, memberName) { /** * Gets a member based on the author and proxy tag. * + * @async * @param {string} authorId - The author of the message * @param {string} proxy - The proxy tag * @returns {Promise} The member object. @@ -380,13 +400,14 @@ mh.getMemberByName = async function(authorId, memberName) { */ mh.getMemberByProxy = async function(authorId, proxy) { return await db.members.findOne({ where: { userid: authorId, proxy: proxy } }).catch(e => { - throw new EmptyResultError(`Can't add ${memberName}. ${enums.err.NO_MEMBER}: ${e.message}`); + throw new EmptyResultError(`Can't find member with that proxy. ${enums.err.NO_MEMBER}: ${e.message}`); }); } /** * Gets all members belonging to the author. * + * @async * @param {string} authorId - The author of the message * @returns {Promise} The member object array. * @throws { EmptyResultError } When no members are found. diff --git a/helpers/messageHelper.js b/src/helpers/messageHelper.js similarity index 98% rename from helpers/messageHelper.js rename to src/helpers/messageHelper.js index e5a23ed..c8363e2 100644 --- a/helpers/messageHelper.js +++ b/src/helpers/messageHelper.js @@ -39,7 +39,7 @@ msgh.parseCommandArgs = function(content, commandName) { * @returns {Object} The proxy message object. */ msgh.parseProxyTags = async function (authorId, attachment, content){ - const members = await memberHelper.getMembersByAuthor(authorId); + const members = await memberHelper.getMembersByAuthor(authorId).catch(e => throw e); const proxyMessage = {} members.filter(member => member.proxy).forEach(member => { diff --git a/helpers/webhookHelper.js b/src/helpers/webhookHelper.js similarity index 75% rename from helpers/webhookHelper.js rename to src/helpers/webhookHelper.js index 6eab103..a6e10ed 100644 --- a/helpers/webhookHelper.js +++ b/src/helpers/webhookHelper.js @@ -7,6 +7,62 @@ const wh = {}; const name = 'PluralFlux Proxy Webhook'; +/** + * Replaces a proxied message with a webhook using the member information. + * @param {Client} client - The fluxer.js client. + * @param {Message} message - The full message object. + * @param {string} content - The full content of the message. + * @throws {Error} When the proxy message is not in a server. + */ +wh.sendMessageAsMember = async function(client, message, content) { + const proxyMatch = await messageHelper.parseProxyTags(message.author.id, message.attachments[0] ?? null, content).catch(e => throw e); + // If the message doesn't match a proxy, just return. + if (!proxyMatch.proxy) { + return; + } + // If the message does match a proxy but is in a guild server + if (!message.guildId) { + throw new Error(enums.err.NOT_IN_SERVER); + } + const member = await memberHelper.getMemberByProxy(message.author.id, proxyMatch.proxy).catch(e => throw e); + await replaceMessage(client, message, message.channelId, proxyMatch.message, member).catch(e => throw e); +} + +/** + * Replaces a proxied message with a webhook using the member information. + * @param {Client} client - The fluxer.js client. + * @param {Message} message - The message to be deleted. + * @param {string} channelId - The channel id to send the webhook message in. + * @param {string} text - The text to send via the webhook. + * @param {model} member - A member object from the database. + * @throws {Error} When there's no message to send. + */ +async function replaceMessage(client, message, channelId, text, member) { + if (text.length > 0) { + const channel = client.channels.get(channelId); + const webhook = await getOrCreateWebhook(client, channel).catch((e) => throw e); + const username = member.displayname ?? member.name; + await webhook.send({content: text, username: username, avatar_url: member.propic}); + await message.delete(); + } + else { + throw new Error(enums.err.NO_MESSAGE_SENT_WITH_PROXY); + } +} + +// /** +// * Fluxer collapses same-name messages, so if two would be sent by different users, break them up with a tiny space. (Inspired by Tupperbox) +// * @param {string} channelId - The channel id to send the webhook message in. +// * @param {string} username - The text to send via the webhook. +// */ +// +// function preventSameNameCollapse(channelId, username) { +// +// if(bot.recent[msg.channel.id] && msg.author.id !== bot.recent[msg.channel.id][0].user_id && un === bot.recent[msg.channel.id][0].name) { +// username = un.substring(0,1) + "\u200a" + un.substring(1); +// } +// } + /** * Gets or creates a webhook. * @@ -16,8 +72,9 @@ const name = 'PluralFlux Proxy Webhook'; * @throws {Error} When no webhooks are allowed in the channel. */ async function getOrCreateWebhook(client, channel) { + // If channel doesn't allow webhooks if (!channel?.createWebhook) throw new Error(enums.err.NO_WEBHOOKS_ALLOWED); - let webhook = await getWebhook(client, channel); + let webhook = await getWebhook(client, channel).catch((e) => throw e); if (!webhook) { webhook = await channel.createWebhook({name: name}); } @@ -45,60 +102,4 @@ async function getWebhook(client, channel) { return pf_webhook; } -/** - * Replaces a proxied message with a webhook using the member information. - * @param {Client} client - The fluxer.js client. - * @param {Message} message - The message to be deleted. - * @param {string} channelId - The channel id to send the webhook message in. - * @param {string} text - The text to send via the webhook. - * @param {model} member - A member object from the database. - * @throws {Error} When there's no message to send. - */ -async function replaceMessage(client, message, channelId, text, member) { - if (text.length > 0) { - const channel = client.channels.get(channelId); - const webhook = await getOrCreateWebhook(client, channel); - const username = member.displayname ?? member.name; - await webhook.send({content: text, username: username, avatar_url: member.propic}); - await message.delete(); - } - else { - throw new Error(enums.err.NO_MESSAGE_SENT_WITH_PROXY); - } -} - -/** - * Fluxer collapses same-name messages, so if two would be sent by different users, break them up with a tiny space. - * @param {string} channelId - The channel id to send the webhook message in. - * @param {string} username - The text to send via the webhook. - */ - -function preventSameNameCollapse(channel, username) { - - if(bot.recent[msg.channel.id] && msg.author.id !== bot.recent[msg.channel.id][0].user_id && un === bot.recent[msg.channel.id][0].name) { - username = un.substring(0,1) + "\u200a" + un.substring(1); - } -} - -/** - * Replaces a proxied message with a webhook using the member information. - * @param {Client} client - The fluxer.js client. - * @param {Message} message - The full message object. - * @param {string} content - The full content of the message. - * @throws {Error} When the proxy message is not in a server. - */ -wh.sendMessageAsMember = async function(client, message, content) { - - const proxyMatch = await messageHelper.parseProxyTags(message.author.id, message.attachments[0] ?? null, content); - // If the message doesn't match a proxy, just return. - if (!proxyMatch.proxy) { - return; - } - if (!message.guildId) { - throw new Error(enums.err.NOT_IN_SERVER); - } - const member = await memberHelper.getMemberByProxy(message.author.id, proxyMatch.proxy); - await replaceMessage(client, message, message.channelId, proxyMatch.message, member); -} - export const webhookHelper = wh; \ No newline at end of file