From 6294fa1fd0c3b311d4bf5025534544300bd85709 Mon Sep 17 00:00:00 2001 From: theimperious1 Date: Wed, 13 Nov 2024 15:21:05 -0500 Subject: [PATCH 1/7] (tripsitme): Remove warmline, fireside issue section. Rename close btn. Remove issue field. Small tweaks. --- src/discord/commands/global/d.tripsitmode.ts | 2 +- src/discord/utils/tripsitme.ts | 35 +++++++------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/discord/commands/global/d.tripsitmode.ts b/src/discord/commands/global/d.tripsitmode.ts index 0c5fa087e..d43d5b538 100644 --- a/src/discord/commands/global/d.tripsitmode.ts +++ b/src/discord/commands/global/d.tripsitmode.ts @@ -342,7 +342,7 @@ async function tripsitmodeOn( Click here to be taken to their private room: ${threadHelpUser} - You can also click in your channel list to see your private room!`; + You can also click in your channel list to see their private room!`; const embed = embedTemplate() .setColor(Colors.DarkBlue) .setDescription(replyMessage); diff --git a/src/discord/utils/tripsitme.ts b/src/discord/utils/tripsitme.ts index 5e2efeff5..763f80d6d 100644 --- a/src/discord/utils/tripsitme.ts +++ b/src/discord/utils/tripsitme.ts @@ -1165,7 +1165,7 @@ export async function tripSitMe( interaction:ModalSubmitInteraction, memberInput:GuildMember | null, triage:string, - intro:string, + intro: string = '', ):Promise { log.info(F, await commandContext(interaction)); // await interaction.deferReply({ ephemeral: true }); @@ -1306,24 +1306,18 @@ export async function tripSitMe( You've taken: ${triage ? `\n${triage}` : noInfo} - Your issue: ${intro ? `\n${intro}` : noInfo} - Someone from the ${roleTripsitter} ${guildData.role_helper && !targetIsTeamMember ? `and/or ${roleHelper}` : ''} team will be with you as soon as they're available! If this is a medical emergency please contact your local emergency services: we do not call EMS on behalf of anyone. When you're feeling better you can use the "I'm Good" button to let the team know you're okay. - - **Not in an emergency, but still want to talk to a mental health advisor? Warm lines provide non-crisis mental health support and guidance from trained volunteers. https://warmline.org/warmdir.html#directory** - - **The wonderful people at the Fireside project can also help you through a rough trip. You can check them out: https://firesideproject.org/** `; const row = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId(`tripsitmeUserClose~${target.id}`) - .setLabel('I\'m good now!') + .setLabel('I no longer need help') .setStyle(ButtonStyle.Success), ); await threadHelpUser.send({ @@ -1350,13 +1344,15 @@ export async function tripSitMe( // log.debug(F, `Sent intro message to ${threadHelpUser.name} ${threadHelpUser.id}`); + const issue = intro ? `**Their issue: ** \n${intro}` : ''; + // Send an embed to the tripsitter room const embedTripsitter = embedTemplate() .setColor(Colors.DarkBlue) .setDescription(stripIndents` - ${target} has requested assistance! - **They've taken:** ${triage ? `${triage}` : noInfo} - **Their issue: ** ${intro ? `${intro}` : noInfo} + A tripsitter has put ${target} into tripsitmode! + **They've taken:** ${triage ? `\n${triage}` : noInfo} + ${issue} **Read the log before interacting** Use this channel coordinate efforts. @@ -1421,14 +1417,14 @@ export async function tripSitMe( update: {}, }); + const description = triage ? `\n${triage}` : noInfo; + // Set ticket information - const introStr = intro ? `\n${intro}` : noInfo; const newTicketData = { user_id: userData.id, description: ` - They've taken: ${triage ? `\n${triage}` : noInfo} - - Their issue: ${introStr}`, + They've taken: ${description} + ${issue}`, thread_id: threadHelpUser.id, type: 'TRIPSIT', status: 'OPEN', @@ -1759,12 +1755,6 @@ export async function tripsitmeButton( .setLabel('What substance? How much taken? How long ago?') .setMaxLength(120) .setStyle(TextInputStyle.Short)), - new ActionRowBuilder() - .addComponents(new TextInputBuilder() - .setCustomId('introInput') - .setLabel('What\'s going on? Give us the details!') - .setMaxLength(1100) - .setStyle(TextInputStyle.Paragraph)), )); const filter = (i:ModalSubmitInteraction) => i.customId.startsWith('tripsitmeSubmit'); @@ -1773,9 +1763,8 @@ export async function tripsitmeButton( if (i.customId.split('~')[1] !== interaction.id) return; await i.deferReply({ ephemeral: true }); const triage = i.fields.getTextInputValue('triageInput'); - const intro = i.fields.getTextInputValue('introInput'); - const threadHelpUser = await tripSitMe(i, target, triage, intro) as ThreadChannel; + const threadHelpUser = await tripSitMe(i, target, triage) as ThreadChannel; if (!threadHelpUser) { const embed = embedTemplate() From 1395881f4d23becd3a59fef3df4edb6f0af6793f Mon Sep 17 00:00:00 2001 From: theimperious1 Date: Wed, 13 Nov 2024 15:40:05 -0500 Subject: [PATCH 2/7] Update intro placeholder, references to "Im good" btn, spacing issues --- src/discord/commands/global/d.tripsitmode.ts | 2 +- src/discord/utils/tripsitme.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/discord/commands/global/d.tripsitmode.ts b/src/discord/commands/global/d.tripsitmode.ts index d43d5b538..36b70cd15 100644 --- a/src/discord/commands/global/d.tripsitmode.ts +++ b/src/discord/commands/global/d.tripsitmode.ts @@ -323,7 +323,7 @@ async function tripsitmodeOn( new ActionRowBuilder().addComponents(new TextInputBuilder() .setCustomId('introInput') .setLabel('What\'s going on with them?') - .setPlaceholder('This will be posted in the channel for them to see!') + .setPlaceholder('This will only be visible to helpers and TripSitters.') .setStyle(TextInputStyle.Paragraph)), )); diff --git a/src/discord/utils/tripsitme.ts b/src/discord/utils/tripsitme.ts index 763f80d6d..9641f70f9 100644 --- a/src/discord/utils/tripsitme.ts +++ b/src/discord/utils/tripsitme.ts @@ -845,7 +845,7 @@ export async function tripsitmeUserClose( const target = await interaction.guild.members.fetch(targetId); const actor = interaction.member as GuildMember; await actor.fetch(); - log.debug(F, `${actor.displayName} (${actor.id}) clicked "I'm good" in ${target.displayName}'s (${target.id}) session `); + log.debug(F, `${actor.displayName} (${actor.id}) clicked "I no longer need help" in ${target.displayName}'s (${target.id}) session `); if (targetId !== actor.id && !override) { log.debug(F, 'They did not create the thread, so I am not doing anything!'); @@ -1310,7 +1310,7 @@ export async function tripSitMe( If this is a medical emergency please contact your local emergency services: we do not call EMS on behalf of anyone. - When you're feeling better you can use the "I'm Good" button to let the team know you're okay. + When you're feeling better you can use the "I no longer need help" button to let the team know you're okay. `; const row = new ActionRowBuilder() @@ -1350,10 +1350,10 @@ export async function tripSitMe( const embedTripsitter = embedTemplate() .setColor(Colors.DarkBlue) .setDescription(stripIndents` - A tripsitter has put ${target} into tripsitmode! + ${issue ? `A tripsitter has put ${target} into tripsitmode!\n` : `${target} has requested assistance!\n`} **They've taken:** ${triage ? `\n${triage}` : noInfo} - ${issue} + ${intro !== '' ? `${issue}\n` : ''} **Read the log before interacting** Use this channel coordinate efforts. From 15d4bea78e245f9543c5dd11f83d679a23185262 Mon Sep 17 00:00:00 2001 From: theimperious1 Date: Thu, 14 Nov 2024 01:39:15 -0500 Subject: [PATCH 3/7] (tripsit): add delete ticket button, small tweaks --- src/discord/events/buttonClick.ts | 6 ++ src/discord/utils/tripsitme.ts | 157 ++++++++++++++++++++++++++++-- 2 files changed, 156 insertions(+), 7 deletions(-) diff --git a/src/discord/events/buttonClick.ts b/src/discord/events/buttonClick.ts index 0adb9a567..ae987c9db 100644 --- a/src/discord/events/buttonClick.ts +++ b/src/discord/events/buttonClick.ts @@ -12,6 +12,7 @@ import { tripsitmeBackup, tripsitmeTeamClose, tripsitmeUserClose, + tripsitmeUserDelete, } from '../utils/tripsitme'; import { techHelpClick, techHelpClose, techHelpOwn } from '../utils/techHelp'; // import { @@ -183,6 +184,11 @@ export async function buttonClick(interaction:ButtonInteraction, discordClient:C return; } + if (buttonID.startsWith('tripsitmeUserDelete')) { + tripsitmeUserDelete(interaction); + return; + } + if (buttonID.startsWith('tripsitmeMeta')) { tripsitmeMeta(interaction); return; diff --git a/src/discord/utils/tripsitme.ts b/src/discord/utils/tripsitme.ts index 9641f70f9..4ce4c4e36 100644 --- a/src/discord/utils/tripsitme.ts +++ b/src/discord/utils/tripsitme.ts @@ -117,7 +117,7 @@ const memberOnly = 'This must be performed by a member of a guild!'; # Initialize * As a user, create a new ticket Click the I need help button - FIll in information + Fill in information Click submit - On BL your roles are not removed - On other guilds your roles are removed @@ -754,8 +754,11 @@ export async function tripsitmeTeamClose( } const closeMessage = stripIndents`Hey ${target}, it looks like you're doing somewhat better! + This thread will remain here for a day if you want to follow up tomorrow. - After 7 days, or on request, it will be deleted to preserve your privacy =) + After 7 days, it will be deleted to preserve your privacy =) + Alternatively, you may press the "Delete my ticket" button to delete it yourself. + If you'd like to go back to social mode, just click the button below! `; @@ -763,7 +766,7 @@ export async function tripsitmeTeamClose( .addComponents( new ButtonBuilder() .setCustomId(`tripsitmeUserClose~${targetId}`) - .setLabel('I\'m good now!') + .setLabel('I no longer need help.') .setStyle(ButtonStyle.Success), ); @@ -1076,10 +1079,13 @@ export async function tripsitmeUserClose( // Send the end message to the user try { - await interaction.editReply(stripIndents`Hey ${target}, we're glad you're doing better! - We've restored your old roles back to normal <3 - This thread will remain here for a day if you want to follow up tomorrow. - After 7 days, or on request, it will be deleted to preserve your privacy =)`); + await interaction.editReply(stripIndents`Hey ${target}, it looks like you're doing somewhat better! + + This thread will remain here for a day if you want to follow up tomorrow. + After 7 days, it will be deleted to preserve your privacy =) + Alternatively, you may press the "Delete my ticket" button to delete it yourself. + + If you'd like to go back to social mode, just click the button below!`); } catch (err) { log.error(F, `Error sending end help message to ${threadHelpUser}`); log.error(F, err as string); @@ -1154,6 +1160,138 @@ export async function tripsitmeUserClose( log.debug(F, 'Updated ticket status to CLOSED'); } +/** + * Handles deleting the thread and marking it as deleted + * @param {ButtonInteraction} interaction + */ +export async function tripsitmeUserDelete( + interaction:ButtonInteraction, +) { + if (!interaction.guild) return; + if (!interaction.member) return; + if (!interaction.channel) return; + log.info(F, await commandContext(interaction)); + + await interaction.deferReply({ ephemeral: false }); + + const targetId = interaction.customId.split('~')[1]; + const override = interaction.customId.split('~')[0] === 'tripsitmodeOffOverride'; + + const target = await interaction.guild.members.fetch(targetId); + const actor = interaction.member as GuildMember; + await actor.fetch(); + log.debug(F, `${actor.displayName} (${actor.id}) clicked "Delete my ticket" in ${target.displayName}'s (${target.id}) session `); + + if (targetId !== actor.id && !override) { + log.debug(F, 'They did not create the thread, so I am not doing anything!'); + await interaction.editReply({ content: 'Only the user receiving help can click this button!' }); + return; + } + + const userData = await db.users.upsert({ + where: { + discord_id: target.id, + }, + create: { + discord_id: target.id, + }, + update: {}, + }); + + const ticketData = await db.user_tickets.findFirst({ + where: { + user_id: userData.id, + status: { + not: { + in: ['OPEN', 'DELETED'], + }, + }, + }, + }); + + const guildData = await db.discord_guilds.upsert({ + where: { + id: interaction.guild?.id, + }, + create: { + id: interaction.guild?.id, + }, + update: {}, + }); + + if (!ticketData) { + log.debug(F, `${actor.displayName} does not have any tickets that are not closed or deleted`); + const rejectMessage = stripIndents` + Hey ${actor.displayName}, your ticket is still open! + + If you would like to delete your ticket, please click the 'I no longer need help' button first, then click this button again. + + If you still need help, please be patient and someone will be with you soon! + `; + const embed = embedTemplate().setColor(Colors.DarkBlue); + embed.setDescription(rejectMessage); + // log.debug(F, `target ${target} does not need help!`); + await interaction.editReply({ embeds: [embed] }); + return; + } + + log.debug(F, `Found ticket in ${ticketData.status} status`); + + log.debug(F, `Deleting ticket ${ticketData.id}...`); + + // Delete the thread on discord + if (ticketData.thread_id) { + try { + const thread = await global.discordClient.channels.fetch(ticketData.thread_id) as ThreadChannel; + await thread.delete(); + log.debug(F, `Thread ${ticketData.thread_id} was deleted`); + } catch (err) { + // Thread was likely manually deleted + log.debug(F, `Thread ${ticketData.thread_id} was likely manually deleted`); + } + } + + // Let the meta channel know the user has been helped + const metaChannelId = ticketData.meta_thread_id ?? guildData.channel_tripsitmeta; + if (metaChannelId) { + try { + const metaChannel = await interaction.guild.channels.fetch(metaChannelId) as TextChannel; + await metaChannel.send({ + content: stripIndents`${actor.displayName} has deleted their ticket!`, + }); + if (metaChannelId !== guildData.channel_tripsitmeta) { + metaChannel.setName(`💚│${target.displayName}'s discussion!`); + } + } catch (err) { + // Meta thread likely doesn't exist + if (metaChannelId === ticketData.meta_thread_id) { + ticketData.meta_thread_id = null; + // await ticketUpdate(ticketData); + await db.user_tickets.update({ + where: { + id: ticketData.id, + }, + data: { + meta_thread_id: null, + }, + }); + } + } + } + + // Update the ticket status to deleted + await db.user_tickets.update({ + where: { + id: ticketData.id, + }, + data: { + status: 'DELETED' as ticket_status, + deleted_at: DateTime.local().toJSDate(), + }, + }); + log.debug(F, 'Updated ticket status to DELETED'); +} + /** * Creates the tripsit modal * @param {ButtonInteraction} interaction The interaction that initialized this @@ -1319,7 +1457,12 @@ export async function tripSitMe( .setCustomId(`tripsitmeUserClose~${target.id}`) .setLabel('I no longer need help') .setStyle(ButtonStyle.Success), + new ButtonBuilder() + .setCustomId(`tripsitmeUserDelete~${target.id}`) + .setLabel('Delete my ticket') + .setStyle(ButtonStyle.Success), ); + await threadHelpUser.send({ content: firstMessage, components: [row], From bc20d2e99a068a2ae1a27c6c3bc2df5a3d2082ba Mon Sep 17 00:00:00 2001 From: theimperious1 Date: Thu, 21 Nov 2024 06:25:46 -0500 Subject: [PATCH 4/7] fix(tripsit): only team can close tripsitmode tickets. --- src/discord/commands/global/d.tripsitmode.ts | 39 +++++++++++++++++++ src/discord/utils/tripsitme.ts | 15 +++++++ .../migration.sql | 2 + src/prisma/tripbot/schema.prisma | 1 + 4 files changed, 57 insertions(+) create mode 100644 src/prisma/tripbot/migrations/20241121103027_add_tripsit_mode/migration.sql diff --git a/src/discord/commands/global/d.tripsitmode.ts b/src/discord/commands/global/d.tripsitmode.ts index 36b70cd15..28b5a5562 100644 --- a/src/discord/commands/global/d.tripsitmode.ts +++ b/src/discord/commands/global/d.tripsitmode.ts @@ -286,6 +286,7 @@ async function tripsitmodeOn( }, data: { status: 'OPEN' as ticket_status, + tripsit_mode: true, reopened_at: new Date(), archived_at: env.NODE_ENV === 'production' ? DateTime.local().plus({ days: 7 }).toJSDate() @@ -347,6 +348,44 @@ async function tripsitmodeOn( .setColor(Colors.DarkBlue) .setDescription(replyMessage); await i.editReply({ embeds: [embed] }); + + if (!ticketData) { + ticketData = await db.user_tickets.create({ + data: { + user_id: target.id, + type: 'TRIPSIT', + status: 'OPEN' as ticket_status, + tripsit_mode: true, + description: '', + first_message_id: '', + thread_id: threadHelpUser.id, + archived_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 3 }).toJSDate() + : DateTime.local().plus({ minutes: 1 }).toJSDate(), + deleted_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 6 }).toJSDate() + : DateTime.local().plus({ minutes: 2 }).toJSDate(), + }, + }); + } else { + // Create or update thread to toggle tripsit_mode on + ticketData = await db.user_tickets.update({ + where: { + id: ticketData.id, // Assuming 'id' is unique; use a dummy value if `ticketData` is null + }, + data: { + status: 'OPEN' as ticket_status, + tripsit_mode: true, + reopened_at: new Date(), + archived_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 3 }).toJSDate() + : DateTime.local().plus({ minutes: 1 }).toJSDate(), + deleted_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 6 }).toJSDate() + : DateTime.local().plus({ minutes: 2 }).toJSDate(), + }, + }); + } }); return true; diff --git a/src/discord/utils/tripsitme.ts b/src/discord/utils/tripsitme.ts index 4ce4c4e36..47d4e5bb7 100644 --- a/src/discord/utils/tripsitme.ts +++ b/src/discord/utils/tripsitme.ts @@ -877,6 +877,21 @@ export async function tripsitmeUserClose( }, }); + if (ticketData?.tripsit_mode && targetId === actor.id) { + log.debug(F, 'They requested to close their ticket, but are in TripSit Mode. Request denied.'); + // await interaction.editReply({ content: 'Only a TripSitter or Moderator can close your ticket.' }); + const rejectMessage = stripIndents` + Hey ${actor.displayName}, since a team member made this ticket for you, only a team member can close it. + + Let us know if you're okay now and no longer need assistance and we'll get it closed for you! + `; + const embed = embedTemplate().setColor(Colors.DarkBlue); + embed.setDescription(rejectMessage); + // log.debug(F, `target ${target} does not need help!`); + await interaction.editReply({ embeds: [embed] }); + return; + } + const guildData = await db.discord_guilds.upsert({ where: { id: interaction.guild?.id, diff --git a/src/prisma/tripbot/migrations/20241121103027_add_tripsit_mode/migration.sql b/src/prisma/tripbot/migrations/20241121103027_add_tripsit_mode/migration.sql new file mode 100644 index 000000000..12c5236a4 --- /dev/null +++ b/src/prisma/tripbot/migrations/20241121103027_add_tripsit_mode/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "user_tickets" ADD COLUMN "tripsit_mode" BOOLEAN DEFAULT false; diff --git a/src/prisma/tripbot/schema.prisma b/src/prisma/tripbot/schema.prisma index a815453dd..a4f920cc6 100644 --- a/src/prisma/tripbot/schema.prisma +++ b/src/prisma/tripbot/schema.prisma @@ -423,6 +423,7 @@ model user_tickets { first_message_id String type ticket_type status ticket_status @default(OPEN) + tripsit_mode Boolean? @default(false) closed_by String? @db.Uuid closed_at DateTime? @db.Timestamptz(6) reopened_by String? @db.Uuid From d452947b947baef935ef5f8dd653ac92c9c39a31 Mon Sep 17 00:00:00 2001 From: theimperious1 Date: Mon, 23 Dec 2024 11:23:08 -0500 Subject: [PATCH 5/7] bug fixes --- src/discord/commands/global/d.tripsitmode.ts | 105 ++++++++++--------- src/discord/utils/tripsitme.ts | 59 ++++++----- 2 files changed, 90 insertions(+), 74 deletions(-) diff --git a/src/discord/commands/global/d.tripsitmode.ts b/src/discord/commands/global/d.tripsitmode.ts index 6f75ad87b..96920ffc4 100644 --- a/src/discord/commands/global/d.tripsitmode.ts +++ b/src/discord/commands/global/d.tripsitmode.ts @@ -27,7 +27,7 @@ import { } from 'discord.js'; import { stripIndents } from 'common-tags'; import { DateTime } from 'luxon'; -import { ticket_status } from '@prisma/client'; +import { ticket_status, user_tickets } from '@prisma/client'; import { SlashCommand } from '../../@types/commandDef'; import { embedTemplate } from '../../utils/embedTemplate'; // import {embedTemplate} from '../../utils/embedTemplate'; @@ -251,10 +251,13 @@ async function tripsitmodeOn( if (minutes > 5) { // Switch to seconds > 10 for dev server const helperString = `and/or ${roleHelper}`; try { + // eslint-disable-next-line max-len metaMessage = `Hey ${roleTripsitter} ${guildData.role_helper ? helperString : ''} team, ${interaction.member} has indicated that ${target.displayName} needs assistance!`; } catch (err) { // If for example helper role has been deleted but the ID is still stored, do this + // eslint-disable-next-line max-len metaMessage = `Hey ${roleTripsitter} team, ${interaction.member} has indicated that ${target.displayName} needs assistance!`; + // eslint-disable-next-line max-len log.error(F, `Stored Helper ID for guild ${guildData.id} is no longer valid. Role is unfetchable or deleted.`); } } else { @@ -287,6 +290,7 @@ async function tripsitmodeOn( } } + log.info(F, 'TripSit Mode was triggered in Condition 1 (Update)'); ticketData = await db.user_tickets.update({ where: { id: ticketData.id, @@ -343,55 +347,60 @@ async function tripsitmodeOn( const triage = i.fields.getTextInputValue('triageInput'); const intro = i.fields.getTextInputValue('introInput'); - const threadHelpUser = await tripSitMe(i, target, triage, intro) as ThreadChannel; + const result = (await tripSitMe(i, target, triage, intro)) as [ThreadChannel | null, user_tickets] | null; - const replyMessage = stripIndents` - Hey ${i.member}, you activated tripsit mode on ${target.displayName}! - - Click here to be taken to their private room: ${threadHelpUser} - - You can also click in your channel list to see their private room!`; - const embed = embedTemplate() - .setColor(Colors.DarkBlue) - .setDescription(replyMessage); - await i.editReply({ embeds: [embed] }); + if (result !== null) { + const [threadHelpUser, newTicketData] = result; + const replyMessage = stripIndents` + Hey ${i.member}, you activated tripsit mode on ${target.displayName}! + + Click here to be taken to their private room: ${threadHelpUser} + + You can also click in your channel list to see their private room!`; + const embed = embedTemplate() + .setColor(Colors.DarkBlue) + .setDescription(replyMessage); + await i.editReply({ embeds: [embed] }); - if (!ticketData) { - ticketData = await db.user_tickets.create({ - data: { - user_id: target.id, - type: 'TRIPSIT', - status: 'OPEN' as ticket_status, - tripsit_mode: true, - description: '', - first_message_id: '', - thread_id: threadHelpUser.id, - archived_at: env.NODE_ENV === 'production' - ? DateTime.local().plus({ days: 3 }).toJSDate() - : DateTime.local().plus({ minutes: 1 }).toJSDate(), - deleted_at: env.NODE_ENV === 'production' - ? DateTime.local().plus({ days: 6 }).toJSDate() - : DateTime.local().plus({ minutes: 2 }).toJSDate(), - }, - }); - } else { - // Create or update thread to toggle tripsit_mode on - ticketData = await db.user_tickets.update({ - where: { - id: ticketData.id, // Assuming 'id' is unique; use a dummy value if `ticketData` is null - }, - data: { - status: 'OPEN' as ticket_status, - tripsit_mode: true, - reopened_at: new Date(), - archived_at: env.NODE_ENV === 'production' - ? DateTime.local().plus({ days: 3 }).toJSDate() - : DateTime.local().plus({ minutes: 1 }).toJSDate(), - deleted_at: env.NODE_ENV === 'production' - ? DateTime.local().plus({ days: 6 }).toJSDate() - : DateTime.local().plus({ minutes: 2 }).toJSDate(), - }, - }); + if (!ticketData) { + log.info(F, 'TripSit Mode was triggered in Condition 2 (Creation)'); + ticketData = await db.user_tickets.create({ + data: { + user_id: userData.id, + type: 'TRIPSIT', + status: 'OPEN' as ticket_status, + tripsit_mode: true, + description: '', + first_message_id: '', + thread_id: threadHelpUser !== null ? threadHelpUser.id : '', + archived_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 3 }).toJSDate() + : DateTime.local().plus({ minutes: 1 }).toJSDate(), + deleted_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 6 }).toJSDate() + : DateTime.local().plus({ minutes: 2 }).toJSDate(), + }, + }); + } else { + // Create or update thread to toggle tripsit_mode on + log.info(F, 'TripSit Mode was triggered in Condition 3 (Update)'); + ticketData = await db.user_tickets.update({ + where: { + id: newTicketData.id, // Assuming 'id' is unique; use a dummy value if `ticketData` is null + }, + data: { + status: 'OPEN' as ticket_status, + tripsit_mode: true, + reopened_at: new Date(), + archived_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 3 }).toJSDate() + : DateTime.local().plus({ minutes: 1 }).toJSDate(), + deleted_at: env.NODE_ENV === 'production' + ? DateTime.local().plus({ days: 6 }).toJSDate() + : DateTime.local().plus({ minutes: 2 }).toJSDate(), + }, + }); + } } }); diff --git a/src/discord/utils/tripsitme.ts b/src/discord/utils/tripsitme.ts index 8aecb8e88..c9b00bf62 100644 --- a/src/discord/utils/tripsitme.ts +++ b/src/discord/utils/tripsitme.ts @@ -888,11 +888,13 @@ export async function tripsitmeUserClose( }, }, }, + orderBy: { + thread_id: 'desc', + }, }); if (ticketData?.tripsit_mode && targetId === actor.id) { log.debug(F, 'They requested to close their ticket, but are in TripSit Mode. Request denied.'); - // await interaction.editReply({ content: 'Only a TripSitter or Moderator can close your ticket.' }); const rejectMessage = stripIndents` Hey ${actor.displayName}, since a team member made this ticket for you, only a team member can close it. @@ -1332,7 +1334,7 @@ export async function tripSitMe( memberInput:GuildMember | null, triage:string, intro: string = '', -):Promise { +):Promise<[ThreadChannel | null, user_tickets] | null> { log.info(F, await commandContext(interaction)); // await interaction.deferReply({ ephemeral: true }); @@ -1527,7 +1529,7 @@ export async function tripSitMe( const embedTripsitter = embedTemplate() .setColor(Colors.DarkBlue) .setDescription(stripIndents` - ${issue ? `A tripsitter has put ${target} into tripsitmode!\n` : `${target} has requested assistance!\n`} + ${issue ? `${interaction.member} has put ${target} into tripsitmode!\n` : `${target} has requested assistance!\n`} **They've taken:** ${triage ? `\n${triage}` : noInfo} ${intro !== '' ? `${issue}\n` : ''} @@ -1606,6 +1608,7 @@ export async function tripSitMe( type: 'TRIPSIT', status: 'OPEN', first_message_id: '', + tripsit_mode: !!issue, archived_at: archiveTime.toJSDate(), deleted_at: deleteTime.toJSDate(), } as user_tickets; @@ -1613,7 +1616,7 @@ export async function tripSitMe( // log.debug(F, `newTicketData: ${JSON.stringify(newTicketData, null, 2)}`); // Update the ticket in the DB - await db.user_tickets.create({ + const newTicket = await db.user_tickets.create({ data: { user_id: newTicketData.user_id, description: newTicketData.description, @@ -1621,12 +1624,13 @@ export async function tripSitMe( type: newTicketData.type, status: newTicketData.status, first_message_id: newTicketData.first_message_id, + tripsit_mode: newTicketData.tripsit_mode, archived_at: newTicketData.archived_at, deleted_at: newTicketData.deleted_at, }, }); - return threadHelpUser; + return [threadHelpUser, newTicket]; } export async function tripsitmeButton( @@ -1955,30 +1959,33 @@ export async function tripsitmeButton( await i.deferReply({ ephemeral: true }); const triage = i.fields.getTextInputValue('triageInput'); - const threadHelpUser = await tripSitMe(i, target, triage) as ThreadChannel; + const result = (await tripSitMe(i, target, triage)) as [ThreadChannel | null, user_tickets] | null; - if (!threadHelpUser) { + if (result !== null) { + const [threadHelpUser] = result; + if (!threadHelpUser) { + const embed = embedTemplate() + .setColor(Colors.DarkBlue) + .setDescription(stripIndents`Hey ${interaction.member}, there was an error creating your help thread! The Guild owner should get a message with specifics!`); + await i.editReply({ embeds: [embed] }); + return; + } + + const replyMessage = stripIndents` + Hey ${target}, thank you for asking for assistance! + + Click here to be taken to your private room: ${threadHelpUser.toString()} + + You can also click in your channel list to see your private room!`; const embed = embedTemplate() .setColor(Colors.DarkBlue) - .setDescription(stripIndents`Hey ${interaction.member}, there was an error creating your help thread! The Guild owner should get a message with specifics!`); - await i.editReply({ embeds: [embed] }); - return; - } - - const replyMessage = stripIndents` - Hey ${target}, thank you for asking for assistance! - - Click here to be taken to your private room: ${threadHelpUser.toString()} - - You can also click in your channel list to see your private room!`; - const embed = embedTemplate() - .setColor(Colors.DarkBlue) - .setDescription(replyMessage); - try { - await i.editReply({ embeds: [embed] }); - } catch (err) { - log.error(F, `There was an error responding to the user! ${err}`); - log.error(F, `Error: ${JSON.stringify(err, null, 2)}`); + .setDescription(replyMessage); + try { + await i.editReply({ embeds: [embed] }); + } catch (err) { + log.error(F, `There was an error responding to the user! ${err}`); + log.error(F, `Error: ${JSON.stringify(err, null, 2)}`); + } } }); } From 0a35a0120461cc6711215a1e9fde29dd22a221ec Mon Sep 17 00:00:00 2001 From: theimperious1 Date: Mon, 23 Dec 2024 11:36:16 -0500 Subject: [PATCH 6/7] Fix ~tripsit formatting --- src/discord/utils/messageCommand.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/discord/utils/messageCommand.ts b/src/discord/utils/messageCommand.ts index 1b0a3d735..b79a19a1a 100644 --- a/src/discord/utils/messageCommand.ts +++ b/src/discord/utils/messageCommand.ts @@ -195,11 +195,11 @@ give people a chance to answer 😄 If no one answers in 5 minutes you can try a } const roleTripsitter = await message.guild.roles.fetch(env.ROLE_TRIPSITTER) as Role; const roleHelper = await message.guild.roles.fetch(env.ROLE_HELPER) as Role; - await message.channel.send(`Hey ${displayName}, someone from the ${roleTripsitter} and/or ${roleHelper} team will be with you as soon as they're available! + await message.channel.send(stripIndents`Hey ${displayName}, someone from the ${roleTripsitter} and/or ${roleHelper} team will be with you as soon as they're available! - If you’re in the right mindset please start by telling us what you took, at what dose and route, how long ago, along with any concerns you may have. + If you’re in the right mindset please start by telling us what you took, at what dose and route, how long ago, along with any concerns you may have. - **If this is a medical emergency** please contact your local emergency services: we do not call EMS on behalf of anyone. + **If this is a medical emergency** please contact your local emergency services: we do not call EMS on behalf of anyone. `); From 8462588d3b0a6928a29346ec52a348fd7ef5e4a4 Mon Sep 17 00:00:00 2001 From: theimperious1 Date: Mon, 23 Dec 2024 13:00:32 -0500 Subject: [PATCH 7/7] Ephemeral in-ticket actions if mod,sitter,helper. Better cooldowns. TeamClose if team uses UserClose in sessions. Shorter cooldowns in some areas. Bug fixes --- src/discord/utils/commandCooldown.ts | 4 +- src/discord/utils/tripsitme.ts | 90 +++++++++++++++++++++------- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/discord/utils/commandCooldown.ts b/src/discord/utils/commandCooldown.ts index c866997c5..01a395462 100644 --- a/src/discord/utils/commandCooldown.ts +++ b/src/discord/utils/commandCooldown.ts @@ -31,10 +31,10 @@ async function commandCooldown( // If the cooldown is still active, inform the user if (now < expirationTime) { - const timeLeft = (expirationTime - now) / 1000; // Time left in seconds return { success: false, - message: `Please wait ${timeLeft.toFixed(1)} more seconds before using this command or button again.`, + // eslint-disable-next-line max-len + message: `Please wait before using this command or button again. You can press it again `, }; } } diff --git a/src/discord/utils/tripsitme.ts b/src/discord/utils/tripsitme.ts index c9b00bf62..95117a466 100644 --- a/src/discord/utils/tripsitme.ts +++ b/src/discord/utils/tripsitme.ts @@ -658,7 +658,12 @@ export async function tripsitmeTeamClose( if (!interaction.member) return; if (!interaction.channel) return; log.info(F, await commandContext(interaction)); - await interaction.deferReply({ ephemeral: true }); + try { + await interaction.deferReply({ ephemeral: true }); + } catch (err) { + // If this errors it is likely because a mod/tripsitter called this function from UserClose button. + // This is normal and intended, if a little hacky. + } const targetId = interaction.customId.split('~')[1]; @@ -763,7 +768,7 @@ export async function tripsitmeTeamClose( This thread will remain here for a day if you want to follow up tomorrow. After 7 days, it will be deleted to preserve your privacy =) - Alternatively, you may press the "Delete my ticket" button to delete it yourself. + Alternatively, you may press the "Delete ticket" button at the start of your ticket to delete it yourself. If you'd like to go back to social mode, just click the button below! `; @@ -846,26 +851,38 @@ export async function tripsitmeUserClose( if (!interaction.channel) return; log.info(F, await commandContext(interaction)); - const cooldown = await commandCooldown(interaction.user, interaction.customId); + const cooldown = await commandCooldown(interaction.user, interaction.customId, 10); if (!cooldown.success && cooldown.message) { await interaction.reply({ content: cooldown.message, ephemeral: true }); return; } - await interaction.deferReply({ ephemeral: false }); + const actor = interaction.member as GuildMember; + const isModTripsitterOrHelper = actor.roles.cache.some( + role => [env.ROLE_MODERATOR, env.ROLE_TRIPSITTER, env.ROLE_HELPER].includes(role.id), + ); + + await interaction.deferReply({ ephemeral: isModTripsitterOrHelper }); const targetId = interaction.customId.split('~')[1]; const override = interaction.customId.split('~')[0] === 'tripsitmodeOffOverride'; const target = await interaction.guild.members.fetch(targetId); - const actor = interaction.member as GuildMember; await actor.fetch(); log.debug(F, `${actor.displayName} (${actor.id}) clicked "I no longer need help" in ${target.displayName}'s (${target.id}) session `); - if (targetId !== actor.id && !override) { - log.debug(F, 'They did not create the thread, so I am not doing anything!'); - await interaction.editReply({ content: 'Only the user receiving help can click this button!' }); + if ( + targetId !== actor.id + && !override + && !isModTripsitterOrHelper) { + log.debug(F, 'They did not create the thread or have the required role, so I am not doing anything!'); + await interaction.editReply({ content: 'Only moderators, tripsitters, or the user receiving help can click this button!' }); + return; + } + + if (isModTripsitterOrHelper) { + await tripsitmeTeamClose(interaction); return; } @@ -1112,10 +1129,8 @@ export async function tripsitmeUserClose( await interaction.editReply(stripIndents`Hey ${target}, it looks like you're doing somewhat better! This thread will remain here for a day if you want to follow up tomorrow. - After 7 days, it will be deleted to preserve your privacy =) - Alternatively, you may press the "Delete my ticket" button to delete it yourself. - - If you'd like to go back to social mode, just click the button below!`); + After 3 days, it will be deleted to preserve your privacy =) + Alternatively, you may press the "Delete ticket" button at the start of your ticket to delete it yourself.`); } catch (err) { log.error(F, `Error sending end help message to ${threadHelpUser}`); log.error(F, err as string); @@ -1202,19 +1217,33 @@ export async function tripsitmeUserDelete( if (!interaction.channel) return; log.info(F, await commandContext(interaction)); - await interaction.deferReply({ ephemeral: false }); + const cooldown = await commandCooldown(interaction.user, interaction.customId, 10); + + if (!cooldown.success && cooldown.message) { + await interaction.reply({ content: cooldown.message, ephemeral: true }); + return; + } + + const actor = interaction.member as GuildMember; + const isModTripsitterOrHelper = actor.roles.cache.some( + role => [env.ROLE_MODERATOR, env.ROLE_TRIPSITTER, env.ROLE_HELPER].includes(role.id), + ); + + await interaction.deferReply({ ephemeral: isModTripsitterOrHelper }); const targetId = interaction.customId.split('~')[1]; const override = interaction.customId.split('~')[0] === 'tripsitmodeOffOverride'; const target = await interaction.guild.members.fetch(targetId); - const actor = interaction.member as GuildMember; await actor.fetch(); - log.debug(F, `${actor.displayName} (${actor.id}) clicked "Delete my ticket" in ${target.displayName}'s (${target.id}) session `); + log.debug(F, `${actor.displayName} (${actor.id}) clicked "Delete ticket" in ${target.displayName}'s (${target.id}) session `); - if (targetId !== actor.id && !override) { - log.debug(F, 'They did not create the thread, so I am not doing anything!'); - await interaction.editReply({ content: 'Only the user receiving help can click this button!' }); + if ( + targetId !== actor.id + && !override + && !isModTripsitterOrHelper) { + log.debug(F, 'They did not create the thread or have the required role, so I am not doing anything!'); + await interaction.editReply({ content: 'Only moderators, tripsitters, or the user receiving help can click this button!' }); return; } @@ -1228,7 +1257,7 @@ export async function tripsitmeUserDelete( update: {}, }); - const ticketData = await db.user_tickets.findFirst({ + let ticketData = await db.user_tickets.findFirst({ where: { user_id: userData.id, status: { @@ -1249,13 +1278,28 @@ export async function tripsitmeUserDelete( update: {}, }); - if (!ticketData) { + if (!ticketData && isModTripsitterOrHelper) { + ticketData = await db.user_tickets.findFirst({ + where: { + user_id: userData.id, + status: { + not: { + in: ['DELETED'], + }, + }, + }, + }); + if (!ticketData) { + log.error(F, 'Ticket data not found in tripsitmeUserDelete tripsitme.ts'); + return; + } + } else if (!ticketData) { log.debug(F, `${actor.displayName} does not have any tickets that are not closed or deleted`); const rejectMessage = stripIndents` Hey ${actor.displayName}, your ticket is still open! - + If you would like to delete your ticket, please click the 'I no longer need help' button first, then click this button again. - + If you still need help, please be patient and someone will be with you soon! `; const embed = embedTemplate().setColor(Colors.DarkBlue); @@ -1495,7 +1539,7 @@ export async function tripSitMe( .setStyle(ButtonStyle.Success), new ButtonBuilder() .setCustomId(`tripsitmeUserDelete~${target.id}`) - .setLabel('Delete my ticket') + .setLabel('Delete ticket') .setStyle(ButtonStyle.Success), );