const { GuildMember, TextChannel, Message, User, MessageEmbed, RoleResolvable, Guild } = require('discord.js');
const winston = require('winston');
const BotGuild = require('./db/mongo/BotGuild');
/**
* The discord services module has useful discord related functions.
* These functions are helper, discord related functions.
* @module DiscordServices
*/
/**
* Checks if the member has a role, returns true if it does
* @param {GuildMember} member - member to check role
* @param {String} role - role ID to check for
*/
function checkForRole(member, role) {
winston.loggers.get(member.guild.id).verbose(`A role check was requested. Role ID: ${role}. Member ID: ${member.id}`);
return member.roles.cache.has(role);
}
module.exports.checkForRole = checkForRole;
/**
* Will send a message to a text channel and ping the user, can be deleted after a timeout.
* @param {TextChannel} channel - the channel to send the message to
* @param {String} userId - the user to tag on the message
* @param {String} message - the message to send
* @param {Number} timeout - timeout before delete if any, in seconds
* @async
* @returns {Promise<Message>}
*/
async function sendMsgToChannel(channel, userId, message, timeout = 0) {
let msg = await channel.send('<@' + userId + '> ' + message);
if (timeout) msg.delete({timeout: timeout * 1000}); // convert to milliseconds
winston.loggers.get(channel.guild.id).verbose(`A message has been sent to the channel ${channel.name} for the user with id ${userId} ${timeout === 0 ? 'with no timeout requested' : 'with a ' + timeout + ' second timeout.'}`);
return msg;
}
module.exports.sendMsgToChannel = sendMsgToChannel;
/**
* Send a Direct message to a member, option to delete after a few seconds.
* Helps user fix DM issue if the bot can't reach them over DM.
* @param {User | GuildMember} member - the user or member to send a DM to
* @param {String | MessageEmbed} message - the message to send
* @param {Boolean} isDelete - weather to delete message after 60 seconds
* @async
* @return {Promise<Message>}
*/
async function sendMessageToMember(member, message, isDelete = false) {
return await member.send(message).then(msg => {
winston.loggers.get(member?.guild?.id || 'main').verbose(`A DM message was sent to user with id ${member.id}.`);
if (isDelete === true) {
msg.delete({timeout: 60000});
}
return msg;
}).catch(async error => {
if (error.code === 50007) {
winston.loggers.get(member?.guild?.id || 'main').warning(`A DM message was sent to user with id ${member.id} but failed, he has been asked to fix this problem!`);
let botGuild;
if (member?.guild) botGuild = await BotGuild.findById(member.guild.id);
else {
winston.loggers.get(member.guild.id).error('While trying to help a user to get my DMs I could not find a botGuild for which this member is in. I could not help him!');
throw Error(`I could not help ${member.id} due to not finding the guild he is trying to access. I need a member and not a user!`);
}
member.guild.channels.resolve(botGuild.channelIDs.botSupportChannel).send('<@' + member.id + '> I couldn\'t reach you :(. Please turn on server DMs, explained in this link: https://support.discord.com/hc/en-us/articles/217916488-Blocking-Privacy-Settings-');
} else {
throw error;
}
});
}
module.exports.sendMessageToMember = sendMessageToMember;
/**
* @typedef FieldInfo
* @property {String} title - field title
* @property {String} description - field description
*/
/**
* @typedef EmbedOptions
* @property {String} title - embed title
* @property {String} description - embed description
* @property {String} color - embed color
* @property {Array<FieldInfo>} fields - embed fields
*/
/**
* Sends an embed to a user via DM. Title and description are required, color and fields are optional.
* @param {User | GuildMember} member - member to send embed to
* @param {EmbedOptions} embedOptions - embed information
* @param {Boolean} isDelete - should the message be deleted after some time?
* @async
* @returns {Promise<Message>}
*/
async function sendEmbedToMember(member, embedOptions, isDelete = false) {
// check embedOptions
if (embedOptions?.title === undefined || embedOptions?.title === '') throw new Error('A title is needed for the embed!');
if (embedOptions?.description === undefined || embedOptions?.description === '') throw new Error('A description is needed for the embed!');
if (embedOptions?.color === undefined || embedOptions?.color === '') embedOptions.color === '#ff0000';
let embed = new MessageEmbed().setColor(embedOptions.color)
.setTitle(embedOptions.title)
.setDescription(embedOptions.description)
.setTimestamp();
if (embedOptions?.fields) embedOptions.fields.forEach((fieldInfo, index) => embed.addField(fieldInfo.title, fieldInfo.description));
return sendMessageToMember(member, embed, isDelete);
}
module.exports.sendEmbedToMember = sendEmbedToMember;
/**
* Add a role to a member
* @param {GuildMember} member - the guild member to give a role to
* @param {RoleResolvable} addRole - the role to add to the member
*/
function addRoleToMember(member, addRole) {
if (!member?.guild) throw Error('I need a member not a user!!!');
let role = member.guild.roles.resolve(addRole);
member.roles.add(addRole).catch(error => {
// try one more time
member.roles.add(addRole).catch(error => {
// now send error to admins
discordLog(member.guild, '@everyone The member <@' + member.id + '> did not get the role <@&' + role.id +'> please help me!');
winston.loggers.get(member.guild.id).error(`Could not give the member with id ${member.id} the role ${role.name} with id ${role.id}. The following error ocurred: ${error.name} - ${error.message}.`, { event: 'Error', data: error });
});
});
winston.loggers.get(member.guild.id).verbose(`A member with id ${member.id} was given the role ${role.name} with id ${role.id}`);
}
module.exports.addRoleToMember = addRoleToMember;
/**
* Remove a role to a member
* @param {GuildMember} member - the guild member to give a role to
* @param {RoleResolvable} removeRole - the role to add to the member
*/
function removeRolToMember(member, removeRole) {
let role = member.guild.roles.resolve(removeRole);
member.roles.remove(removeRole).catch(error => {
// try one more time
member.roles.remove(removeRole).catch(error => {
// now send error to admins
discordLog(member.guild, '@everyone The member <@' + member.user.id + '> did not loose the role ' + member.guild.roles.cache.get(removeRole).id + ', please help me!');
winston.loggers.get(member.guild.id).error(`Could not remove the member with id ${member.id} the role ${role.name} with id ${role.id}. The following error ocurred: ${error.name} - ${error.message}.`);
});
});
winston.loggers.get(member.guild.id).verbose(`A member with id ${member.id} lost the role ${role.name} with id ${role.id}`);
}
module.exports.removeRolToMember = removeRolToMember;
/**
* Replaces one role for the other
* @param {GuildMember} member - member to change roles to
* @param {RoleResolvable} removeRole - role to remove
* @param {RoleResolvable} addRole - role to add
*/
function replaceRoleToMember(member, removeRole, addRole) {
addRoleToMember(member, addRole);
removeRolToMember(member, removeRole);
}
module.exports.replaceRoleToMember = replaceRoleToMember;
/**
* Log a message on the log channel
* @param {Guild} guild - the guild being used
* @param {String | MessageEmbed} message - message to send to the log channel
* @async
*/
async function discordLog(guild, message) {
let botGuild = await BotGuild.findById(guild.id);
if (botGuild?.channelIDs?.adminLog) {
guild.channels.cache.get(botGuild.channelIDs.adminLog)?.send(message);
winston.loggers.get(guild.id).silly(`The following was logged to discord: ${message}`);
}
else winston.loggers.get(guild.id).error('I was not able to log something to discord!! I could not find the botGuild or the adminLog channel!');
}
module.exports.discordLog = discordLog;
/**
* Reply to message and delete 5 seconds later
* @param {Message} message - the message to reply to
* @param {String} reply - the string to reply
*/
async function replyAndDelete(message, reply) {
var msg = await message.reply(reply);
msg.delete({timeout: 5000});
winston.loggers.get(message?.guild.id || 'main').verbose(`A message with id ${message.id} is being replied to and then the reply is being deleted.`);
}
module.exports.replyAndDelete = replyAndDelete;
/**
* Deletes a message if the message hasn't been deleted already
* @param {Message} message - the message to delete
* @param {Number} timeout - the time to wait in milliseconds
* @async
*/
async function deleteMessage(message, timeout = 0) {
if (!message.deleted && message.deletable && message.channel.type != 'dm') {
winston.loggers.get(message.guild.id).verbose(`A message with id ${message.id} in the guild channel ${message.channel.name} with id ${message.channel.id} was deleted.`);
await message.delete({timeout: timeout});
} else if (message.channel.type === 'dm' && message.author.bot) {
winston.loggers.get('main').verbose(`A message with id ${message.id} in a DM channel with user id ${message.channel.recipient.id} from the bot was deleted.`);
await message.delete({timeout: timeout});
} else {
winston.loggers.get(message?.guild.id | 'main').warning(`A message with id ${message.id} in a DM channel from user with id ${message.author.id} tried to be deleted but was not possible.`);
}
}
module.exports.deleteMessage = deleteMessage;
/**
* Delete the given channel if it is not deleted already
* @param {TextChannel} channel
*/
async function deleteChannel(channel) {
if (!channel.deleted && channel.deletable) {
winston.loggers.get(channel.guild.id).verbose(`The channel ${channel.name} with id ${channel.id} was deleted.`);
await channel.delete();
} else {
winston.loggers.get(channel.guild.id).warning(`The channel ${channel?.name} with id ${channel?.id} tried to be deleted but was not possible!`);
}
}
module.exports.deleteChannel = deleteChannel;
/**
* Returns a random color as a hex string.
* @returns {String} - hex color
*/
function randomColor() {
winston.loggers.get('main').silly('A random color has been used!');
return Math.floor(Math.random()*16777215).toString(16);
}
module.exports.randomColor = randomColor;
/**
* Validates an email using a reg exp.
* @param {String} email - the email to validate
* @returns {Boolean} true if valid email, false otherwise
*/
function validateEmail(email) {
winston.loggers.get('main').silly('An email has been validated!');
// make email lowercase
email = email.toLowerCase();
// regex to validate email
const re = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i;
// let user know he has used the command incorrectly and exit
if (email === '' || !re.test(email)) {
return false;
} else {
return true;
}
}
module.exports.validateEmail = validateEmail;
/**
* will shuffle an array as best and fast as possible
* @param {Array<*>} array - array to shuffle
* @private
*/
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
module.exports.shuffleArray = shuffleArray;
Source