From 0af2ce21fa29c86bc3b23891bbdf1629681173ec Mon Sep 17 00:00:00 2001 From: tescher Date: Tue, 23 May 2023 13:34:52 -0500 Subject: [PATCH] Handle DM problems better, notify the assigned user when bounty is created (#108) * Handle DM problems better, notify the assigned user about their bounty when created * Clean up comments --- src/app/activity/bounty/Apply.ts | 4 +- src/app/activity/bounty/Assign.ts | 4 +- src/app/activity/bounty/Claim.ts | 12 ++-- src/app/activity/bounty/Complete.ts | 4 +- src/app/activity/bounty/Create.ts | 35 ++++++---- src/app/activity/bounty/Delete.ts | 2 +- src/app/activity/bounty/Help.ts | 4 +- src/app/activity/bounty/List.ts | 4 +- src/app/activity/bounty/Paid.ts | 4 +- src/app/activity/bounty/Publish.ts | 2 +- src/app/activity/bounty/Submit.ts | 4 +- src/app/activity/bounty/Tag.ts | 4 +- src/app/activity/user/RegisterWallet.ts | 5 +- src/app/commands/bounty/Bounty.ts | 9 +-- src/app/commands/bounty/IOU.ts | 4 +- src/app/events/InteractionCreate.ts | 2 +- src/app/requests/AssignRequest.ts | 17 ++++- src/app/utils/DiscordUtils.ts | 92 ++++++++++++++++--------- 18 files changed, 130 insertions(+), 82 deletions(-) diff --git a/src/app/activity/bounty/Apply.ts b/src/app/activity/bounty/Apply.ts index c8b7eb2..376d16b 100644 --- a/src/app/activity/bounty/Apply.ts +++ b/src/app/activity/bounty/Apply.ts @@ -109,9 +109,9 @@ export const finishApply = async (request: ApplyRequest, pitch: string) => { `Their pitch: ${pitch ? pitch : ''} \n` + 'Use the "/bounty assign" command to select an applicant who can claim.'; - await DiscordUtils.activityNotification(creatorDM, createdByUser, cardMessage.url); + await DiscordUtils.activityNotification(creatorDM, createdByUser, request.guildId, cardMessage.url); const activityMessage = `<@${applyingUser.user.id}>, You have applied for this bounty! Reach out to <@${createdByUser.id}> with any questions.`; - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, activityMessage, cardMessage.url); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, activityMessage, applyingUser.id, request.guildId, cardMessage.url); return; }; diff --git a/src/app/activity/bounty/Assign.ts b/src/app/activity/bounty/Assign.ts index def477d..883751f 100644 --- a/src/app/activity/bounty/Assign.ts +++ b/src/app/activity/bounty/Assign.ts @@ -25,8 +25,8 @@ export const assignBounty = async (request: AssignRequest): Promise => { let assignedContent = `You have been assigned this bounty! Click Claim It to claim. Reach out to <@${assigningUser.id}> with any questions.\n`; let assigneeBountyEmbed = await assigneeBountySummaryEmbed(cardMessage, request.guildId); - await DiscordUtils.activityNotification(assignedContent, assignedUser, cardMessage.url, assigneeBountyEmbed); - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, assigningContent, cardMessage.url); + await DiscordUtils.activityNotification(assignedContent, assignedUser, request.guildId, cardMessage.url, assigneeBountyEmbed); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, assigningContent, assigningUser.id, request.guildId, cardMessage.url); return; }; diff --git a/src/app/activity/bounty/Claim.ts b/src/app/activity/bounty/Claim.ts index d596d66..f1350e9 100644 --- a/src/app/activity/bounty/Claim.ts +++ b/src/app/activity/bounty/Claim.ts @@ -35,11 +35,11 @@ export const claimBounty = async (request: ClaimRequest): Promise => { } catch (e) { if (e instanceof ValidationError) { await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, `Unable to complete this operation.\n` + - 'Please try entering your wallet address with the command `/register-wallet` and then try claiming the bounty again.\n'); + 'Please try entering your wallet address with the command `/register-wallet` and then try claiming the bounty again.\n', request.userId, request.guildId); return; } if (e instanceof ModalTimeoutError) { - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, `Unable to complete this operation - form timeout.`); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, `Unable to complete this operation - form timeout.`, request.userId, request.guildId); return; } throw new RuntimeError(e); @@ -57,7 +57,7 @@ export const finishClaim = async (request: any) => { // Check to make sure they didn't enter DELETE when putting in the wallet address if ( !request.clientSyncRequest && walletStillNeeded ) { await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, `You must enter a wallet address to claim a bounty.\n` + - 'Please try entering your wallet address with the command `/register-wallet` and then try claiming the bounty again.\n'); + 'Please try entering your wallet address with the command `/register-wallet` and then try claiming the bounty again.\n', request.userId, request.guildId); return; } @@ -94,13 +94,13 @@ export const finishClaim = async (request: any) => { } const createdByUser = await DiscordUtils.getGuildMemberFromUserId(getDbResult.dbBountyResult.createdBy.discordId, request.guildId); - await DiscordUtils.activityNotification(creatorNotification, createdByUser, claimedBountyCard.url); + await DiscordUtils.activityNotification(creatorNotification, createdByUser, request.guildId, claimedBountyCard.url); const claimaintResponse = `<@${claimedByUser.user.id}>, you have claimed this bounty! Reach out to <@${createdByUser.user.id}> with any questions.`; if (!request.clientSyncRequest) { - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, claimaintResponse, claimedBountyCard.url); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, claimaintResponse, request.userId, request.guildId, claimedBountyCard.url); } else { - await DiscordUtils.activityNotification(claimaintResponse, claimedByUser, claimedBountyCard.url) + await DiscordUtils.activityNotification(claimaintResponse, claimedByUser, request.guildId, claimedBountyCard.url) if (walletStillNeeded) { const walletMessage = `Please click the button below to enter your ethereum wallet address (non-ENS) to receive the reward amount for this bounty`; const walletButton = new MessageButton().setStyle('SECONDARY').setCustomId('👛').setLabel('Register Wallet'); diff --git a/src/app/activity/bounty/Complete.ts b/src/app/activity/bounty/Complete.ts index 8aba4f1..f4e7236 100644 --- a/src/app/activity/bounty/Complete.ts +++ b/src/app/activity/bounty/Complete.ts @@ -52,8 +52,8 @@ export const completeBounty = async (request: CompleteRequest): Promise => if (!getDbResult.dbBountyResult.paidStatus || getDbResult.dbBountyResult.paidStatus === PaidStatus.unpaid) { submitterCompleteDM = submitterCompleteDM.concat(`<@${completedByUser.id}> should be paying you with ${getDbResult.dbBountyResult.reward.amount} ${getDbResult.dbBountyResult.reward.currency} soon.`); } - await DiscordUtils.activityNotification(submitterCompleteDM, submittedByUser, cardMessage.url); - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, creatorCompleteDM, cardMessage.url); + await DiscordUtils.activityNotification(submitterCompleteDM, submittedByUser, request.guildId, cardMessage.url); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, creatorCompleteDM, request.userId, request.guildId, cardMessage.url); return; } diff --git a/src/app/activity/bounty/Create.ts b/src/app/activity/bounty/Create.ts index a2344d0..ed2b0cd 100644 --- a/src/app/activity/bounty/Create.ts +++ b/src/app/activity/bounty/Create.ts @@ -11,8 +11,9 @@ import { BountyStatus } from '../../constants/bountyStatus'; import { Clients } from '../../constants/clients'; import { PaidStatus } from '../../constants/paidStatus'; import { Activities } from '../../constants/activities'; -import DMPermissionError from '../../errors/DMPermissionError'; import { ComponentType, ModalInteractionContext, TextInputStyle } from 'slash-create'; +import { AssignRequest } from '../../requests/AssignRequest'; +import { assignBounty } from './Assign'; export const createBounty = async (createRequest: CreateRequest): Promise => { Log.debug('In Create activity'); @@ -168,8 +169,8 @@ export const finishCreate = async (createRequest: CreateRequest, description: st if (createRequest.isIOU) { // await createRequest.commandContext.sendFollowUp({ content: "Your IOU was created." } , { ephemeral: true }); - const IOUContent = `<@${owedTo.id}> An IOU was created for you by <@${guildMember.user.id}>: ${cardMessage.url}`; - await owedTo.send({ content: IOUContent }).catch(() => { throw new DMPermissionError(IOUContent) }); + const IOUContent = `<@${owedTo.id}> An IOU was created for you by <@${guildMember.user.id}>:`; + await DiscordUtils.activityNotification(IOUContent, owedTo, createRequest.guildId, cardMessage.url); const walletNeeded = !(await BountyUtils.userWalletRegistered(owedTo.id)); @@ -180,13 +181,29 @@ export const finishCreate = async (createRequest: CreateRequest, description: st const iouWalletMessage = `Please click the button below to enter your ethereum wallet address (non-ENS) to receive the reward amount for this bounty`; const walletButton = new MessageButton().setStyle('SECONDARY').setCustomId('👛').setLabel('Register Wallet'); - await owedTo.send({ content: iouWalletMessage, components: [new MessageActionRow().addComponents(walletButton)] }); + await DiscordUtils.attemptDM({ content: iouWalletMessage, components: [new MessageActionRow().addComponents(walletButton)] }, owedTo, createRequest.guildId); } await DiscordUtils.activityResponse(createRequest.commandContext, null, 'IOU created successfully.' + (walletNeeded ? "\n" + - `${owedTo} has not registered a wallet. Remind them to check their DMs for a register wallet button, or to use the /register-wallet command.` : "")); + `${owedTo} has not registered a wallet. Remind them to check their DMs for a register wallet button, or to use the /register-wallet command.` : ""), createRequest.userId, createRequest.guildId); } else { await modalContext?.send('Bounty created, see below...'); + if (createRequest.assign) { + const assignRequest = new AssignRequest({ + commandContext: null, + messageReactionRequest: null, + buttonInteraction: null, + directRequest: { + guildId: createRequest.guildId, + bountyId: newBounty._id, + userId: createRequest.userId, + assign: createRequest.assign, + activity: Activities.assign, + bot: false, + } + }); + await assignBounty(assignRequest); + } return; } } @@ -310,14 +327,6 @@ export const generateBountyRecord = async ( } } - if (createRequest.assign) { - bountyRecord.assignTo = { - discordId: assignedTo.user.id, - discordHandle: assignedTo.user.tag, - iconUrl: assignedTo.user.avatarURL(), - } - } - if (createRequest.requireApplication) { bountyRecord.requireApplication = true; } diff --git a/src/app/activity/bounty/Delete.ts b/src/app/activity/bounty/Delete.ts index a278a02..20e1ece 100644 --- a/src/app/activity/bounty/Delete.ts +++ b/src/app/activity/bounty/Delete.ts @@ -35,7 +35,7 @@ export const deleteBounty = async (request: DeleteRequest): Promise => { getDbResult.dbBountyResult.childrenIds !== undefined && getDbResult.dbBountyResult.childrenIds.length > 0) { creatorDeleteDM += 'Children bounties created from this multi-claimant bounty will remain.\n'; } - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, creatorDeleteDM); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, creatorDeleteDM, request.userId, request.guildId); return; } diff --git a/src/app/activity/bounty/Help.ts b/src/app/activity/bounty/Help.ts index 13383d5..58b543a 100644 --- a/src/app/activity/bounty/Help.ts +++ b/src/app/activity/bounty/Help.ts @@ -32,8 +32,8 @@ export const helpBounty = async (request: HelpRequest): Promise => { const userHelpDM = `<@${bountyCreator.id}> has been notified of your request for help with the following bounty.\n`; - await DiscordUtils.activityNotification(creatorHelpDM, bountyCreator, bountyUrl); - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, userHelpDM, bountyUrl); + await DiscordUtils.activityNotification(creatorHelpDM, bountyCreator, request.guildId, bountyUrl); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, userHelpDM, request.userId, request.guildId, bountyUrl); } else { const bountyChannel: TextChannel = await client.channels.fetch(request.commandContext.channelID) as TextChannel; diff --git a/src/app/activity/bounty/List.ts b/src/app/activity/bounty/List.ts index c771b70..13f0ae8 100644 --- a/src/app/activity/bounty/List.ts +++ b/src/app/activity/bounty/List.ts @@ -151,7 +151,7 @@ export const listBounty = async (request: ListRequest, preventResponse ?: boolea if (!!request.message) { // List from a refresh reaction listMessage = request.message; await listMessage.edit({ embeds: [listCard], components: [componentActions] }); - !preventResponse && await DiscordUtils.activityResponse(null, request.buttonInteraction, 'Bounty list refreshed successfully'); + !preventResponse && await DiscordUtils.activityResponse(null, request.buttonInteraction, 'Bounty list refreshed successfully', request.userId, request.guildId); } else { // List from a slash command const channel = await DiscordUtils.getTextChannelfromChannelId(request.commandContext.channelID); listMessage = await channel.send({ embeds: [listCard], components: [componentActions] }); @@ -176,7 +176,7 @@ export const listBounty = async (request: ListRequest, preventResponse ?: boolea } catch (e) { throw new DMPermissionError(e); } - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, 'Please check your DM for bounty list'); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, 'Please check your DM for bounty list', request.userId, request.guildId); } }; diff --git a/src/app/activity/bounty/Paid.ts b/src/app/activity/bounty/Paid.ts index 300c3f2..f7bc9ed 100644 --- a/src/app/activity/bounty/Paid.ts +++ b/src/app/activity/bounty/Paid.ts @@ -36,8 +36,8 @@ export const paidBounty = async (request: PaidRequest): Promise => { `Your bounty has been marked as paid: ${getDbResult.dbBountyResult.title}.\n` + `If you don't see a payment post, contact the bounty creator <@${paidByUser.user.id}>`; - await DiscordUtils.activityNotification(payeeDMContent, payee, cardMessage.url); - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, creatorPaidDM, cardMessage.url); + await DiscordUtils.activityNotification(payeeDMContent, payee, request.guildId, cardMessage.url); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, creatorPaidDM, request.userId, request.guildId, cardMessage.url); return; } diff --git a/src/app/activity/bounty/Publish.ts b/src/app/activity/bounty/Publish.ts index d9f9e41..3a21d12 100644 --- a/src/app/activity/bounty/Publish.ts +++ b/src/app/activity/bounty/Publish.ts @@ -38,7 +38,7 @@ export const publishBounty = async (publishRequest: PublishRequest): Promise`, bountyMessage.url); + await DiscordUtils.activityResponse(publishRequest.commandContext, publishRequest.buttonInteraction, `Bounty published to \`${(bountyMessage.channel as any).name || bountyChannel.name}\` and the website! <${process.env.BOUNTY_BOARD_URL}${bountyId}>`, publishRequest.userId, publishRequest.guildId, bountyMessage.url); } Log.info(`bounty published to ${(bountyMessage.channel as any).name || bountyChannel.name}`); diff --git a/src/app/activity/bounty/Submit.ts b/src/app/activity/bounty/Submit.ts index 44b45c1..b5c3cb0 100644 --- a/src/app/activity/bounty/Submit.ts +++ b/src/app/activity/bounty/Submit.ts @@ -83,8 +83,8 @@ export const submitBounty = async (request: SubmitRequest): Promise => { if (request.notes) { creatorSubmitDM += `\nNotes included in submission:\n${request.notes}` } - await DiscordUtils.activityNotification(creatorSubmitDM, createdByUser, cardMessage.url); - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, `Bounty in review! Expect a message from <@${createdByUser.id}>.`, cardMessage.url); + await DiscordUtils.activityNotification(creatorSubmitDM, createdByUser, request.guildId, cardMessage.url); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, `Bounty in review! Expect a message from <@${createdByUser.id}>.`, request.userId, request.guildId, cardMessage.url); return; } diff --git a/src/app/activity/bounty/Tag.ts b/src/app/activity/bounty/Tag.ts index 1fb99cf..018fefb 100644 --- a/src/app/activity/bounty/Tag.ts +++ b/src/app/activity/bounty/Tag.ts @@ -26,7 +26,9 @@ export const tagBounty = async (request: TagRequest): Promise => { await DiscordUtils.activityResponse( request.commandContext, request.buttonInteraction, - tagResponse, + tagResponse, + request.userId, + request.guildId, bountyCard.url ); return; diff --git a/src/app/activity/user/RegisterWallet.ts b/src/app/activity/user/RegisterWallet.ts index 1419e1a..61dd4c0 100644 --- a/src/app/activity/user/RegisterWallet.ts +++ b/src/app/activity/user/RegisterWallet.ts @@ -125,14 +125,15 @@ export const upsertUserWallet = async (request: UpsertUserWalletRequest): Promis } export const finishRegister = async (request: UpsertUserWalletRequest) => { + // Not passing user to activityResponse - don't want this on the public channel even if the DM fails if (ADDRESS_DELETE_REGEX.test(request.address)) { - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, "Your wallet address has been deleted."); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, "Your wallet address has been deleted.", null, null); } else { const activityMessage = `<@${request.userDiscordId}>, your wallet address has been registered as ${request.address}.\n`+ `You can change it by using the /register-wallet command.`; const etherscanUrl = `https://etherscan.io/address/${request.address}`; - await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, activityMessage, etherscanUrl, "View on Etherscan"); + await DiscordUtils.activityResponse(request.commandContext, request.buttonInteraction, activityMessage, null, null, etherscanUrl, "View on Etherscan"); } } diff --git a/src/app/commands/bounty/Bounty.ts b/src/app/commands/bounty/Bounty.ts index 4188e02..3b7be9a 100644 --- a/src/app/commands/bounty/Bounty.ts +++ b/src/app/commands/bounty/Bounty.ts @@ -306,14 +306,6 @@ export default class Bounty extends SlashCommand { buttonInteraction: null, }); break; - case Activities.assign: - request = new AssignRequest({ - commandContext: commandContext, - messageReactionRequest: null, - buttonInteraction: null, - - }); - break; case Activities.apply: request = new ApplyRequest({ commandContext: commandContext, @@ -326,6 +318,7 @@ export default class Bounty extends SlashCommand { commandContext: commandContext, messageReactionRequest: null, buttonInteraction: null, + directRequest: null, }); break; case Activities.submit: diff --git a/src/app/commands/bounty/IOU.ts b/src/app/commands/bounty/IOU.ts index 4523734..e92f65b 100644 --- a/src/app/commands/bounty/IOU.ts +++ b/src/app/commands/bounty/IOU.ts @@ -81,9 +81,9 @@ export default class IOU extends SlashCommand { } catch (e) { if (e instanceof ValidationError) { - await DiscordUtils.activityResponse(commandContext, null, `<@${commandContext.user.id}>\n` + e.message); + await DiscordUtils.activityResponse(commandContext, null, `<@${commandContext.user.id}>\n` + e.message, request.userId, request.guildId); } else if (e instanceof AuthorizationError) { - await DiscordUtils.activityResponse(commandContext, null, `<@${commandContext.user.id}>\n` + e.message); + await DiscordUtils.activityResponse(commandContext, null, `<@${commandContext.user.id}>\n` + e.message, request.userId, request.guildId); } else if (e instanceof NotificationPermissionError) { await ErrorUtils.sendToDefaultChannel(e.message, request); } else if (e instanceof DMPermissionError) { diff --git a/src/app/events/InteractionCreate.ts b/src/app/events/InteractionCreate.ts index 1676091..eeb9063 100644 --- a/src/app/events/InteractionCreate.ts +++ b/src/app/events/InteractionCreate.ts @@ -272,7 +272,7 @@ export default class implements DiscordEvent { errorContent = 'Sorry something is not working and our devs are looking into it.'; } - return await DiscordUtils.interactionResponse(interaction, errorContent); + return await DiscordUtils.interactionResponse(interaction, {content: errorContent}); } } } \ No newline at end of file diff --git a/src/app/requests/AssignRequest.ts b/src/app/requests/AssignRequest.ts index 978c69a..e463a91 100644 --- a/src/app/requests/AssignRequest.ts +++ b/src/app/requests/AssignRequest.ts @@ -12,10 +12,20 @@ export class AssignRequest extends Request { message: Message; buttonInteraction: ButtonInteraction; + constructor(args: { commandContext: CommandContext, messageReactionRequest: MessageReactionRequest, buttonInteraction: ButtonInteraction, + directRequest: { + guildId: string, + bountyId: string, + userId: string, + assign: string, + activity: string + bot: boolean, + } + }) { if (args.commandContext) { @@ -27,7 +37,12 @@ export class AssignRequest extends Request { this.bountyId = args.commandContext.options.assign['bounty-id']; this.assign = args.commandContext.options.assign['for-user']; - } else { + } else if (args.directRequest) { + const { guildId, bountyId, userId, assign, activity, bot } = args.directRequest; + super (activity, guildId, userId, bot); + this.bountyId = bountyId; + this.assign = assign; + } else { // TODO add flow to assign though message reaction throw new Error('Assign context is required to be not null for AssignRequest construction.'); } diff --git a/src/app/utils/DiscordUtils.ts b/src/app/utils/DiscordUtils.ts index 9a9591b..f2597c6 100644 --- a/src/app/utils/DiscordUtils.ts +++ b/src/app/utils/DiscordUtils.ts @@ -13,7 +13,7 @@ import { ListRequest } from '../requests/ListRequest'; import { CustomerCollection } from '../types/bounty/CustomerCollection'; import MongoDbUtils from '../utils/MongoDbUtils'; import BountyUtils from './BountyUtils'; -import { LogUtils } from './Log'; +import Log, { LogUtils } from './Log'; @@ -151,17 +151,8 @@ const DiscordUtils = { return messages.first(); }, - async interactionResponse(buttonInteraction: ButtonInteraction, content: string, link?: string, linkTitle?: string) { - let replyOptions: MessageOptions = { content: content }; - if (link) { - const componentActions = new MessageActionRow().addComponents( - new MessageButton() - .setLabel(linkTitle ? linkTitle : 'View Bounty') - .setStyle('LINK') - .setURL(link || '') - ); - replyOptions.components = [componentActions]; - } + async interactionResponse(buttonInteraction: ButtonInteraction, replyOptions: MessageOptions) { + try { if ((buttonInteraction.deferred || buttonInteraction.replied)) await buttonInteraction.followUp(Object.assign(replyOptions, { ephemeral: true }) as InteractionReplyOptions); else await buttonInteraction.reply(Object.assign(replyOptions, { ephemeral: true }) as InteractionReplyOptions); @@ -172,8 +163,24 @@ const DiscordUtils = { }, // Send a response to a command (use ephemeral) or a reaction (use the context) or if neither, treat it as an activityNotification instead - async activityResponse(commandContext: CommandContext, buttonInteraction: ButtonInteraction, content: string, link?: string, linkTitle?: string): Promise { - if (!!commandContext) { // This was a slash command + async activityResponse(commandContext: CommandContext, buttonInteraction: ButtonInteraction, content: string, userId: string, customerId: string, link?: string, linkTitle?: string): Promise { + if (!commandContext) { // Either a button interaction or a direct message + let replyOptions: MessageOptions = { content: content }; + if (link) { + const componentActions = new MessageActionRow().addComponents( + new MessageButton() + .setLabel(linkTitle ? linkTitle : 'View Bounty') + .setStyle('LINK') + .setURL(link || '') + ); + replyOptions.components = [componentActions]; + } + if (buttonInteraction){ + await this.interactionResponse(buttonInteraction, replyOptions); + } else { + await this.attemptDM(replyOptions, userId, customerId); + } + } else { // This was a slash command const btnComponent = (link ? [{ type: ComponentType.ACTION_ROW, components: [{ @@ -184,38 +191,59 @@ const DiscordUtils = { }] }] : []) as ComponentActionRow[]; await commandContext.send({ content: content, ephemeral: true, components: btnComponent }); - } else { // This was a button interaction - await this.interactionResponse(buttonInteraction, content, link, linkTitle); - } + } }, // Send a notification to an interested party (use a DM) async activityNotification( content: string, toUser: GuildMember, + customerId: string, link: string, bountyCard?: { embeds: MessageEmbedOptions, buttons: MessageButton[] } ): Promise { + const linkButton = new MessageButton() + .setLabel('View Bounty') + .setStyle('LINK') + .setURL(link || ''); + + await this.attemptDM({ + content, + embeds: bountyCard ? [bountyCard.embeds] : [], + components: bountyCard + ? [new MessageActionRow().addComponents( + bountyCard.buttons.concat(linkButton) + )] + : link && [new MessageActionRow().addComponents(linkButton)] + }, toUser, customerId); + }, + + async attemptDM(content: string | MessageOptions, user: string | GuildMember, customerId: string): Promise { + let guildMember: GuildMember; + if (typeof user === "string") { + guildMember = await this.getGuildMemberFromUserId(user, customerId); + } else { + guildMember = user; + } try { - const linkButton = new MessageButton() - .setLabel('View Bounty') - .setStyle('LINK') - .setURL(link || ''); - - await toUser.send({ - content, - embeds: bountyCard ? [bountyCard.embeds] : [], - components: bountyCard - ? [new MessageActionRow().addComponents( - bountyCard.buttons.concat(linkButton) - )] - : link && [new MessageActionRow().addComponents(linkButton)] - }); + await guildMember.send(content); } catch (e) { - throw new NotificationPermissionError(content); + if (!user || !customerId) { + Log.error(`Cannot send DM, no user or customer given.\nContent: ${JSON.stringify(content)}\ne.message`); + } else { + Log.info(`Attempt to send DM to ${guildMember.displayName} failed with ${e.message}. Attempting channel message instead.`); + const bountyChannel = await DiscordUtils.getBountyChannelfromCustomerId(customerId); + const kick = `<@${guildMember.id}>, allow DMs from the Bounty Bot, I'm trying to send you a message ;). Here it is:\n` + if (typeof content === "string") { + content = kick + content; + } else { + content.content = kick + content.content; + } + await bountyChannel.send(content); + } } },