diff --git a/apps/bot/src/app/commands/(User)/hevy.tsx b/apps/bot/src/app/commands/(User)/hevy.tsx index ae0bb5b..78531d0 100644 --- a/apps/bot/src/app/commands/(User)/hevy.tsx +++ b/apps/bot/src/app/commands/(User)/hevy.tsx @@ -69,6 +69,7 @@ export const chatInput: ChatInputCommand = async ({ interaction }) => { const pendingUserVerification = await findOrCreateUserVerification( discordUserId, hevyUsername, + interaction as unknown as ChatInputCommandInteraction, ); if (!pendingUserVerification) { diff --git a/apps/bot/src/features/core/user.service.ts b/apps/bot/src/features/core/user.service.ts index fb28fe6..f70496f 100644 --- a/apps/bot/src/features/core/user.service.ts +++ b/apps/bot/src/features/core/user.service.ts @@ -2,13 +2,14 @@ import { cacheLife, cacheTag, revalidateTag } from "@commandkit/cache"; import { HevyVerification, prisma } from "@repo/db"; import client from "@/app"; -import { MessageFlags } from "discord.js"; +import { MessageFlags, TextChannel, User } from "discord.js"; import { successfulyLinkedToHevy } from "../hevy/hevy.embeds"; import { getUserByDiscordId, UserWithHevyVerification, } from "../hevy/hevy.service"; import { generatePrivateFollowInstructionsComponents } from "../hevy/verification.embeds"; +import { Logger } from "commandkit"; export const setAutoShareEnabledStatus = async ( guildId: string, user: UserWithHevyVerification, @@ -77,18 +78,58 @@ export const getUserAutoShareConfig = async ( }); }; -export const sendSuccessfullVerificationDM = async ( +export const sendSuccessfullVerificationMessage = async ( verification: HevyVerification, ) => { const user = client.users.cache.get(verification.userDiscordId); if (user) { - await user.send({ - flags: MessageFlags.IsComponentsV2, - components: successfulyLinkedToHevy(verification, false), - }); + Logger.info(`Sending verification DM`); + try { + await sendSuccessfullVerificationDM(user, verification); + } catch (error) { + Logger.warn(error); + Logger.warn( + `Failed to send verification DM. Replying to original interaction.`, + ); + await sendSuccessfullVerificationViaInteraction(verification); + } } }; +export const sendSuccessfullVerificationDM = async ( + user: User, + verification: HevyVerification, +) => { + await user.send({ + flags: MessageFlags.IsComponentsV2, + components: successfulyLinkedToHevy(verification, false), + }); +}; + +export const sendSuccessfullVerificationViaInteraction = async ( + verification: HevyVerification, +) => { + if ( + verification.originalInteractionChannelId == null || + verification.originalInteractionId == null + ) + return; + const channel = (await client.channels.fetch( + verification.originalInteractionChannelId, + )) as TextChannel; + if (!channel) return; + + const interaction = await channel.messages.fetch( + verification.originalInteractionId, + ); + + await interaction.reply({ + flags: MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral, + + components: successfulyLinkedToHevy(verification, false), + }); +}; + export const sendPrivateAccountInstructionsDM = async ( userDiscordId: string, ) => { diff --git a/apps/bot/src/features/hevy/verification.service.ts b/apps/bot/src/features/hevy/verification.service.ts index 2b02520..181a96d 100644 --- a/apps/bot/src/features/hevy/verification.service.ts +++ b/apps/bot/src/features/hevy/verification.service.ts @@ -5,7 +5,7 @@ import { Logger } from "commandkit"; import { track } from "commandkit/analytics"; import { sendPrivateAccountInstructionsDM, - sendSuccessfullVerificationDM, + sendSuccessfullVerificationMessage, } from "../core/user.service"; import { sendActivity } from "../liveActivity/liveActivity.service"; import { @@ -20,6 +20,7 @@ import { setIsHevyProfilePrivate, setUserHevyUsername, } from "./hevy.service"; +import { ChatInputCommandInteraction } from "discord.js"; const MAX_CODE_GENERATION_ATTEMPTS = 10; @@ -38,6 +39,7 @@ export const generateVerificationCode = (length: number) => { export const insertVerificationCode = async ( discordId: string, hevyUsername: string, + interaction?: ChatInputCommandInteraction, ) => { Logger.info(`Inserting verification code: ${discordId} - ${hevyUsername}`); let verificationCode = ""; @@ -61,6 +63,8 @@ export const insertVerificationCode = async ( workoutId: verificationConfig.workoutShortId, userDiscordId: discordId, username: hevyUsername, + originalInteractionId: interaction?.id, + originalInteractionChannelId: interaction?.channelId, }, }); @@ -141,6 +145,7 @@ export const getRemainingPendingVerifications = async (): Promise< export const findOrCreateUserVerification = async ( discordId: string, hevyUsername: string, + interaction?: ChatInputCommandInteraction, ) => { let hevyVerification = await getUserLatestPendingVerification( discordId, @@ -150,7 +155,8 @@ export const findOrCreateUserVerification = async ( if (hevyVerification == null) { Logger.info(`No verification for this user. Creating one.`); hevyVerification = - (await insertVerificationCode(discordId, hevyUsername)) ?? null; + (await insertVerificationCode(discordId, hevyUsername, interaction)) ?? + null; } else { Logger.info(`User verification already exists. Returning it.`); } @@ -215,7 +221,7 @@ export const executeVerificationTask = async () => { verification.userDiscordId, hevyProfile.private_profile, ); - await sendSuccessfullVerificationDM(verification); + await sendSuccessfullVerificationMessage(verification); await deleteComment(correspondingComment.id); } } diff --git a/packages/database/prisma/migrations/20251017122836_verification_original_interaction_id/migration.sql b/packages/database/prisma/migrations/20251017122836_verification_original_interaction_id/migration.sql new file mode 100644 index 0000000..a46f2fe --- /dev/null +++ b/packages/database/prisma/migrations/20251017122836_verification_original_interaction_id/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "public"."HevyVerification" ADD COLUMN "orignalInteractionId" TEXT; diff --git a/packages/database/prisma/migrations/20251017124254_store_original_interaction_to_reply_to_it/migration.sql b/packages/database/prisma/migrations/20251017124254_store_original_interaction_to_reply_to_it/migration.sql new file mode 100644 index 0000000..f47a5ed --- /dev/null +++ b/packages/database/prisma/migrations/20251017124254_store_original_interaction_to_reply_to_it/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `orignalInteractionId` on the `HevyVerification` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "public"."HevyVerification" DROP COLUMN "orignalInteractionId", +ADD COLUMN "originalInteractionChannelId" TEXT, +ADD COLUMN "originalInteractionId" TEXT; diff --git a/packages/database/prisma/schema.prisma b/packages/database/prisma/schema.prisma index 7415bf5..f4f68a6 100644 --- a/packages/database/prisma/schema.prisma +++ b/packages/database/prisma/schema.prisma @@ -30,17 +30,19 @@ enum UserVerificationStatus { } model HevyVerification { - id String @id @unique @default(cuid()) - lastCheck DateTime? - workoutId String - verificationCode String @unique - status UserVerificationStatus @default(pending) - User User @relation(fields: [userDiscordId], references: [discordId]) - userDiscordId String @unique - username String - privateProfile Boolean @default(false) - followedByBot Boolean @default(false) - privateInstructionsSent Boolean @default(false) + id String @id @unique @default(cuid()) + lastCheck DateTime? + workoutId String + verificationCode String @unique + status UserVerificationStatus @default(pending) + User User @relation(fields: [userDiscordId], references: [discordId]) + userDiscordId String @unique + username String + privateProfile Boolean @default(false) + followedByBot Boolean @default(false) + privateInstructionsSent Boolean @default(false) + originalInteractionId String? + originalInteractionChannelId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt