/[sudobot]/branches/8.x/src/services/SurveyService.ts
ViewVC logotype

Contents of /branches/8.x/src/services/SurveyService.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 577 - (show annotations)
Mon Jul 29 18:52:37 2024 UTC (8 months ago) by rakinar2
File MIME type: application/typescript
File size: 8699 byte(s)
chore: add old version archive branches (2.x to 9.x-dev)
1 /**
2 * This file is part of SudoBot.
3 *
4 * Copyright (C) 2021-2024 OSN Developers.
5 *
6 * SudoBot is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * SudoBot is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with SudoBot. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 import {
21 ActionRowBuilder,
22 ButtonInteraction,
23 CacheType,
24 ChatInputCommandInteraction,
25 GuildMember,
26 HeadingLevel,
27 Interaction,
28 ModalBuilder,
29 ModalSubmitInteraction,
30 PermissionsString,
31 TextInputBuilder,
32 TextInputStyle,
33 heading
34 } from "discord.js";
35 import Service from "../core/Service";
36 import { GatewayEventListener } from "../decorators/GatewayEventListener";
37 import { HasEventListeners } from "../types/HasEventListeners";
38 import { userInfo } from "../utils/embed";
39 import { safeChannelFetch } from "../utils/fetch";
40 import { chunkedString } from "../utils/utils";
41 import { GuildConfig } from "./ConfigManager";
42
43 export const name = "surveyService";
44
45 export default class SurveyService extends Service implements HasEventListeners {
46 @GatewayEventListener("interactionCreate")
47 public async onInteractionCreate(interaction: Interaction<CacheType>) {
48 const config = this.client.configManager.config[interaction.guildId!]?.survey;
49
50 if (!config?.enabled) {
51 return;
52 }
53
54 if (interaction.isButton() && interaction.customId.startsWith("survey_")) {
55 await this.onSurveyShowRequest(interaction);
56 return;
57 }
58
59 if (interaction.isModalSubmit() && interaction.customId.startsWith("survey_")) {
60 await this.onModalSubmit(interaction);
61 return;
62 }
63 }
64
65 public async onSurveyShowRequest(interaction: ButtonInteraction | ChatInputCommandInteraction) {
66 const config = this.client.configManager.config[interaction.guildId!]?.survey;
67
68 if (!config?.enabled) {
69 await interaction.reply({
70 content: "Sorry, surveys are not enabled on this server.",
71 ephemeral: true
72 });
73
74 return;
75 }
76
77 const customId = interaction.isButton()
78 ? interaction.customId.replace(/^survey_/, "")
79 : interaction.options.getString("survey", true);
80 const surveyConfig = config.surveys[customId as unknown as keyof typeof config.surveys];
81
82 if (!surveyConfig) {
83 await interaction.reply({
84 content: "That survey does not exist!",
85 ephemeral: true
86 });
87
88 return;
89 }
90
91 if (!this.checkPreconditions(interaction, surveyConfig)) {
92 await interaction.reply({
93 content: "Sorry, you do not meet the requirements to fill out this survey.",
94 ephemeral: true
95 });
96
97 return;
98 }
99
100 const modal = this.buildModal(customId.toLowerCase(), surveyConfig);
101 await interaction.showModal(modal);
102 }
103
104 public async onModalSubmit(interaction: ModalSubmitInteraction<CacheType>) {
105 const config = this.client.configManager.config[interaction.guildId!]?.survey;
106
107 if (!config?.enabled) {
108 await interaction.reply({
109 content: "Sorry, surveys are not enabled on this server.",
110 ephemeral: true
111 });
112
113 return;
114 }
115
116 const customId = interaction.customId.replace(/^survey_/, "");
117 const surveyConfig = config.surveys[customId as unknown as keyof typeof config.surveys];
118
119 if (!surveyConfig) {
120 await interaction.reply({
121 content: "That survey does not exist!",
122 ephemeral: true
123 });
124
125 return;
126 }
127
128 if (!this.checkPreconditions(interaction, surveyConfig)) {
129 await interaction.reply({
130 content: "Sorry, you do not meet the requirements to fill out this survey.",
131 ephemeral: true
132 });
133
134 return;
135 }
136
137 let i = 0;
138 let summary = `${heading(
139 `Survey completed by <@${interaction.user.id}> (${interaction.user.id})`,
140 HeadingLevel.One
141 )}\n${userInfo(interaction.user)}\n\n`;
142
143 for (const question of surveyConfig.questions) {
144 const answer = interaction.fields.getTextInputValue(
145 `survey_${customId}_question_${i++}`
146 );
147
148 if (question.required && !answer) {
149 await interaction.reply({
150 content: "You must fill out all required questions!",
151 ephemeral: true
152 });
153
154 return;
155 }
156
157 summary += `${heading(question.question, HeadingLevel.Three)}\n${answer}\n`;
158 }
159
160 await interaction.reply({
161 content: surveyConfig.end_message ?? "Thank you for filling out the form!",
162 ephemeral: true
163 });
164
165 const logChannelId = surveyConfig.log_channel ?? config.default_log_channel;
166
167 if (!logChannelId) {
168 this.client.logger.warn("No log channel specified for survey completion.");
169 return;
170 }
171
172 const logChannel = await safeChannelFetch(interaction.guild!, logChannelId);
173
174 if (!logChannel?.isTextBased()) {
175 this.client.logger.warn(
176 "Log channel specified for survey completion does not exist or is not text-based."
177 );
178
179 return;
180 }
181
182 chunkedString(summary, 2000).forEach(chunk =>
183 logChannel.send({
184 content: chunk,
185 allowedMentions: { parse: [], roles: [], users: [] }
186 })
187 );
188 }
189
190 private checkPreconditions(
191 interaction: Interaction,
192 surveyConfig: NonNullable<GuildConfig["survey"]>["surveys"][string]
193 ) {
194 return (
195 surveyConfig &&
196 (!surveyConfig.required_channels?.length ||
197 surveyConfig.required_channels.includes(interaction.channelId!)) &&
198 (!surveyConfig.required_roles?.length ||
199 (interaction.member instanceof GuildMember &&
200 surveyConfig.required_roles.some(role =>
201 (interaction.member as GuildMember)?.roles.cache.has(role)
202 ))) &&
203 (!surveyConfig.required_permissions?.length ||
204 (interaction.member as GuildMember).permissions.has(
205 surveyConfig.required_permissions as PermissionsString[],
206 true
207 )) &&
208 (!surveyConfig.required_users?.length ||
209 surveyConfig.required_users.includes(interaction.user.id))
210 );
211 }
212
213 public buildModal(
214 customId: string,
215 surveyConfig: NonNullable<GuildConfig["survey"]>["surveys"][string]
216 ) {
217 const components: ActionRowBuilder<TextInputBuilder>[] = [];
218 let i = 0;
219
220 for (const question of surveyConfig.questions) {
221 const input = new TextInputBuilder()
222 .setCustomId(`survey_${customId}_question_${i}`)
223 .setLabel(question.question);
224
225 if (question.maxLength) {
226 input.setMaxLength(question.maxLength);
227 }
228
229 if (question.minLength) {
230 input.setMinLength(question.minLength);
231 }
232
233 if (question.placeholder) {
234 input.setPlaceholder(question.placeholder);
235 }
236
237 if (question.default_value) {
238 input.setValue(question.default_value);
239 }
240
241 const row = new ActionRowBuilder<TextInputBuilder>().addComponents(
242 input
243 .setRequired(question.required)
244 .setStyle(
245 question.type === "paragraph"
246 ? TextInputStyle.Paragraph
247 : TextInputStyle.Short
248 )
249 );
250
251 components.push(row);
252 i++;
253 }
254
255 return new ModalBuilder()
256 .setCustomId(`survey_${customId}`)
257 .setTitle(surveyConfig.name)
258 .setComponents(components);
259 }
260 }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26