2023-04-14 14:20:57 -07:00
import { ActionRow , ApplicationCommandFlags , Bot , ButtonStyles , Embed , Interaction , InteractionResponseTypes , MessageComponentTypes } from '../../../deps.ts' ;
2023-04-08 01:31:04 -07:00
import { LFGMember , UrlIds } from '../../types/commandTypes.ts' ;
2023-04-14 14:20:57 -07:00
import { infoColor1 , safelyDismissMsg , sendDirectMessage , somethingWentWrong , successColor } from '../../commandUtils.ts' ;
2023-04-14 12:52:45 -07:00
import { generateAlternateList , generateMemberList , generateMemberTitle , idSeparator , leaveEventBtnStr , LfgEmbedIndexes , noMembersStr } from '../eventUtils.ts' ;
import { approveStr , customId as joinRequestCustomId , denyStr } from './joinRequest.ts' ;
2023-04-07 23:53:51 -07:00
import utils from '../../utils.ts' ;
2023-04-09 01:12:53 -07:00
// 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 ) ;
2023-04-07 23:53:51 -07:00
// Get Member Counts from the title
2023-04-08 00:42:22 -07:00
const getEventMemberCount = ( rawMemberTitle : string ) : [ number , number ] = > {
2023-04-07 23:53:51 -07:00
const [ rawCurrentCount , rawMaxCount ] = rawMemberTitle . split ( '/' ) ;
const currentMemberCount = parseInt ( rawCurrentCount . split ( ':' ) [ 1 ] || '0' ) ;
const maxMemberCount = parseInt ( rawMaxCount || '0' ) ;
return [ currentMemberCount , maxMemberCount ] ;
} ;
// Get LFGMember objects from string list
2023-04-09 01:12:53 -07:00
export const getLfgMembers = ( rawMemberList : string ) : Array < LFGMember > = >
2023-04-07 23:53:51 -07:00
rawMemberList . trim ( ) === noMembersStr ? [ ] : rawMemberList . split ( '\n' ) . map ( ( rawMember ) = > {
const [ memberName , memberMention ] = rawMember . split ( '-' ) ;
const lfgMember : LFGMember = {
id : BigInt ( memberMention . split ( '<@' ) [ 1 ] . split ( '>' ) [ 0 ] . trim ( ) || '0' ) ,
name : memberName.trim ( ) ,
joined : rawMember.endsWith ( '*' ) ,
} ;
return lfgMember ;
} ) ;
// Remove LFGMember from array filter
2023-04-08 00:42:22 -07:00
const removeLfgMember = ( memberList : Array < LFGMember > , memberId : bigint ) : Array < LFGMember > = > memberList . filter ( ( member ) = > member . id !== memberId ) ;
2023-04-07 23:53:51 -07:00
2023-04-08 02:11:41 -07:00
// Handles updating the fields and editing the event
2023-04-08 00:42:22 -07:00
const editEvent = async (
bot : Bot ,
interaction : Interaction ,
evtMessageEmbed : Embed ,
evtMessageId : bigint ,
evtChannelId : bigint ,
memberList : Array < LFGMember > ,
maxMemberCount : number ,
alternateList : Array < LFGMember > ,
2023-04-14 14:20:57 -07:00
loudAcknowledge = false ,
2023-04-08 00:42:22 -07:00
) = > {
2023-04-07 23:53:51 -07:00
if ( evtMessageEmbed . fields ) {
2023-04-08 00:42:22 -07:00
// Update the fields
2023-04-07 23:53:51 -07:00
evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . name = generateMemberTitle ( memberList , maxMemberCount ) ;
evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . value = generateMemberList ( memberList ) ;
evtMessageEmbed . fields [ LfgEmbedIndexes . AlternateMembers ] . value = generateAlternateList ( alternateList ) ;
2023-04-08 00:42:22 -07:00
// Edit the event
await bot . helpers . editMessage ( evtChannelId , evtMessageId , {
2023-04-07 23:53:51 -07:00
embeds : [ evtMessageEmbed ] ,
} ) . then ( ( ) = > {
// Let discord know we didn't ignore the user
2023-04-14 14:20:57 -07:00
if ( loudAcknowledge ) {
bot . helpers . sendInteractionResponse ( interaction . id , interaction . token , {
type : InteractionResponseTypes . ChannelMessageWithSource ,
data : {
flags : ApplicationCommandFlags.Ephemeral ,
embeds : [ {
color : successColor ,
title : 'Event Updated' ,
description : ` The action requested was completed successfully.
2023-04-14 14:23:57 -07:00
$ { safelyDismissMsg } ` ,
2023-04-14 14:20:57 -07:00
} ] ,
} ,
} ) . catch ( ( e : Error ) = > utils . commonLoggers . interactionSendError ( 'utils.ts' , interaction , e ) ) ;
} else {
bot . helpers . sendInteractionResponse ( interaction . id , interaction . token , {
type : InteractionResponseTypes . DeferredUpdateMessage ,
} ) . catch ( ( e : Error ) = > utils . commonLoggers . interactionSendError ( 'utils.ts' , interaction , e ) ) ;
}
2023-04-07 23:53:51 -07:00
} ) . catch ( ( e : Error ) = > {
// Edit failed, try to notify user
2023-04-08 00:42:22 -07:00
utils . commonLoggers . messageEditError ( 'utils.ts' , 'event edit fail' , e ) ;
somethingWentWrong ( bot , interaction , 'editFailedInUpdateEvent' ) ;
2023-04-07 23:53:51 -07:00
} ) ;
2023-04-08 00:42:22 -07:00
}
} ;
2023-04-08 02:11:41 -07:00
// Generic no response response
2023-04-14 14:20:57 -07:00
const noEdit = async ( bot : Bot , interaction : Interaction , loudAcknowledge = false ) = > {
if ( loudAcknowledge ) {
bot . helpers . sendInteractionResponse ( interaction . id , interaction . token , {
type : InteractionResponseTypes . ChannelMessageWithSource ,
data : {
flags : ApplicationCommandFlags.Ephemeral ,
embeds : [ {
color : infoColor1 ,
title : 'No Changes Made' ,
description : ` The action requested was not performed as it was not necessary.
2023-04-14 14:23:57 -07:00
$ { safelyDismissMsg } ` ,
2023-04-14 14:20:57 -07:00
} ] ,
} ,
} ) . catch ( ( e : Error ) = > utils . commonLoggers . interactionSendError ( 'utils.ts' , interaction , e ) ) ;
} else {
bot . helpers . sendInteractionResponse ( interaction . id , interaction . token , {
type : InteractionResponseTypes . DeferredUpdateMessage ,
} ) . catch ( ( e : Error ) = > utils . commonLoggers . interactionSendError ( 'utils.ts' , interaction , e ) ) ;
}
} ;
2023-04-08 00:42:22 -07:00
2023-04-08 02:09:35 -07:00
// Get Guild Name
2023-04-09 01:12:53 -07:00
export const getGuildName = async ( bot : Bot , guildId : bigint ) : Promise < string > = >
2023-04-08 02:09:35 -07:00
( await bot . helpers . getGuild ( guildId ) . catch ( ( e : Error ) = > utils . commonLoggers . messageGetError ( 'utils.ts' , 'get guild' , e ) ) || { name : 'failed to get guild name' } ) . name ;
2023-04-08 00:42:22 -07:00
// Remove member from the event
2023-04-08 01:31:04 -07:00
export const removeMemberFromEvent = async ( bot : Bot , interaction : Interaction , evtMessageEmbed : Embed , evtMessageId : bigint , evtChannelId : bigint , userId : bigint , evtGuildId : bigint ) = > {
2023-04-08 00:42:22 -07:00
if ( evtMessageEmbed . fields ) {
// Get old counts
const [ oldMemberCount , maxMemberCount ] = getEventMemberCount ( evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . name ) ;
// Remove user from event
const oldMemberList = getLfgMembers ( evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . value ) ;
const memberList = removeLfgMember ( oldMemberList , userId ) ;
const oldAlternateList = getLfgMembers ( evtMessageEmbed . fields [ LfgEmbedIndexes . AlternateMembers ] . value ) ;
let alternateList = removeLfgMember ( oldAlternateList , userId ) ;
// Check if user actually left event
if ( oldMemberList . length !== memberList . length || oldAlternateList . length !== alternateList . length ) {
// Check if we need to auto-promote a member
const memberToPromote = alternateList . find ( ( member ) = > member . joined ) ;
if ( oldMemberCount !== memberList . length && oldMemberCount === maxMemberCount && memberToPromote ) {
// Promote member
alternateList = removeLfgMember ( alternateList , memberToPromote . id ) ;
memberList . push ( memberToPromote ) ;
2023-04-08 01:31:04 -07:00
const urlIds : UrlIds = {
guildId : evtGuildId ,
channelId : evtChannelId ,
messageId : evtMessageId ,
} ;
2023-04-08 00:42:22 -07:00
// Notify member of promotion
2023-04-08 01:31:04 -07:00
await sendDirectMessage ( bot , memberToPromote . id , {
embeds : [ {
2023-04-08 01:38:33 -07:00
color : successColor ,
2023-04-08 01:31:04 -07:00
title : 'Good news, you\'ve been promoted!' ,
2023-04-08 02:09:35 -07:00
description : ` A member left [the full event]( ${ utils . idsToMessageUrl ( urlIds ) } ) in \` ${ await getGuildName (
bot ,
evtGuildId ,
) } \ ` you tried to join, leaving space for me to promote you from the alternate list to the joined list. \ n \ nPlease verify the event details below. If you are no longer available for this event, please click on the ' ${ leaveEventBtnStr } ' button below ` ,
2023-04-08 01:31:04 -07:00
fields : [
evtMessageEmbed . fields [ LfgEmbedIndexes . Activity ] ,
evtMessageEmbed . fields [ LfgEmbedIndexes . StartTime ] ,
evtMessageEmbed . fields [ LfgEmbedIndexes . ICSLink ] ,
] ,
} ] ,
components : [ {
type : MessageComponentTypes . ActionRow ,
components : [ {
type : MessageComponentTypes . Button ,
label : leaveEventBtnStr ,
style : ButtonStyles.Danger ,
customId : 'leaveEventCustomId' , // TODO: fix
} ] ,
} ] ,
2023-04-09 01:12:53 -07:00
} ) . catch ( ( e : Error ) = > utils . commonLoggers . messageSendError ( 'utils.ts' , 'user promotion dm' , e ) ) ;
2023-04-08 00:42:22 -07:00
}
// Update the event
await editEvent ( bot , interaction , evtMessageEmbed , evtMessageId , evtChannelId , memberList , maxMemberCount , alternateList ) ;
} else {
// Send noEdit response because user did not actually leave
await noEdit ( bot , interaction ) ;
}
2023-04-07 23:53:51 -07:00
} else {
2023-04-08 00:42:22 -07:00
await somethingWentWrong ( bot , interaction , 'noFieldsInRemoveMember' ) ;
2023-04-07 23:53:51 -07:00
}
} ;
// Alternate member to the event
2023-04-14 14:23:57 -07:00
export const alternateMemberToEvent = async (
bot : Bot ,
interaction : Interaction ,
evtMessageEmbed : Embed ,
evtMessageId : bigint ,
evtChannelId : bigint ,
member : LFGMember ,
userJoinOnFull = false ,
loudAcknowledge = false ,
) = > {
2023-04-07 23:53:51 -07:00
if ( evtMessageEmbed . fields ) {
member . joined = userJoinOnFull ;
2023-04-08 00:42:22 -07:00
// Get current alternates
let alternateList = getLfgMembers ( evtMessageEmbed . fields [ LfgEmbedIndexes . AlternateMembers ] . value ) ;
2023-04-07 23:53:51 -07:00
2023-04-08 00:42:22 -07:00
// Verify user is not already on the alternate list
if ( ! alternateList . find ( ( alternateMember ) = > alternateMember . id === member . id && alternateMember . joined === member . joined ) ) {
// Add user to event, remove first to update joined status if necessary
alternateList = removeLfgMember ( alternateList , member . id ) ;
alternateList . push ( member ) ;
// Get member count and remove user from joined list (if they are there)
const [ _oldMemberCount , maxMemberCount ] = getEventMemberCount ( evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . name ) ;
const memberList = removeLfgMember ( getLfgMembers ( evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . value ) , member . id ) ;
// Update the event
evtMessageEmbed . fields [ LfgEmbedIndexes . AlternateMembers ] . value = generateAlternateList ( alternateList ) ;
2023-04-14 14:20:57 -07:00
await editEvent ( bot , interaction , evtMessageEmbed , evtMessageId , evtChannelId , memberList , maxMemberCount , alternateList , loudAcknowledge ) ;
2023-04-08 00:42:22 -07:00
} else {
// Send noEdit response because user was already an alternate and joined status did not change
2023-04-14 14:20:57 -07:00
await noEdit ( bot , interaction , loudAcknowledge ) ;
2023-04-08 00:42:22 -07:00
}
2023-04-07 23:53:51 -07:00
} else {
// No fields, can't alternate
2023-04-08 00:42:22 -07:00
await somethingWentWrong ( bot , interaction , 'noFieldsInAlternateMember' ) ;
2023-04-07 23:53:51 -07:00
}
} ;
// Join member to the event
2023-04-08 02:09:35 -07:00
export const joinMemberToEvent = async ( bot : Bot , interaction : Interaction , evtMessageEmbed : Embed , evtMessageId : bigint , evtChannelId : bigint , member : LFGMember , evtGuildId : bigint ) = > {
2023-04-07 23:53:51 -07:00
if ( evtMessageEmbed . fields ) {
2023-04-08 00:42:22 -07:00
// Get current member list and count
const [ oldMemberCount , maxMemberCount ] = getEventMemberCount ( evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . name ) ;
const memberList = getLfgMembers ( evtMessageEmbed . fields [ LfgEmbedIndexes . JoinedMembers ] . value ) ;
// 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 ) ;
} else if ( oldMemberCount === maxMemberCount ) {
// Event full, add member to alternate list
await alternateMemberToEvent ( bot , interaction , evtMessageEmbed , evtMessageId , evtChannelId , member , true ) ;
} else {
// Join member to event
memberList . push ( member ) ;
// Remove user from alternate list (if they are there)
const alternateList = removeLfgMember ( getLfgMembers ( evtMessageEmbed . fields [ LfgEmbedIndexes . AlternateMembers ] . value ) , member . id ) ;
// Update the event
await editEvent ( bot , interaction , evtMessageEmbed , evtMessageId , evtChannelId , memberList , maxMemberCount , alternateList ) ;
2023-04-08 02:09:35 -07:00
// Check if we need to notify the owner that their event has filled
if ( memberList . length === maxMemberCount ) {
const urlIds : UrlIds = {
guildId : evtGuildId ,
channelId : evtChannelId ,
messageId : evtMessageId ,
} ;
const guildName = await getGuildName ( bot , evtGuildId ) ;
// Notify member of promotion
await sendDirectMessage ( bot , BigInt ( evtMessageEmbed . footer ? . iconUrl ? . split ( '#' ) [ 1 ] || '0' ) , {
embeds : [ {
color : successColor ,
title : ` Good news, your event in ${ guildName } has filled! ` ,
description : ` [Click here to view the event in ${ guildName } .]( ${ utils . idsToMessageUrl ( urlIds ) } ) ` ,
fields : evtMessageEmbed.fields ,
} ] ,
2023-04-09 01:12:53 -07:00
} ) . catch ( ( e : Error ) = > utils . commonLoggers . messageSendError ( 'utils.ts' , 'event filled dm' , e ) ) ;
2023-04-08 02:09:35 -07:00
}
2023-04-08 00:42:22 -07:00
}
2023-04-07 23:53:51 -07:00
} else {
2023-04-08 00:42:22 -07:00
// No fields, can't join
await somethingWentWrong ( bot , interaction , 'noFieldsInJoinMember' ) ;
2023-04-07 23:53:51 -07:00
}
} ;
2023-04-09 01:12:53 -07:00
// Join Request Approve/Deny Buttons
export const joinRequestResponseButtons = ( disabled : boolean ) : ActionRow [ ] = > [ {
type : MessageComponentTypes . ActionRow ,
components : [ {
type : MessageComponentTypes . Button ,
label : 'Approve Request' ,
style : ButtonStyles.Success ,
2023-04-14 12:52:45 -07:00
customId : ` ${ joinRequestCustomId } ${ idSeparator } ${ approveStr } ` ,
2023-04-09 01:12:53 -07:00
disabled ,
} , {
type : MessageComponentTypes . Button ,
label : 'Deny Request' ,
style : ButtonStyles.Danger ,
2023-04-14 12:52:45 -07:00
customId : ` ${ joinRequestCustomId } ${ idSeparator } ${ denyStr } ` ,
2023-04-09 01:12:53 -07:00
disabled ,
} ] ,
} ] ;