Add manager Join/Leave/Alternate commands

additionally update deleteConfirmed such that the group up manager name syntax matches name syntax elsewhere in the bot
This commit is contained in:
Ean Milligan (Bastion) 2023-04-21 02:00:36 -04:00
parent 9051792ff3
commit 00a98db405
8 changed files with 215 additions and 15 deletions

View File

@ -8,6 +8,9 @@ const actions = [
'cmd-report',
'cmd-setup',
'cmd-gameSel',
'cmd-join',
'cmd-leave',
'cmd-alternate',
'btn-gameSel',
'btn-customAct',
'btn-createEvt',

View File

@ -10,7 +10,7 @@ export const confirmedCustomId = 'confirmedCustomId';
export const confirmStr = 'yes';
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction?.data?.customId && interaction?.data?.components?.length && interaction.channelId && interaction.guildId && interaction.member) {
if (interaction?.data?.customId && interaction?.data?.components?.length && interaction.channelId && interaction.guildId && interaction.member && interaction.member.user) {
// Light Telemetry
dbClient.execute(queries.callIncCnt('btn-confirmDelEvent')).catch((e) => utils.commonLoggers.dbError('deleteConfirmed.ts@incCnt', 'call sproc INC_CNT on', e));
@ -33,6 +33,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
if (tempDataMap.get(confirmedCustomId)?.toLowerCase() === confirmStr) {
const eventMessage = await bot.helpers.getMessage(evtChannelId, evtMessageId).catch((e: Error) => utils.commonLoggers.messageGetError('deleteConfirmed.ts', 'get eventMessage', e));
const userId = interaction.member.id;
const userName = interaction.member.user.username;
// Delete event
bot.helpers.deleteMessage(evtChannelId, evtMessageId, 'User deleted event').then(() => {
dbClient.execute(queries.deleteEvent, [evtChannelId, evtMessageId]).catch((e) => utils.commonLoggers.dbError('deleteConfirmed.ts@deleteEvent', 'delete event from', e));
@ -56,7 +57,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
embeds: [{
color: infoColor2,
title: `Event deleted by a ${config.name} Manager`,
description: `The following event was deleted by <@${userId}>.`,
description: `The following event was deleted by ${userName} - <@${userId}>.`,
timestamp: new Date().getTime(),
}, eventEmbed],
}).catch((e: Error) => utils.commonLoggers.messageSendError('deleteConfirmed.ts', 'send log message', e));

View File

@ -18,7 +18,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
const memberRequesting = getLfgMembers(interaction.message.embeds[0].fields[0].value || '')[0];
const approved = interaction.data.customId.includes(approveStr);
const responseStr = interaction.data.customId.split(idSeparator)[1] || '';
const capResponseStr = `${responseStr.charAt(0).toUpperCase()}${responseStr.slice(1)}`;
const capResponseStr = utils.capitalizeFirstChar(responseStr);
const eventIds = utils.messageUrlToIds(interaction.message.embeds[0].description.split(')')[0] || '');
const eventUrl = utils.idsToMessageUrl(eventIds);
const joinRequestMapId = generateMapId(eventIds.messageId, eventIds.channelId, memberRequesting.id);

View File

@ -81,7 +81,7 @@ const editEvent = async (
memberList: Array<LFGMember>,
maxMemberCount: number,
alternateList: Array<LFGMember>,
loudAcknowledge = false,
loudAcknowledge: boolean,
) => {
if (evtMessageEmbed.fields) {
// Update the fields
@ -122,7 +122,7 @@ ${safelyDismissMsg}`,
};
// Generic no response response
const noEdit = async (bot: Bot, interaction: Interaction, loudAcknowledge = false) => {
const noEdit = async (bot: Bot, interaction: Interaction, loudAcknowledge: boolean) => {
if (loudAcknowledge) {
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
type: InteractionResponseTypes.ChannelMessageWithSource,
@ -149,7 +149,16 @@ export const getGuildName = async (bot: Bot, guildId: bigint): Promise<string> =
(await bot.helpers.getGuild(guildId).catch((e: Error) => utils.commonLoggers.messageGetError('utils.ts', 'get guild', e)) || { name: 'failed to get guild name' }).name;
// Remove member from the event
export const removeMemberFromEvent = async (bot: Bot, interaction: Interaction, evtMessageEmbed: Embed, evtMessageId: bigint, evtChannelId: bigint, userId: bigint, evtGuildId: bigint) => {
export const removeMemberFromEvent = async (
bot: Bot,
interaction: Interaction,
evtMessageEmbed: Embed,
evtMessageId: bigint,
evtChannelId: bigint,
userId: bigint,
evtGuildId: bigint,
loudAcknowledge = false,
): Promise<boolean> => {
if (evtMessageEmbed.fields) {
// Get old counts
const [oldMemberCount, maxMemberCount] = getEventMemberCount(evtMessageEmbed.fields[LfgEmbedIndexes.JoinedMembers].name);
@ -202,13 +211,16 @@ export const removeMemberFromEvent = async (bot: Bot, interaction: Interaction,
}
// Update the event
await editEvent(bot, interaction, evtMessageEmbed, evtMessageId, evtChannelId, memberList, maxMemberCount, alternateList);
await editEvent(bot, interaction, evtMessageEmbed, evtMessageId, evtChannelId, memberList, maxMemberCount, alternateList, loudAcknowledge);
return true;
} else {
// Send noEdit response because user did not actually leave
await noEdit(bot, interaction);
await noEdit(bot, interaction, loudAcknowledge);
return false;
}
} else {
await somethingWentWrong(bot, interaction, 'noFieldsInRemoveMember');
return false;
}
};
@ -222,7 +234,7 @@ export const alternateMemberToEvent = async (
member: LFGMember,
userJoinOnFull = false,
loudAcknowledge = false,
) => {
): Promise<boolean> => {
if (evtMessageEmbed.fields) {
member.joined = userJoinOnFull;
// Get current alternates
@ -241,18 +253,30 @@ export const alternateMemberToEvent = async (
// Update the event
evtMessageEmbed.fields[LfgEmbedIndexes.AlternateMembers].value = generateAlternateList(alternateList);
await editEvent(bot, interaction, evtMessageEmbed, evtMessageId, evtChannelId, memberList, maxMemberCount, alternateList, loudAcknowledge);
return true;
} else {
// Send noEdit response because user was already an alternate and joined status did not change
await noEdit(bot, interaction, loudAcknowledge);
return false;
}
} else {
// No fields, can't alternate
await somethingWentWrong(bot, interaction, 'noFieldsInAlternateMember');
return false;
}
};
// Join member to the event
export const joinMemberToEvent = async (bot: Bot, interaction: Interaction, evtMessageEmbed: Embed, evtMessageId: bigint, evtChannelId: bigint, member: LFGMember, evtGuildId: bigint) => {
export const joinMemberToEvent = async (
bot: Bot,
interaction: Interaction,
evtMessageEmbed: Embed,
evtMessageId: bigint,
evtChannelId: bigint,
member: LFGMember,
evtGuildId: bigint,
loudAcknowledge = false,
): Promise<boolean> => {
if (evtMessageEmbed.fields) {
// Get current member list and count
const [oldMemberCount, maxMemberCount] = getEventMemberCount(evtMessageEmbed.fields[LfgEmbedIndexes.JoinedMembers].name);
@ -260,10 +284,11 @@ export const joinMemberToEvent = async (bot: Bot, interaction: Interaction, evtM
// Verify user is not already on the joined list
if (memberList.find((joinedMember) => joinedMember.id === member.id)) {
// Send noEdit response because user was already joined
await noEdit(bot, interaction);
await noEdit(bot, interaction, loudAcknowledge);
return false;
} else if (oldMemberCount === maxMemberCount) {
// Event full, add member to alternate list
await alternateMemberToEvent(bot, interaction, evtMessageEmbed, evtMessageId, evtChannelId, member, true);
return await alternateMemberToEvent(bot, interaction, evtMessageEmbed, evtMessageId, evtChannelId, member, true, loudAcknowledge);
} else {
// Join member to event
memberList.push(member);
@ -272,7 +297,7 @@ export const joinMemberToEvent = async (bot: Bot, interaction: Interaction, evtM
const alternateList = removeLfgMember(getLfgMembers(evtMessageEmbed.fields[LfgEmbedIndexes.AlternateMembers].value), member.id);
// Update the event
await editEvent(bot, interaction, evtMessageEmbed, evtMessageId, evtChannelId, memberList, maxMemberCount, alternateList);
await editEvent(bot, interaction, evtMessageEmbed, evtMessageId, evtChannelId, memberList, maxMemberCount, alternateList, loudAcknowledge);
// Check if we need to notify the owner that their event has filled
if (memberList.length === maxMemberCount) {
@ -292,10 +317,12 @@ export const joinMemberToEvent = async (bot: Bot, interaction: Interaction, evtM
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('utils.ts', 'event filled dm', e));
}
return true;
}
} else {
// No fields, can't join
await somethingWentWrong(bot, interaction, 'noFieldsInJoinMember');
return false;
}
};

View File

@ -6,9 +6,10 @@ import info from './info.ts';
import report from './report.ts';
import setup from './setup.ts';
import deleteCmd from './delete.ts';
import managerJLA from './managerJLA.ts';
import { gameSelectionCommand } from '../buttons/event-creation/step1-gameSelection.ts';
export const commands: Array<Command> = [deleteCmd, info, report, setup, gameSelectionCommand];
export const commands: Array<Command> = [deleteCmd, info, report, setup, gameSelectionCommand, managerJLA];
export const createSlashCommands = async (bot: Bot) => {
const globalCommands: MakeRequired<CreateApplicationCommand, 'name'>[] = [];

164
src/commands/managerJLA.ts Normal file
View File

@ -0,0 +1,164 @@
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, Bot, Interaction } from '../../deps.ts';
import { alternateMemberToEvent, getGuildName, joinMemberToEvent, removeMemberFromEvent } from '../buttons/live-event/utils.ts';
import { generateMemberList } from '../buttons/eventUtils.ts';
import { dbClient, generateGuildSettingKey, lfgChannelSettings, queries } from '../db.ts';
import { infoColor2, sendDirectMessage, somethingWentWrong, stopThat } from '../commandUtils.ts';
import { CommandDetails, LFGMember } from '../types/commandTypes.ts';
import config from '../../config.ts';
import utils from '../utils.ts';
export const eventName = 'event';
export const joinName = 'join';
export const leaveName = 'leave';
export const alternateName = 'alternate';
export const eventLinkName = 'event-link';
export const userName = 'user';
// Create command with three nearly identical subcommands
const generateOptions = (commandName: string) => ({
name: commandName,
description: `${config.name} Manager Command: ${utils.capitalizeFirstChar(commandName)}s a user to an event in this channel.`,
type: ApplicationCommandOptionTypes.SubCommand,
options: [
{
name: eventLinkName,
type: ApplicationCommandOptionTypes.String,
description: 'Please copy the message link for the desired event.',
required: true,
minLength: 31,
maxLength: 100,
},
{
name: userName,
type: ApplicationCommandOptionTypes.User,
description: `The user you wish to ${commandName}.`,
required: true,
},
],
});
const details: CommandDetails = {
name: eventName,
description: `${config.name} Manager Command`,
type: ApplicationCommandTypes.ChatInput,
options: [generateOptions(joinName), generateOptions(leaveName), generateOptions(alternateName)],
};
const execute = async (bot: Bot, interaction: Interaction) => {
if (interaction.data?.options?.[0].options && interaction.channelId && interaction.guildId && interaction.member && interaction.member.user) {
// Get action and log to db
const actionName = interaction.data.options[0].name;
dbClient.execute(queries.callIncCnt(`cmd-${actionName}`)).catch((e) => utils.commonLoggers.dbError('managerJLA.ts', 'call sproc INC_CNT on', e));
const lfgChannelSetting = lfgChannelSettings.get(generateGuildSettingKey(interaction.guildId, interaction.channelId)) || {
managed: false,
managerRoleId: 0n,
logChannelId: 0n,
};
// Check if guild is managed and if user is a manager
if (lfgChannelSetting.managed && interaction.member.roles.includes(lfgChannelSetting.managerRoleId)) {
// User is a manager, parse out our data
const tempDataMap: Map<string, string> = new Map();
for (const option of interaction.data.options[0].options) {
tempDataMap.set(option.name || 'missingCustomId', option.value as string || '');
}
const eventLink = tempDataMap.get(eventLinkName) || '';
const userToAdd = BigInt(tempDataMap.get(userName) || '0');
const eventIds = utils.messageUrlToIds(eventLink);
// Verify fields exist
if (!eventLink || !userToAdd || !eventIds.guildId || !eventIds.channelId || !eventIds.messageId) {
somethingWentWrong(bot, interaction, 'missingLinkOrUserInManagerJLA');
return;
}
// Get event from link
const eventMessage = await bot.helpers.getMessage(eventIds.channelId, eventIds.messageId).catch((e: Error) => utils.commonLoggers.messageGetError('managerJLA.ts', 'get eventMessage', e));
const userDetails = await bot.helpers.getUser(userToAdd).catch((e: Error) => utils.commonLoggers.messageGetError('managerJLA.ts', 'get userDetails', e));
if (eventMessage && userDetails) {
// Perform the action
const userInfo: LFGMember = {
id: userToAdd,
name: userDetails.username,
};
let changeMade = false;
switch (actionName) {
case joinName:
changeMade = await joinMemberToEvent(bot, interaction, eventMessage.embeds[0], eventIds.messageId, eventIds.channelId, userInfo, eventIds.guildId, true);
break;
case leaveName:
changeMade = await removeMemberFromEvent(bot, interaction, eventMessage.embeds[0], eventIds.messageId, eventIds.channelId, userToAdd, eventIds.guildId, true);
break;
case alternateName:
changeMade = await alternateMemberToEvent(bot, interaction, eventMessage.embeds[0], eventIds.messageId, eventIds.channelId, userInfo, false, true);
break;
default:
somethingWentWrong(bot, interaction, 'actionNameWrongManagerJLA');
break;
}
if (changeMade) {
// userToAdd was had JLA done to them, DM them with details\
const guildName = await getGuildName(bot, interaction.guildId);
const commonFields = [{
name: 'Event Link:',
value: `[Click Here](${eventLink}) to view the event.`,
inline: true,
}, {
name: 'Action Performed:',
value: utils.capitalizeFirstChar(actionName),
inline: true,
}];
sendDirectMessage(bot, userToAdd, {
embeds: [{
color: infoColor2,
title: `Notice: A ${config.name} Manager has performed an action for you in ${guildName}`,
fields: [
{
name: `${config.name} Manager:`,
value: generateMemberList([{
id: interaction.member.id,
name: interaction.member.user.username,
}]),
inline: true,
},
...commonFields,
{
name: 'Are you unhappy with this action?',
value: `Please reach out to the ${config.name} Manager that performed this action, or the moderators/administrators of ${guildName}.`,
},
],
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('managerJLA.ts', 'send DM fail', e));
// Log this action
bot.helpers.sendMessage(lfgChannelSetting.logChannelId, {
embeds: [{
color: infoColor2,
title: `A ${config.name} Manager has performed an action on behalf of a user.`,
description: `The following user had an action by ${interaction.member.user.username} - <@${interaction.member.id}>.`,
fields: [...commonFields, {
name: 'User:',
value: generateMemberList([userInfo]),
inline: true,
}],
timestamp: new Date().getTime(),
}],
}).catch((e: Error) => utils.commonLoggers.messageSendError('deleteConfirmed.ts', 'send log message', e));
}
} else {
somethingWentWrong(bot, interaction, 'eventOrUserMissingFromManagerJLA');
}
} else {
// User not a manager
stopThat(bot, interaction, `${actionName} users to`);
}
} else {
// All data missing
somethingWentWrong(bot, interaction, 'missingDataInManagerJLA');
}
};
export default {
details,
execute,
};

View File

@ -19,6 +19,7 @@ import { CommandDetails } from '../types/commandTypes.ts';
import utils from '../utils.ts';
import { customId as gameSelId } from '../buttons/event-creation/step1-gameSelection.ts';
import { alternateEventBtnStr, joinEventBtnStr, leaveEventBtnStr, requestToJoinEventBtnStr } from '../buttons/eventUtils.ts';
import { alternateName, eventLinkName, eventName, joinName, leaveName, userName } from './managerJLA.ts';
const withoutMgrRole = 'without-manager-role';
const withMgrRole = 'with-manager-role';
@ -150,7 +151,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
name: `${config.name} Manager Details:`,
value: `${config.name} Managers with the <@&${managerRoleId}> role may edit or delete events in this guild, along with using the following commands to update the activity members:
\`/join\` \`/leave\` \`/alternate\`
\`/${eventName} [${joinName} | ${leaveName} | ${alternateName}] [${eventLinkName}] [${userName}]\`
The Discord Slash Command system will ensure you provide all the required details.`,
});

View File

@ -18,6 +18,8 @@ const messageUrlToIds = (url: string): UrlIds => {
};
};
const capitalizeFirstChar = (input: string) => `${input.charAt(0).toUpperCase()}${input.slice(1)}`;
const genericLogger = (level: LT, message: string) => log(level, message);
const interactionSendError = (location: string, interaction: Interaction | string, err: Error) =>
genericLogger(LT.ERROR, `${location} | Failed to respond to interaction: ${jsonStringifyBig(interaction)} | Error: ${err.name} - ${err.message}`);
@ -38,6 +40,7 @@ const channelUpdateError = (location: string, message: string, err: Error) => ge
const dbError = (location: string, type: string, err: Error) => genericLogger(LT.ERROR, `${location} | Failed to ${type} database | Error: ${err.name} - ${err.message}`);
export default {
capitalizeFirstChar,
commonLoggers: {
channelUpdateError,
dbError,