This repository was archived by the owner on Apr 15, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHelpCommandHelper.ts
More file actions
335 lines (318 loc) · 13.9 KB
/
HelpCommandHelper.ts
File metadata and controls
335 lines (318 loc) · 13.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
import {
ActionRowBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
CommandInteraction,
EmbedBuilder,
time,
} from "discord.js";
import { DiscordColorService } from "../service/DiscordColorService.ts";
import { InvalidHelpPageError } from "./error/InvalidHelpPageError.ts";
import { DiscordEmojiService } from "../service/DiscordEmojiService.ts";
import { CommandMentionService } from "../service/CommandMentionService.ts";
export class HelpCommandHelper {
public static async handlePageSwapButton(
interaction: ButtonInteraction,
buttonIdSplit: string[],
): Promise<void> {
const page = buttonIdSplit[1];
switch (page) {
case "profiles":
await interaction.editReply(await this.getProfiles(interaction));
break;
case "verification":
await interaction.editReply(await this.getVerification(interaction));
break;
case "favorites":
await interaction.editReply(await this.getFavorites(interaction));
break;
case "info":
await interaction.editReply(await this.getInfo(interaction));
break;
case "setup":
await interaction.editReply(await this.getSetup(interaction));
break;
case "about":
await interaction.editReply(await this.getAbout(interaction));
break;
default:
throw new InvalidHelpPageError(page);
}
}
public static async getProfiles(
interaction: ButtonInteraction | CommandInteraction,
): Promise<{ embeds: EmbedBuilder[]; components: ActionRowBuilder<ButtonBuilder>[] }> {
const client = interaction.client;
const components = HelpCommandHelper.getButtons("profiles");
const embed = new EmbedBuilder()
.setColor(await DiscordColorService.getBotColorByInteraction(interaction))
.setTitle("Character Profile Commands")
.setThumbnail(client.user!.displayAvatarURL())
.addFields([
{
name: CommandMentionService.mentionOrPlain("profile", "me"),
value: `- Get the profile of your verified character.\n- Characters can be verified with ${
CommandMentionService.mentionOrBacktick("verify", "add")
}.`,
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("profile", "find"),
value:
"- Get any character's profile by providing the full character name and the server the character is on." +
"\n- Alternatively you can right click a user → choose `Apps` → `Find`. This is faster but requires this user to have a verified character.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("profile", "favorite"),
value:
`- A quick access to character profiles.\n- Favorites can be added or removed with ${
CommandMentionService.mentionOrBacktick("favorite", "add")
} and ${CommandMentionService.mentionOrBacktick("favorite", "remove")} respectively.` +
"\n- Alternatively you can right click a user → choose `Apps` → `Add Favorite` / `Remove Favorite`. This is faster but requires this user to have a verified character.",
inline: false,
},
]);
return {
embeds: [embed],
components,
};
}
private static async getVerification(
interaction: ButtonInteraction | CommandInteraction,
): Promise<{ embeds: EmbedBuilder[]; components: ActionRowBuilder<ButtonBuilder>[] }> {
const client = interaction.client;
const components = HelpCommandHelper.getButtons("verification");
const embed = new EmbedBuilder()
.setColor(await DiscordColorService.getBotColorByInteraction(interaction))
.setTitle("Verification Commands")
.setThumbnail(client.user!.displayAvatarURL())
.addFields([
{
name: CommandMentionService.mentionOrPlain("verify", "add"),
value: "- Links your FFXIV character to your Discord account." +
"\n- You will have to verify it by changing the bio of your Lodestone profile." +
`\n- Verification is needed to show your profile via ${
CommandMentionService.mentionOrBacktick("profile", "me")
}, manage and access favorites and to set a theme.`,
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("verify", "remove"),
value: "- Unlinks your FFXIV character from your Discord account." +
"\n- Also removes any other information stored of you including your favorites and your chosen theme.",
inline: false,
},
]);
return {
embeds: [embed],
components,
};
}
private static async getFavorites(
interaction: ButtonInteraction | CommandInteraction,
): Promise<{ embeds: EmbedBuilder[]; components: ActionRowBuilder<ButtonBuilder>[] }> {
const client = interaction.client;
const components = HelpCommandHelper.getButtons("favorites");
const embed = new EmbedBuilder()
.setColor(await DiscordColorService.getBotColorByInteraction(interaction))
.setTitle("Favorite Commands")
.setThumbnail(client.user!.displayAvatarURL())
.addFields([
{
name: CommandMentionService.mentionOrPlain("favorite", "add"),
value: "- Save any character as favorite." +
`\n- You can then access them quickly with ${
CommandMentionService.mentionOrBacktick("profile", "favorite")
}.` +
"\n- You can have up to 25 favorites.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("favorite", "remove"),
value: "- Remove one of your favorites.",
inline: false,
},
]);
return {
embeds: [embed],
components,
};
}
private static async getInfo(
interaction: ButtonInteraction | CommandInteraction,
): Promise<{ embeds: EmbedBuilder[]; components: ActionRowBuilder<ButtonBuilder>[] }> {
const client = interaction.client;
const components = HelpCommandHelper.getButtons("info");
const embed = new EmbedBuilder()
.setColor(await DiscordColorService.getBotColorByInteraction(interaction))
.setTitle("Informational Commands")
.setThumbnail(client.user!.displayAvatarURL())
.addFields([
{
name: CommandMentionService.mentionOrPlain("events"),
value: "- Lists all currently ongoing and upcoming FFXIV events." +
"\n- Shows seasonal events, Moogle Treasure Troves, and Live Letters.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("help"),
value: "- You are already here " + DiscordEmojiService.getAsMarkdown("EMOJI_DOGGO_SMILE"),
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("liveletter"),
value: "- Shows information about the next or current Live Letter.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("maintenances"),
value: "- Lists all currently ongoing and upcoming FFXIV maintenances." +
"\n- Shows maintenance schedules and their durations.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("when-is-reset"),
value: "- Shows daily and weekly reset times for FFXIV." +
"\n- Includes duty roulettes, GC missions, tomestone caps, fashion report and more.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("worldstatus"),
value: "- Shows server status, character creation status and server congestion.",
inline: false,
},
]);
return {
embeds: [embed],
components,
};
}
private static async getSetup(
interaction: ButtonInteraction | CommandInteraction,
): Promise<{ embeds: EmbedBuilder[]; components: ActionRowBuilder<ButtonBuilder>[] }> {
const client = interaction.client;
const components = HelpCommandHelper.getButtons("setup");
const embed = new EmbedBuilder()
.setColor(await DiscordColorService.getBotColorByInteraction(interaction))
.setTitle("Setup Commands")
.setThumbnail(client.user!.displayAvatarURL())
.addFields([
{
name: CommandMentionService.mentionOrPlain("setup", "lodestone"),
value: `- Set up which channels receive automated Lodestone news updates.` +
"\n- You can set or remove channels for each category of news." +
"\n- The Lodestone news categories are:" +
"\n - Topic (Latest news, PLL's and patch notes)" +
"\n - Notice (Secondary news)" +
"\n - Maintenance (All kind of maintenances and their durations)" +
"\n - Update (Outcome from maintenances)" +
"\n - Status (Technical difficulties and server statuses)" +
"\n- By default, this command requires \`Manage Channels\` permission to execute.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("setup", "filters"),
value: "- Set up keyword filter blacklist to exclude certain Lodestone news." +
'\n- Configure comma-separated keywords for each news category. E.g. "ddos, difficulty logging in to north america"' +
"\n- If any keyword matches content in a news article, it will not be posted. The keywords are case-insensitive." +
"\n- Filters are applied per category (topics, notices, maintenances, updates, statuses)." +
"\n- By default, this command requires \`Manage Channels\` permission to execute." +
"\n- _For tech savvy power users only:_" +
"\n - Use regex patterns with `/pattern/flags` syntax (e.g. `/north\\s+america/i`)." +
"\n - Supported flags: `i` (case-insensitive), `g` (global), `m` (multiline), `s` (dotAll), `u` (unicode)." +
"\n - Flags are not applied by default so if you need case-insensitivity, you have to provide the `i` flag." +
'\n - You can have both regex and normal keywords, e.g. "ddos,/data cent(er|re)/i".',
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("event-reminders"),
value: "- Set up reminders for events that are ending soon and live letters that are starting." +
"\n- By default, reminders are enabled and sent to the topics news channel." +
"\n- You can disable reminders or override the channel." +
"\n- By default, this command requires \`Manage Channels\` permission to execute.",
inline: false,
},
{
name: CommandMentionService.mentionOrPlain("theme"),
value: "- Set a theme for your verified character's profile.",
inline: false,
},
]);
return {
embeds: [embed],
components,
};
}
private static async getAbout(
interaction: ButtonInteraction | CommandInteraction,
): Promise<{ embeds: EmbedBuilder[]; components: ActionRowBuilder<ButtonBuilder>[] }> {
const client = interaction.client;
const components = HelpCommandHelper.getButtons("about");
const uptimeFormatted = time(new Date(Date.now() - client.uptime!), "R");
const deploymentHash = Deno.env.get("DEPLOYMENT_HASH");
const embed = new EmbedBuilder()
.setColor(await DiscordColorService.getBotColorByInteraction(interaction))
.setTitle("About M'naago")
.setThumbnail(client.user!.displayAvatarURL())
.setDescription(
"M'naago is a Discord bot for Final Fantasy XIV that provides character profiles and " +
"automated Lodestone news notifications. The bot allows you to verify your character, " +
"view detailed character profiles with customizable themes, manage favorites, and stay " +
"up-to-date with the latest news, maintenances, and updates from the Lodestone.",
)
.addFields([
{
name: "Support",
value:
`In order to get help, report a bug or to see updates as I post them, please join the [Support Server](${Deno
.env.get("SUPPORT_SERVER_URL")!}).`,
inline: false,
},
{ name: "Ping", value: `${client.ws.ping} ms`, inline: true },
{ name: "Latest restart", value: uptimeFormatted, inline: true },
{ name: "Servers", value: (await client.guilds.fetch())?.size.toString(), inline: true },
]);
if (deploymentHash) {
embed.setFooter({ text: `Deployment Hash: ${deploymentHash}` });
}
return {
embeds: [embed],
components,
};
}
private static getButtons(
currentPage: string,
): ActionRowBuilder<ButtonBuilder>[] {
const row1 = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel("Character Profile")
.setCustomId("help.profiles")
.setStyle(currentPage === "profiles" ? ButtonStyle.Primary : ButtonStyle.Secondary),
new ButtonBuilder()
.setLabel("Verification")
.setCustomId("help.verification")
.setStyle(currentPage === "verification" ? ButtonStyle.Primary : ButtonStyle.Secondary),
new ButtonBuilder()
.setLabel("Favorites")
.setCustomId("help.favorites")
.setStyle(currentPage === "favorites" ? ButtonStyle.Primary : ButtonStyle.Secondary),
new ButtonBuilder()
.setLabel("Info")
.setCustomId("help.info")
.setStyle(currentPage === "info" ? ButtonStyle.Primary : ButtonStyle.Secondary),
new ButtonBuilder()
.setLabel("Setup")
.setCustomId("help.setup")
.setStyle(currentPage === "setup" ? ButtonStyle.Primary : ButtonStyle.Secondary),
);
const row2 = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel("About")
.setCustomId("help.about")
.setStyle(currentPage === "about" ? ButtonStyle.Primary : ButtonStyle.Secondary),
);
return [row1, row2];
}
}