Skip to content

Commit ecb8270

Browse files
feat(verification): add static verification method [NAE]
This update cannot be automatically installed as it contains changes to the database schema. Please run the migration file 0004_verification_method_static.sql manually. Signed-off-by: GitHub <[email protected]>
1 parent a00aa21 commit ecb8270

File tree

6 files changed

+146
-45
lines changed

6 files changed

+146
-45
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TYPE "public"."verification_method" ADD VALUE 'channel_static_interaction';--> statement-breakpoint

drizzle/meta/_journal.json

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,41 @@
11
{
2-
"version": "7",
3-
"dialect": "postgresql",
4-
"entries": [
5-
{
6-
"idx": 0,
7-
"version": "7",
8-
"when": 1722191553959,
9-
"tag": "0000_init",
10-
"breakpoints": true
11-
},
12-
{
13-
"idx": 1,
14-
"version": "7",
15-
"when": 1724739546407,
16-
"tag": "0001_verification_entries",
17-
"breakpoints": true
18-
},
19-
{
20-
"idx": 2,
21-
"version": "7",
22-
"when": 1736528127478,
23-
"tag": "0002_infraction_attachments",
24-
"breakpoints": true
25-
},
26-
{
27-
"idx": 3,
28-
"version": "7",
29-
"when": 1736697049685,
30-
"tag": "0003_infraction_type_reaction_clear",
31-
"breakpoints": true
32-
}
33-
]
34-
}
2+
"version": "7",
3+
"dialect": "postgresql",
4+
"entries": [
5+
{
6+
"idx": 0,
7+
"version": "7",
8+
"when": 1722191553959,
9+
"tag": "0000_init",
10+
"breakpoints": true
11+
},
12+
{
13+
"idx": 1,
14+
"version": "7",
15+
"when": 1724739546407,
16+
"tag": "0001_verification_entries",
17+
"breakpoints": true
18+
},
19+
{
20+
"idx": 2,
21+
"version": "7",
22+
"when": 1736528127478,
23+
"tag": "0002_infraction_attachments",
24+
"breakpoints": true
25+
},
26+
{
27+
"idx": 3,
28+
"version": "7",
29+
"when": 1736697049685,
30+
"tag": "0003_infraction_type_reaction_clear",
31+
"breakpoints": true
32+
},
33+
{
34+
"idx": 4,
35+
"version": "7",
36+
"when": 1738490931635,
37+
"tag": "0004_verification_method_static",
38+
"breakpoints": true
39+
}
40+
]
41+
}

src/main/typescript/automod/VerificationService.ts

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { verificationEntries } from "@main/models/VerificationEntry";
3030
import { VerificationMethod, verificationRecords } from "@main/models/VerificationRecord";
3131
import VerificationExpiredQueue from "@main/queues/VerificationExpiredQueue";
3232
import type ConfigurationManager from "@main/services/ConfigurationManager";
33+
import type DirectiveParsingService from "@main/services/DirectiveParsingService";
3334
import type ModerationActionService from "@main/services/ModerationActionService";
3435
import { getAxiosClient } from "@main/utils/axios";
3536
import { formatDistanceToNowStrict } from "date-fns";
@@ -53,6 +54,9 @@ class VerificationService extends Service {
5354
@Inject("moderationActionService")
5455
private readonly moderationActionService!: ModerationActionService;
5556

57+
@Inject("directiveParsingService")
58+
private readonly directiveParsingService!: DirectiveParsingService;
59+
5660
private configFor(guildId: Snowflake) {
5761
return this.configManager.config[guildId]?.member_verification;
5862
}
@@ -68,7 +72,7 @@ class VerificationService extends Service {
6872

6973
const memberId = interaction.customId.split("_")[1];
7074

71-
if (interaction.user.id !== memberId) {
75+
if (memberId !== "static" && interaction.user.id !== memberId) {
7276
return void (await interaction.reply({
7377
content: "This button is not under your control.",
7478
ephemeral: true
@@ -84,11 +88,11 @@ class VerificationService extends Service {
8488
}));
8589
}
8690

87-
if (config.method !== "channel_interaction") {
88-
return void (await interaction.reply({
89-
content: "This server does not have this verification method enabled.",
90-
ephemeral: true
91-
}));
91+
if (
92+
(memberId !== "static" && config.method !== "channel_interaction") ||
93+
(memberId === "static" && config.method !== "channel_static_interaction")
94+
) {
95+
return;
9296
}
9397

9498
await interaction.deferReply({ ephemeral: true });
@@ -102,6 +106,36 @@ class VerificationService extends Service {
102106
}
103107
});
104108

109+
if (memberId === "static") {
110+
const url = entry
111+
? this.getVerificationURL(interaction.guildId, interaction.user.id, entry.token)
112+
: await this.startVerification(
113+
interaction.member as GuildMember,
114+
"Verification requested by user.",
115+
true
116+
);
117+
118+
if (!url) {
119+
return void (await interaction.editReply({
120+
content: "Failed to start verification."
121+
}));
122+
}
123+
124+
await interaction.editReply({
125+
content: `Hi **${interaction.user.username}**! Please click the button below. Alternatively, you can verify yourself by copy-pasting the following link in your browser.\n${url}`,
126+
components: [
127+
new ActionRowBuilder<ButtonBuilder>().addComponents(
128+
new ButtonBuilder()
129+
.setStyle(ButtonStyle.Link)
130+
.setLabel("Verify")
131+
.setURL(url)
132+
)
133+
]
134+
});
135+
136+
return;
137+
}
138+
105139
if (!entry) {
106140
return void (await interaction.editReply({
107141
content: "This verification session has expired."
@@ -111,7 +145,7 @@ class VerificationService extends Service {
111145
const url = this.getVerificationURL(interaction.guildId, interaction.user.id, entry.token);
112146

113147
await interaction.editReply({
114-
content: `Please click the button below. Alternatively, you can verify yourself by copy-pasting the following link in your browser.\n${url}`,
148+
content: `Hi **${interaction.user.username}**! Please click the button below. Alternatively, you can verify yourself by copy-pasting the following link in your browser.\n${url}`,
115149
components: [
116150
new ActionRowBuilder<ButtonBuilder>().addComponents(
117151
new ButtonBuilder().setStyle(ButtonStyle.Link).setLabel("Verify").setURL(url)
@@ -166,7 +200,7 @@ class VerificationService extends Service {
166200
return `${domain}${domain === env.FRONTEND_URL ? "/verify" : ""}/guilds/${encodeURIComponent(guildId)}/challenge/onboarding?t=${encodeURIComponent(token)}&u=${encodeURIComponent(memberId)}`;
167201
}
168202

169-
public async startVerification(member: GuildMember, reason: string) {
203+
public async startVerification(member: GuildMember, reason: string, silent = false) {
170204
const config = this.configFor(member.guild.id);
171205

172206
if (!config || !member.manageable) {
@@ -206,7 +240,60 @@ class VerificationService extends Service {
206240

207241
const url = this.getVerificationURL(member.guild.id, member.id, token);
208242

243+
if (silent) {
244+
return url;
245+
}
246+
209247
switch (config.method) {
248+
case "channel_static_interaction":
249+
if (!config.channel) {
250+
break;
251+
}
252+
253+
if (!config.message_id_internal) {
254+
try {
255+
const channel = await fetchChannel(member.guild, config.channel);
256+
257+
if (!channel?.isTextBased()) {
258+
break;
259+
}
260+
261+
const { data, output } = await this.directiveParsingService.parse(
262+
config.verification_message ??
263+
"Welcome to the server! Please verify yourself by clicking the button below."
264+
);
265+
const options = {
266+
content: output.trim() === "" ? undefined : output,
267+
embeds: (data.embeds as APIEmbed[]) ?? [],
268+
allowedMentions: { parse: [], roles: [], users: [] },
269+
components: [
270+
new ActionRowBuilder<ButtonBuilder>().addComponents(
271+
new ButtonBuilder()
272+
.setStyle(ButtonStyle.Secondary)
273+
.setLabel("Start Verification")
274+
.setCustomId("verify_static")
275+
)
276+
]
277+
};
278+
279+
const { id } = await channel.send(options);
280+
281+
if (this.configManager.config[member.guild.id]?.member_verification) {
282+
this.configManager.config[
283+
member.guild.id
284+
]!.member_verification!.message_id_internal = id;
285+
await this.configManager.write();
286+
}
287+
} catch (error) {
288+
this.logger.error(
289+
"Failed to send verification message to channel: ",
290+
error
291+
);
292+
}
293+
}
294+
295+
break;
296+
210297
case "channel_interaction":
211298
if (!config.channel) {
212299
break;
@@ -284,6 +371,8 @@ class VerificationService extends Service {
284371
})
285372
.schedule();
286373
}
374+
375+
return url;
287376
}
288377

289378
public async onVerificationExpire(guildId: Snowflake, memberId: Snowflake) {

src/main/typescript/core/DiscordKernel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class DiscordKernel extends Kernel {
8686
"@automod/SpamModerationService",
8787
"@automod/RuleModerationService",
8888
"@automod/RaidProtectionService",
89+
"@services/DirectiveParsingService",
8990
"@automod/VerificationService",
9091
"@automod/AIAutoModeration",
9192
"@services/QuickMuteService",
@@ -94,7 +95,6 @@ class DiscordKernel extends Kernel {
9495
"@services/AFKService",
9596
"@services/AutoRoleService",
9697
"@automod/TriggerService",
97-
"@services/DirectiveParsingService",
9898
"@services/MessageReportingService",
9999
"@services/WelcomerService",
100100
"@services/BumpReminderService",

src/main/typescript/models/VerificationRecord.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import { pgTable, serial, timestamp, varchar } from "drizzle-orm/pg-core";
2222

2323
export enum VerificationMethod {
2424
ChannelInteraction = "channel_interaction",
25-
DMInteraction = "dm_interaction"
25+
DMInteraction = "dm_interaction",
26+
ChannelStaticInteraction = "channel_static_interaction"
2627
}
2728

2829
export const verificationMethodEnum = pgEnum("verification_method", VerificationMethod);

src/main/typescript/schemas/GuildConfigSchema.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,11 @@ export const GuildConfigSchema = z.object({
231231
.describe("Max verification duration (in seconds)")
232232
.int()
233233
.optional(),
234-
method: z.enum(["channel_interaction", "dm_interaction"]).default("dm_interaction"),
235-
channel: zSnowflake.optional()
234+
method: z
235+
.enum(["channel_interaction", "dm_interaction", "channel_static_interaction"])
236+
.default("dm_interaction"),
237+
channel: zSnowflake.optional(),
238+
message_id_internal: zSnowflake.optional()
236239
})
237240
.optional(),
238241
quick_mute: z

0 commit comments

Comments
 (0)