Started work on WL Event system, minor update to other files for consistency
db.ts, setup.ts, delete.ts: change lfgSettings to use generator function for map key joinEvent.ts: add first half of WL system, add system to reduce spam/abuse of this system. commandUtils.ts: fix sendDirectMessage to return right promise
This commit is contained in:
parent
487079713f
commit
a6848eb33a
|
@ -1,5 +1,4 @@
|
|||
// This file will populate the tables with default values
|
||||
|
||||
import { dbClient } from '../src/db.ts';
|
||||
|
||||
console.log('Attempting to insert default actions into command_cnt');
|
||||
|
|
|
@ -27,7 +27,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
|
|||
embeds: [interaction.message.embeds[0]],
|
||||
components: [{
|
||||
type: MessageComponentTypes.ActionRow,
|
||||
components: generateLFGButtons(interaction.data.customId.includes(idSeparator)),
|
||||
components: generateLFGButtons(interaction.data.customId.includes(idSeparator)), // TODO: verify we can DM the user if they set this to WL mode
|
||||
}],
|
||||
}).catch((e: Error) => utils.commonLoggers.messageSendError('step3-createEvent.ts', 'createEvent', e));
|
||||
if (!eventMessage) {
|
||||
|
|
|
@ -1,24 +1,122 @@
|
|||
import { Bot, Interaction } from '../../../deps.ts';
|
||||
import { dbClient, queries } from '../../db.ts';
|
||||
import { somethingWentWrong } from '../../commandUtils.ts';
|
||||
import { idSeparator } from '../eventUtils.ts';
|
||||
import { ApplicationCommandFlags, Bot, Interaction, InteractionResponseTypes } from '../../../deps.ts';
|
||||
import { dbClient, generateGuildSettingKey, lfgChannelSettings, queries } from '../../db.ts';
|
||||
import { infoColor1, safelyDismissMsg, sendDirectMessage, somethingWentWrong, successColor, warnColor } from '../../commandUtils.ts';
|
||||
import { generateMemberList, idSeparator, LfgEmbedIndexes } from '../eventUtils.ts';
|
||||
import utils from '../../utils.ts';
|
||||
import { joinMemberToEvent } from './utils.ts';
|
||||
import config from '../../../config.ts';
|
||||
import { generateMapId, getGuildName, getLfgMembers, joinMemberToEvent, joinRequestMap, joinRequestResponseButtons, JoinRequestStatus } from './utils.ts';
|
||||
|
||||
export const customId = 'joinEvent';
|
||||
|
||||
export const execute = async (bot: Bot, interaction: Interaction) => {
|
||||
if (interaction.data?.customId && interaction.member && interaction.member.user && interaction.channelId && interaction.guildId && interaction.message && interaction.message.embeds[0]) {
|
||||
if (
|
||||
interaction.data?.customId && interaction.member && interaction.member.user && interaction.channelId && interaction.guildId && interaction.message && interaction.message.embeds[0] &&
|
||||
interaction.message.embeds[0].fields
|
||||
) {
|
||||
// Light Telemetry
|
||||
dbClient.execute(queries.callIncCnt(interaction.data.customId.includes(idSeparator) ? 'btn-joinWLEvent' : 'btn-joinEvent')).catch((e) =>
|
||||
utils.commonLoggers.dbError('joinEvent.ts', 'call sproc INC_CNT on', e)
|
||||
);
|
||||
const ownerId = BigInt(interaction.message.embeds[0].footer?.iconUrl?.split('#')[1] || '0');
|
||||
const memberId = interaction.member.id;
|
||||
|
||||
// Join user to event
|
||||
joinMemberToEvent(bot, interaction, interaction.message.embeds[0], interaction.message.id, interaction.channelId, {
|
||||
id: interaction.member.id,
|
||||
name: interaction.member.user.username,
|
||||
}, interaction.guildId);
|
||||
// Check if event is whitelisted
|
||||
if (interaction.data.customId.includes(idSeparator) && memberId !== ownerId) {
|
||||
// Initialize WL vars
|
||||
const joinRequestKey = generateMapId(interaction.message.id, interaction.channelId, memberId);
|
||||
const messageUrl = utils.idsToMessageUrl({
|
||||
guildId: interaction.guildId,
|
||||
channelId: interaction.channelId,
|
||||
messageId: interaction.message.id,
|
||||
});
|
||||
const lfgChannelSetting = lfgChannelSettings.get(generateGuildSettingKey(interaction.guildId, interaction.channelId)) || { managed: false };
|
||||
const urgentManagerStr = lfgChannelSetting.managed ? ` a ${config.name} Manager (members with the <@&${lfgChannelSetting.managerRoleId}> role in this guild) or ` : ' ';
|
||||
const eventMembers = getLfgMembers(interaction.message.embeds[0].fields[LfgEmbedIndexes.JoinedMembers].value);
|
||||
|
||||
if (eventMembers.find((lfgMember) => lfgMember.id === memberId)) {
|
||||
// User is already joined to event, block request
|
||||
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||
data: {
|
||||
flags: ApplicationCommandFlags.Ephemeral,
|
||||
embeds: [{
|
||||
color: warnColor,
|
||||
title: 'Notice: Request Blocked',
|
||||
description: `To reduce spam, ${config.name} has blocked this request to join as you have already joined this event.
|
||||
|
||||
${safelyDismissMsg}`,
|
||||
}],
|
||||
},
|
||||
}).catch((e: Error) => utils.commonLoggers.interactionSendError('joinEvent.ts@userAlreadyJoined', interaction, e));
|
||||
} else if (joinRequestMap.has(joinRequestKey)) {
|
||||
// User has already sent request, block new one
|
||||
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||
data: {
|
||||
flags: ApplicationCommandFlags.Ephemeral,
|
||||
embeds: [{
|
||||
color: warnColor,
|
||||
title: 'Notice: Request Blocked',
|
||||
description: `To reduce spam, ${config.name} has blocked this request to join as you have recently sent a request for this event.
|
||||
|
||||
If this request is urgent, please speak with${urgentManagerStr}the owner of [this event](${messageUrl}), <@${ownerId}>, to resolve the issue.
|
||||
|
||||
The status of your recent Join Request for [this event](${messageUrl}) is: \`${joinRequestMap.get(joinRequestKey)?.status || 'Failed to retrieve status'}\`
|
||||
|
||||
${safelyDismissMsg}`,
|
||||
}],
|
||||
},
|
||||
}).catch((e: Error) => utils.commonLoggers.interactionSendError('joinEvent.ts@requestBlocked', interaction, e));
|
||||
} else {
|
||||
const guildName = await getGuildName(bot, interaction.guildId);
|
||||
// User is not joined and this is first request, send the Join Request
|
||||
sendDirectMessage(bot, ownerId, {
|
||||
embeds: [{
|
||||
color: infoColor1,
|
||||
title: 'New Join Request!',
|
||||
description: `A member has requested to join [your event](${messageUrl}) in \`${guildName}\`. Please use the buttons below this message to Approve or Deny the request.`,
|
||||
fields: [{
|
||||
name: 'Member Details:',
|
||||
value: generateMemberList([{
|
||||
id: memberId,
|
||||
name: interaction.member.user.username,
|
||||
}]),
|
||||
}],
|
||||
}],
|
||||
components: joinRequestResponseButtons(false),
|
||||
}).then(() => {
|
||||
// Alert requester that join request has been sent
|
||||
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||
data: {
|
||||
flags: ApplicationCommandFlags.Ephemeral,
|
||||
embeds: [{
|
||||
color: successColor,
|
||||
title: 'Notice: Request Received',
|
||||
description: `The owner of [this event](${messageUrl}), <@${ownerId}>, has been notified of your request. You will receive a Direct Message when <@${ownerId}> responds to the request.
|
||||
|
||||
${safelyDismissMsg}`,
|
||||
}],
|
||||
},
|
||||
}).catch((e: Error) => utils.commonLoggers.interactionSendError('joinEvent.ts@requestReceived', interaction, e));
|
||||
|
||||
// Track the request to prevent spam
|
||||
joinRequestMap.set(joinRequestKey, {
|
||||
status: JoinRequestStatus.Pending,
|
||||
timestamp: new Date().getTime(),
|
||||
});
|
||||
}).catch((e: Error) => {
|
||||
somethingWentWrong(bot, interaction, 'failedToDMOwnerInRequestToJoinEventButton');
|
||||
utils.commonLoggers.messageSendError('joinEvent.ts@dmOwner', 'failed to DM owner for join request', e);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Join user to event
|
||||
joinMemberToEvent(bot, interaction, interaction.message.embeds[0], interaction.message.id, interaction.channelId, {
|
||||
id: memberId,
|
||||
name: interaction.member.user.username,
|
||||
}, interaction.guildId);
|
||||
}
|
||||
} else {
|
||||
somethingWentWrong(bot, interaction, 'noDataFromJoinEventButton');
|
||||
}
|
||||
|
|
|
@ -1,9 +1,52 @@
|
|||
import { Bot, ButtonStyles, Embed, Interaction, InteractionResponseTypes, MessageComponentTypes } from '../../../deps.ts';
|
||||
import { ActionRow, Bot, ButtonStyles, Embed, Interaction, InteractionResponseTypes, MessageComponentTypes } from '../../../deps.ts';
|
||||
import { LFGMember, UrlIds } from '../../types/commandTypes.ts';
|
||||
import { sendDirectMessage, somethingWentWrong, successColor } from '../../commandUtils.ts';
|
||||
import { generateAlternateList, generateMemberList, generateMemberTitle, leaveEventBtnStr, LfgEmbedIndexes, noMembersStr } from '../eventUtils.ts';
|
||||
import utils from '../../utils.ts';
|
||||
|
||||
// Join status map to prevent spamming the system
|
||||
export enum JoinRequestStatus {
|
||||
Pending = 'Pending',
|
||||
Approved = 'Approved',
|
||||
Denied = 'Denied',
|
||||
}
|
||||
export const generateMapId = (messageId: bigint, channelId: bigint, userId: bigint) => `${messageId}-${channelId}-${userId}`;
|
||||
export const joinRequestMap: Map<string, {
|
||||
status: JoinRequestStatus;
|
||||
timestamp: number;
|
||||
}> = new Map();
|
||||
|
||||
// Join request map cleaner
|
||||
const oneHour = 1000 * 60 * 60;
|
||||
const oneDay = oneHour * 24;
|
||||
const oneWeek = oneDay * 7;
|
||||
setInterval(() => {
|
||||
const now = new Date().getTime();
|
||||
joinRequestMap.forEach((joinRequest, key) => {
|
||||
switch (joinRequest.status) {
|
||||
case JoinRequestStatus.Approved:
|
||||
// Delete Approved when over 1 hour old
|
||||
if (joinRequest.timestamp > now - oneHour) {
|
||||
joinRequestMap.delete(key);
|
||||
}
|
||||
break;
|
||||
case JoinRequestStatus.Pending:
|
||||
// Delete Pending when over 1 day old
|
||||
if (joinRequest.timestamp > now - oneDay) {
|
||||
joinRequestMap.delete(key);
|
||||
}
|
||||
break;
|
||||
case JoinRequestStatus.Denied:
|
||||
// Delete Rejected when over 1 week old
|
||||
if (joinRequest.timestamp > now - oneWeek) {
|
||||
joinRequestMap.delete(key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
// Run cleaner every hour
|
||||
}, oneHour);
|
||||
|
||||
// Get Member Counts from the title
|
||||
const getEventMemberCount = (rawMemberTitle: string): [number, number] => {
|
||||
const [rawCurrentCount, rawMaxCount] = rawMemberTitle.split('/');
|
||||
|
@ -13,7 +56,7 @@ const getEventMemberCount = (rawMemberTitle: string): [number, number] => {
|
|||
};
|
||||
|
||||
// Get LFGMember objects from string list
|
||||
const getLfgMembers = (rawMemberList: string): Array<LFGMember> =>
|
||||
export const getLfgMembers = (rawMemberList: string): Array<LFGMember> =>
|
||||
rawMemberList.trim() === noMembersStr ? [] : rawMemberList.split('\n').map((rawMember) => {
|
||||
const [memberName, memberMention] = rawMember.split('-');
|
||||
const lfgMember: LFGMember = {
|
||||
|
@ -67,7 +110,7 @@ const noEdit = async (bot: Bot, interaction: Interaction) =>
|
|||
}).catch((e: Error) => utils.commonLoggers.interactionSendError('utils.ts', interaction, e));
|
||||
|
||||
// Get Guild Name
|
||||
const getGuildName = async (bot: Bot, guildId: bigint): Promise<string> =>
|
||||
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
|
||||
|
@ -120,7 +163,7 @@ export const removeMemberFromEvent = async (bot: Bot, interaction: Interaction,
|
|||
customId: 'leaveEventCustomId', // TODO: fix
|
||||
}],
|
||||
}],
|
||||
}).catch((e: Error) => utils.commonLoggers.messageSendError('utils.ts', 'user promotion', e));
|
||||
}).catch((e: Error) => utils.commonLoggers.messageSendError('utils.ts', 'user promotion dm', e));
|
||||
}
|
||||
|
||||
// Update the event
|
||||
|
@ -203,7 +246,7 @@ export const joinMemberToEvent = async (bot: Bot, interaction: Interaction, evtM
|
|||
description: `[Click here to view the event in ${guildName}.](${utils.idsToMessageUrl(urlIds)})`,
|
||||
fields: evtMessageEmbed.fields,
|
||||
}],
|
||||
}).catch((e: Error) => utils.commonLoggers.messageSendError('utils.ts', 'event filled', e));
|
||||
}).catch((e: Error) => utils.commonLoggers.messageSendError('utils.ts', 'event filled dm', e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -211,3 +254,21 @@ export const joinMemberToEvent = async (bot: Bot, interaction: Interaction, evtM
|
|||
await somethingWentWrong(bot, interaction, 'noFieldsInJoinMember');
|
||||
}
|
||||
};
|
||||
|
||||
// Join Request Approve/Deny Buttons
|
||||
export const joinRequestResponseButtons = (disabled: boolean): ActionRow[] => [{
|
||||
type: MessageComponentTypes.ActionRow,
|
||||
components: [{
|
||||
type: MessageComponentTypes.Button,
|
||||
label: 'Approve Request',
|
||||
style: ButtonStyles.Success,
|
||||
customId: 'approveJoinRequestCustomId', // TODO: fix
|
||||
disabled,
|
||||
}, {
|
||||
type: MessageComponentTypes.Button,
|
||||
label: 'Deny Request',
|
||||
style: ButtonStyles.Danger,
|
||||
customId: 'denyJoinRequestCustomId', // TODO: fix
|
||||
disabled,
|
||||
}],
|
||||
}];
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ApplicationCommandFlags, Bot, CreateMessage, Interaction, InteractionResponseTypes } from '../deps.ts';
|
||||
import config from '../config.ts';
|
||||
import { lfgChannelSettings } from './db.ts';
|
||||
import { generateGuildSettingKey, lfgChannelSettings } from './db.ts';
|
||||
import utils from './utils.ts';
|
||||
|
||||
export const failColor = 0xe71212;
|
||||
|
@ -21,7 +21,7 @@ export const getRandomStatus = (guildCount: number): string => {
|
|||
};
|
||||
|
||||
export const isLFGChannel = (guildId: bigint, channelId: bigint) => {
|
||||
return (lfgChannelSettings.has(`${guildId}-${channelId}`) || channelId === 0n || guildId === 0n) ? ApplicationCommandFlags.Ephemeral : undefined;
|
||||
return (lfgChannelSettings.has(generateGuildSettingKey(guildId, channelId)) || channelId === 0n || guildId === 0n) ? ApplicationCommandFlags.Ephemeral : undefined;
|
||||
};
|
||||
|
||||
export const somethingWentWrong = async (bot: Bot, interaction: Interaction, errorCode: string) =>
|
||||
|
@ -42,8 +42,8 @@ export const somethingWentWrong = async (bot: Bot, interaction: Interaction, err
|
|||
}).catch((e: Error) => utils.commonLoggers.interactionSendError('commandUtils.ts', interaction, e));
|
||||
|
||||
// Send DM to User
|
||||
export const sendDirectMessage = async (bot: Bot, userId: bigint, message: CreateMessage) =>
|
||||
bot.helpers.getDmChannel(userId).then((userDmChannel) => {
|
||||
// Actually send the DM
|
||||
bot.helpers.sendMessage(userDmChannel.id, message).catch((e: Error) => utils.commonLoggers.messageSendError('commandUtils.ts', message, e));
|
||||
}).catch((e: Error) => utils.commonLoggers.messageGetError('commandUtils.ts', 'get userDmChannel', e));
|
||||
export const sendDirectMessage = async (bot: Bot, userId: bigint, message: CreateMessage) => {
|
||||
const userDmChannel = await bot.helpers.getDmChannel(userId).catch((e: Error) => utils.commonLoggers.messageGetError('commandUtils.ts', 'get userDmChannel', e));
|
||||
// Actually send the DM
|
||||
return bot.helpers.sendMessage(userDmChannel?.id || 0n, message);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import config from '../../config.ts';
|
||||
import { ApplicationCommandFlags, ApplicationCommandTypes, Bot, Interaction, InteractionResponseTypes } from '../../deps.ts';
|
||||
import { failColor, safelyDismissMsg, somethingWentWrong, successColor } from '../commandUtils.ts';
|
||||
import { dbClient, lfgChannelSettings, queries } from '../db.ts';
|
||||
import { dbClient, generateGuildSettingKey, lfgChannelSettings, queries } from '../db.ts';
|
||||
import { CommandDetails } from '../types/commandTypes.ts';
|
||||
import utils from '../utils.ts';
|
||||
|
||||
|
@ -16,7 +16,8 @@ const execute = async (bot: Bot, interaction: Interaction) => {
|
|||
dbClient.execute(queries.callIncCnt('cmd-delete')).catch((e) => utils.commonLoggers.dbError('delete.ts', 'call sproc INC_CNT on', e));
|
||||
|
||||
if (interaction.guildId && interaction.channelId) {
|
||||
if (!lfgChannelSettings.has(`${interaction.guildId}-${interaction.channelId}`)) {
|
||||
const lfgChannelSettingKey = generateGuildSettingKey(interaction.guildId, interaction.channelId);
|
||||
if (!lfgChannelSettings.has(lfgChannelSettingKey)) {
|
||||
// Cannot delete a lfg channel that has not been set up
|
||||
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||
|
@ -43,7 +44,7 @@ const execute = async (bot: Bot, interaction: Interaction) => {
|
|||
somethingWentWrong(bot, interaction, 'deleteDBDeleteFail');
|
||||
return;
|
||||
}
|
||||
lfgChannelSettings.delete(`${interaction.guildId}-${interaction.channelId}`);
|
||||
lfgChannelSettings.delete(lfgChannelSettingKey);
|
||||
|
||||
// Complete the interaction
|
||||
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
OverwriteTypes,
|
||||
} from '../../deps.ts';
|
||||
import { failColor, infoColor2, safelyDismissMsg, somethingWentWrong, successColor } from '../commandUtils.ts';
|
||||
import { dbClient, lfgChannelSettings, queries } from '../db.ts';
|
||||
import { dbClient, generateGuildSettingKey, lfgChannelSettings, queries } from '../db.ts';
|
||||
import { CommandDetails } from '../types/commandTypes.ts';
|
||||
import utils from '../utils.ts';
|
||||
import { customId as gameSelId } from '../buttons/event-creation/step1-gameSelection.ts';
|
||||
|
@ -64,7 +64,8 @@ const execute = async (bot: Bot, interaction: Interaction) => {
|
|||
const setupOpts = interaction.data?.options?.[0];
|
||||
|
||||
if (setupOpts?.name && interaction.channelId && interaction.guildId) {
|
||||
if (lfgChannelSettings.has(`${interaction.guildId}-${interaction.channelId}`)) {
|
||||
const lfgChannelSettingKey = generateGuildSettingKey(interaction.guildId, interaction.channelId);
|
||||
if (lfgChannelSettings.has(lfgChannelSettingKey)) {
|
||||
// Cannot setup a lfg channel that is already set up
|
||||
bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||
|
@ -260,7 +261,7 @@ The Discord Slash Command system will ensure you provide all the required detail
|
|||
return;
|
||||
}
|
||||
// Store the ids to the active map
|
||||
lfgChannelSettings.set(`${interaction.guildId}-${interaction.channelId}`, {
|
||||
lfgChannelSettings.set(lfgChannelSettingKey, {
|
||||
managed: setupOpts.name === withMgrRole,
|
||||
managerRoleId,
|
||||
logChannelId,
|
||||
|
|
|
@ -17,10 +17,11 @@ export const queries = {
|
|||
};
|
||||
|
||||
export const lfgChannelSettings: Map<string, LfgChannelSetting> = new Map();
|
||||
export const generateGuildSettingKey = (guildId: bigint, channelId: bigint) => `${guildId}-${channelId}`;
|
||||
const getGuildSettings = await dbClient.query('SELECT * FROM guild_settings');
|
||||
getGuildSettings.forEach((g: DBGuildSettings) => {
|
||||
lfgChannelSettings.set(`${g.guildId}-${g.lfgChannelId}`, {
|
||||
managed: g.managerRoleId === 0n && g.logChannelId === 0n,
|
||||
lfgChannelSettings.set(generateGuildSettingKey(g.guildId, g.lfgChannelId), {
|
||||
managed: g.managerRoleId !== 0n && g.logChannelId !== 0n,
|
||||
managerRoleId: g.managerRoleId,
|
||||
logChannelId: g.logChannelId,
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue