Skip to content

Commit

Permalink
Rebuild the Travel command (#1037)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* feat: restore legacy travel command

* fix: texts and labels, add not a move requirement

* fix: import after rebase

* chore: review

* fix: build
  • Loading branch information
geisterfurz007 authored Sep 1, 2023
1 parent d274093 commit 2957e35
Show file tree
Hide file tree
Showing 16 changed files with 900 additions and 36 deletions.
3 changes: 2 additions & 1 deletion src/event-distribution/events/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,10 @@ export const rejectWithError = async (
error
);

if (interactionArg.deferred) {
if (interactionArg.deferred || interactionArg.replied) {
return await interactionArg.editReply({
content: detailedMessage,
components: [],
});
}

Expand Down
6 changes: 3 additions & 3 deletions src/programs/nitro-colors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import prisma from "../../prisma";
export const logger = createYesBotLogger("programs", "NitroColors");

export let nitroRolesCache: Collection<Snowflake, Role>;
export let colorSelectionMessage: Message;
export let colorSelectionMessage: Message | undefined;

@Command({
event: DiscordEvent.READY,
Expand Down Expand Up @@ -78,7 +78,7 @@ class RemoveNitroColorIfNotAllowed

if (!nitroColor) return;

colorSelectionMessage.reactions.cache.find(
colorSelectionMessage?.reactions.cache.find(
(reactions) => !!reactions.users.remove(member)
);
await member.roles.remove(nitroColor);
Expand Down Expand Up @@ -149,4 +149,4 @@ const memberHasNitroColor = (member: GuildMember) =>
);

export const isColorSelectionMessage = (messageId: Snowflake) =>
colorSelectionMessage.id === messageId;
colorSelectionMessage?.id === messageId;
5 changes: 2 additions & 3 deletions src/programs/nitro-colors/roles-reset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import bot from "../../index";
import cron from "node-cron";
import { nitroRolesCache, colorSelectionMessage, logger } from ".";
import Tools from "../../common/tools";
import { ColorResolvable, Colors, Role, TextChannel } from "discord.js";
import { ColorResolvable, TextChannel } from "discord.js";
import {
NitroRole,
buildAnnouncementsMessage,
getCurrentSeason,
isNewSeason,
Expand All @@ -28,7 +27,7 @@ export class RoleResetCron {
const cleanupChannelMessages = async (channel: TextChannel) => {
const messages = await channel.messages.fetch({ limit: 5 });
for (const message of messages.values()) {
if (message.id !== colorSelectionMessage.id) {
if (message.id !== colorSelectionMessage?.id) {
await message.delete();
continue;
}
Expand Down
57 changes: 57 additions & 0 deletions src/programs/tickets/travel/approve-travel-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
Command,
CommandHandler,
DiscordEvent,
} from "../../../event-distribution";
import {
ThreadAutoArchiveDuration,
ButtonInteraction,
TextChannel,
} from "discord.js";
import { ChatNames } from "../../../collections/chat-names";
import { TravelDataMessageConverter } from "./travel-data-message-converter";

@Command({
event: DiscordEvent.BUTTON_CLICKED,
customId: "travel-approve",
})
class ApproveTravelButton extends CommandHandler<DiscordEvent.BUTTON_CLICKED> {
async handle(interaction: ButtonInteraction): Promise<void> {
const message = interaction.message;
const guild = interaction.guild!;

const details = TravelDataMessageConverter.fromMessage(message, guild);

const member = guild.members.resolve(interaction.user.id);
if (!member) throw new Error("Could not resolve approving member!");

const approver = member.displayName;
const newContent = message.content + `\n\nApproved by ${approver}`;
// Remove buttons as early as possible before someone else votes as well
await message.edit({ content: newContent, components: [] });

const travelChannel = interaction.guild?.channels.cache.find(
(c): c is TextChannel => c.name === ChatNames.TRAVELING_TOGETHER
);
if (!travelChannel) throw new Error("Could not find travel channel!");

const messageWithMentions = TravelDataMessageConverter.toMessage(
details,
true
);

const travelPost =
messageWithMentions +
"\n\nClick on the thread right below this line if you're interested to join the chat and talk about it 🙂";
const travelMessage = await travelChannel?.send(travelPost);

const ticketMember = await guild.members.fetch(details.userId);
const threadName = `${ticketMember.displayName} in ${details.places}`;
const trimmedThreadname = threadName.substring(0, 100);

await travelMessage.startThread({
name: trimmedThreadname,
autoArchiveDuration: ThreadAutoArchiveDuration.OneWeek,
});
}
}
21 changes: 21 additions & 0 deletions src/programs/tickets/travel/cancel-travel-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
Command,
CommandHandler,
DiscordEvent,
EventLocation,
} from "../../../event-distribution";
import { ButtonInteraction } from "discord.js";

@Command({
event: DiscordEvent.BUTTON_CLICKED,
customId: "travel-cancel",
location: EventLocation.DIRECT_MESSAGE,
})
class CancelTravelButton extends CommandHandler<DiscordEvent.BUTTON_CLICKED> {
async handle(interaction: ButtonInteraction): Promise<void> {
await interaction.message.delete();
await interaction.reply(
"Feel free to open up a new travel ticket anytime using the /travel command!"
);
}
}
126 changes: 126 additions & 0 deletions src/programs/tickets/travel/decline-travel-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
Command,
CommandHandler,
DiscordEvent,
} from "../../../event-distribution";
import {
ActionRowBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
ModalSubmitInteraction,
TextInputBuilder,
TextInputStyle,
} from "discord.js";
import {
TravelDataMessageConverter,
TripDetails,
} from "./travel-data-message-converter";

@Command({
event: DiscordEvent.BUTTON_CLICKED,
customId: "travel-decline",
})
class DeclineTravelButton extends CommandHandler<DiscordEvent.BUTTON_CLICKED> {
async handle(interaction: ButtonInteraction): Promise<void> {
const message = interaction.message;
const originalMessage = message.content;
const guild = interaction.guild!;
const details = TravelDataMessageConverter.fromMessage(message, guild);

const member = guild.members.resolve(interaction.user.id);
if (!member) throw new Error("Could not resolve approving member!");

const decliner = member.displayName;
const newContent =
message.content + `\n\n${decliner} is currently declining...`;
await message.edit({ content: newContent });

const { userId } = details;

const reasonInput = new ActionRowBuilder<TextInputBuilder>({
components: [
new TextInputBuilder({
customId: "reason",
style: TextInputStyle.Paragraph,
required: true,
label: "Reason",
}),
],
});

const modalId = "travel-decline-reason-" + userId;
await interaction.showModal({
title: "Decline reason",
customId: modalId,
components: [reasonInput],
});
try {
const submission = await interaction.awaitModalSubmit({
time: 5 * 60 * 1000,
filter: (i) => i.customId === modalId,
});

await this.declineWithReason(submission, originalMessage, details);
} catch {
await interaction.update({ content: originalMessage });
}
}

async declineWithReason(
submission: ModalSubmitInteraction,
originalMessage: string,
details: TripDetails
) {
const reason = submission.fields.getTextInputValue("reason").trim();
if (!submission.isFromMessage()) return;
const guild = submission.guild!;
const member = guild.members.resolve(submission.user.id);
if (!member) throw new Error("Could not resolve approving member!");

const decliner = member.displayName;

if (!reason) {
await submission.update({ content: originalMessage });
return;
}

await submission.update({
content:
originalMessage + `\n\nDeclined by ${decliner}\nReason: ${reason}`,
components: [],
});

const editButtonId = "travel-edit";
const editButton = new ButtonBuilder({
label: "Edit",
style: ButtonStyle.Primary,
customId: editButtonId,
emoji: "✏",
});

const cancelButtonId = "travel-cancel";
const cancelButton = new ButtonBuilder({
label: "Cancel",
style: ButtonStyle.Danger,
customId: cancelButtonId,
});

const dm = await submission.client.users.createDM(details.userId);
await dm.send({
content: `Hello there! A moderator has declined your travel ticket with the following reason: ${reason}
This was your submission:
---
${originalMessage}
---
You can edit or cancel your ticket:
`,
components: [
new ActionRowBuilder<ButtonBuilder>({
components: [editButton, cancelButton],
}),
],
});
}
}
36 changes: 36 additions & 0 deletions src/programs/tickets/travel/edit-travel-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {
Command,
CommandHandler,
DiscordEvent,
} from "../../../event-distribution";
import { ButtonInteraction } from "discord.js";
import { TravelEditing } from "./travel-editing";
import { TravelDataMessageConverter } from "./travel-data-message-converter";

@Command({
event: DiscordEvent.BUTTON_CLICKED,
customId: "travel-edit",
})
class EditTravelButton extends CommandHandler<DiscordEvent.BUTTON_CLICKED> {
async handle(interaction: ButtonInteraction): Promise<void> {
const editing = new TravelEditing();
const guild = interaction.client.guilds.resolve(process.env.GUILD_ID);

if (!guild) throw new Error("Yeet");

const details = TravelDataMessageConverter.fromMessage(
interaction.message,
guild
);

const { details: finalDetails, interaction: editInteraction } =
await editing.doEditing(details, interaction, false);
await editing.sendApprovalMessage(finalDetails, guild);

await editInteraction.update({
content:
"I've sent everything to the mods! Have some patience while they take a look at the updates :)",
components: [],
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
Command,
CommandHandler,
DiscordEvent,
} from "../../../event-distribution";
} from "../../../../event-distribution";
import {
Collection,
Guild,
Expand All @@ -15,9 +15,9 @@ import {
ThreadChannel,
User,
} from "discord.js";
import { ChatNames } from "../../../collections/chat-names";
import { closeTicket, getChannelName, TicketType } from "../common";
import { createYesBotLogger } from "../../../log";
import { ChatNames } from "../../../../collections/chat-names";
import { closeTicket, getChannelName, TicketType } from "../../common";
import { createYesBotLogger } from "../../../../log";
import { parseOriginMember } from "./common";

@Command({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
TextChannel,
User,
} from "discord.js";
import { CountryRoleFinder } from "../../../common/country-role-finder";
import { ChatNames } from "../../../collections/chat-names";
import { createYesBotLogger } from "../../../log";
import { CountryRoleFinder } from "../../../../common/country-role-finder";
import { ChatNames } from "../../../../collections/chat-names";
import { createYesBotLogger } from "../../../../log";

const fiveMinutes = 5 * 60 * 1000;
type CancellationToken = { cancelled: boolean };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import {
Command,
CommandHandler,
DiscordEvent,
} from "../../../event-distribution";
import { ChatNames } from "../../../collections/chat-names";
} from "../../../../event-distribution";
import { ChatNames } from "../../../../collections/chat-names";
import { MessageReaction, TextChannel, User } from "discord.js";
import { getChannelName, TicketType } from "../common";
import { getChannelName, TicketType } from "../../common";
import { parseOriginMember } from "./common";

@Command({
Expand Down
34 changes: 34 additions & 0 deletions src/programs/tickets/travel/legacy/open-travel-ticket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
Command,
CommandHandler,
DiscordEvent,
HandlerRejectedReason,
} from "../../../../event-distribution";
import { Message } from "discord.js";
import { maybeCreateTicket, TicketType } from "../../common";
import { promptAndSendForApproval } from "./common";

@Command({
event: DiscordEvent.MESSAGE,
trigger: "!travel",
allowedRoles: ["Seek Discomfort"],
description: "This handler is to create a travel ticket.",
stateful: false,
errors: {
[HandlerRejectedReason.MISSING_ROLE]: `Before meeting up with people, it's probably best to let others know who you are! This command requires the 'Seek Discomfort' role which you can get by introducing yourself in #introductions!\n\nIf you already posted your introduction, make sure it's longer than just two or three sentences and give the support team some time to check it :)`,
},
})
class OpenTravelTicket implements CommandHandler<DiscordEvent.MESSAGE> {
async handle(message: Message): Promise<void> {
const channel = await maybeCreateTicket(
message,
TicketType.TRAVEL,
`Hi ${message.member?.toString()}, let's collect all important information for your trip!`,
false
);

if (!channel) return;

await promptAndSendForApproval(channel, message.author.id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
CommandHandler,
DiscordEvent,
EventLocation,
} from "../../../event-distribution";
} from "../../../../event-distribution";
import { Message, TextChannel } from "discord.js";
import { getChannelName, TicketType } from "../common";
import Tools from "../../../common/tools";
import { getChannelName, TicketType } from "../../common";
import Tools from "../../../../common/tools";
import { promptAndSendForApproval } from "./common";

@Command({
Expand Down
Loading

0 comments on commit 2957e35

Please sign in to comment.