Skip to content

Commit 39c02e3

Browse files
committed
feat: add "dm" thread
1 parent 0bf1098 commit 39c02e3

File tree

5 files changed

+188
-76
lines changed

5 files changed

+188
-76
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ POINT_DECAY_TIMER=24
2727
MOD_ROLE_ID=
2828
ADMIN_ROLE_ID=
2929
SERVER_ID= # Local environment server ID
30+
MODMAIL_CHANNEL_ID=
31+
DM_ALT_CHANNEL_ID=
3032

3133
POINT_LIMITER_IN_MINUTES=
3234
# Used for checking if the user has given a point to another user within the timeframe provided

src/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const {DISCORD_TOKEN} = process.env;
88
export const {REPO_LINK} = process.env;
99

1010
export const {MODMAIL_CHANNEL_ID} = process.env;
11+
export const {DM_ALT_CHANNEL_ID} = process.env
1112
export const {MOD_CHANNEL} = process.env;
1213
export const {NUMBER_OF_ALLOWED_MESSAGES} = process.env;
1314
export const {CACHE_REVALIDATION_IN_SECONDS} = process.env;
Lines changed: 148 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1-
import { MessageActionRow, MessageButton, MessageEmbed } from "discord.js";
1+
import type {
2+
DMChannel,
3+
Guild,
4+
Message,
5+
MessageOptions,
6+
MessagePayload,
7+
TextChannel,
8+
ThreadChannel} from 'discord.js';
9+
import {
10+
MessageActionRow,
11+
MessageButton,
12+
MessageEmbed
13+
} from 'discord.js';
214

3-
import type { CommandDataWithHandler } from "../../../../types";
4-
import { SERVER_ID, MOD_ROLE_ID } from "../../../env";
5-
import { ModMailThread } from "../db/modmail_thread";
6-
import { COLOR_DELETED_MSG } from "../util/colors";
15+
import type { CommandDataWithHandler } from '../../../../types';
16+
import { SERVER_ID, MOD_ROLE_ID, DM_ALT_CHANNEL_ID } from '../../../env';
17+
import { DMThread } from '../db/dm_thread';
18+
import { ModMailThread } from '../db/modmail_thread';
19+
import { COLOR_DELETED_MSG, COLOR_MOD } from '../util/colors';
720

821
export const sendCommand: CommandDataWithHandler = {
922
type: 'CHAT_INPUT',
@@ -37,98 +50,160 @@ export const sendCommand: CommandDataWithHandler = {
3750
interaction.deferReply(),
3851
]);
3952

40-
if(!modmailThread) {
41-
interaction.editReply({ content: "Could not find modmail thread" })
53+
if (!modmailThread) {
54+
interaction.editReply({ content: 'Could not find modmail thread' });
4255
return;
4356
}
4457
if (modmailThread.closedAt) {
45-
interaction.editReply({ content: "This modmail thread is closed, no messages can be sent through this thread anymore to the user. It remains open to allow for discussions."})
46-
return
58+
interaction.editReply({
59+
content:
60+
'This modmail thread is closed, no messages can be sent through this thread anymore to the user. It remains open to allow for discussions.',
61+
});
62+
return;
4763
}
4864

49-
const user = await interaction.guild.members.fetch(modmailThread.userId)
50-
const dmChannel = await user.createDM()
51-
const msgContent = interaction.options.get('message').value
65+
const user = await interaction.guild.members.fetch(modmailThread.userId);
66+
const dmChannel = await user.createDM();
67+
const msgContent = interaction.options.get('message').value;
5268

53-
try {
54-
const dmMsg = await dmChannel.send({
55-
embeds: [
56-
new MessageEmbed()
57-
.setColor(0x00_f0_50)
69+
const dmMsgContent = {
70+
embeds: [
71+
new MessageEmbed()
72+
.setColor(COLOR_MOD)
5873
.setAuthor({
5974
name: `A Moderator`,
6075
})
6176
.setDescription(msgContent as string)
6277
.setTitle('Message Received')
63-
.setTimestamp(interaction.createdTimestamp)
64-
]
65-
})
78+
.setTimestamp(interaction.createdTimestamp),
79+
],
80+
};
81+
82+
try {
83+
const dmMsg = await sendDm(interaction.guild, dmChannel, dmMsgContent);
6684

6785
interaction.editReply({
6886
embeds: [
6987
new MessageEmbed()
70-
.setColor(0x00_f0_50)
71-
.setAuthor({
72-
name: `${interaction.user.username}#${interaction.user.discriminator}`,
73-
iconURL: interaction.user.avatarURL()
74-
})
75-
.setDescription(msgContent as string)
76-
.setTitle('Message Sent')
77-
.setTimestamp(interaction.createdTimestamp)
88+
.setColor(0x00_f0_50)
89+
.setAuthor({
90+
name: `${interaction.user.username}#${interaction.user.discriminator}`,
91+
iconURL: interaction.user.avatarURL(),
92+
})
93+
.setDescription(msgContent as string)
94+
.setTitle('Message Sent')
95+
.setTimestamp(interaction.createdTimestamp),
7896
],
7997
components: [
8098
new MessageActionRow().addComponents(
8199
new MessageButton()
82-
.setLabel("Delete Msg")
83-
.setCustomId([
84-
"DELETE_MODMAIL",
85-
user.id,
86-
dmMsg.id,
87-
].join('🤔'))
88-
.setStyle("DANGER")
89-
)
90-
]
91-
})
92-
} catch {
93-
100+
.setLabel('Delete Msg')
101+
.setCustomId(['DELETE_MODMAIL', user.id, dmMsg.id].join('🤔'))
102+
.setStyle('DANGER')
103+
),
104+
],
105+
});
106+
} catch (error) {
107+
console.error(error)
94108
}
95109
},
96-
onAttach(client) {
97-
client.on('interactionCreate', async (interaction) => {
98-
if(!interaction.isButton()) { return }
110+
onAttach(client) {
111+
client.on('interactionCreate', async interaction => {
112+
if (!interaction.isButton()) {
113+
return;
114+
}
99115

100-
const [type, userId, msgId] = interaction.customId.split('🤔')
116+
const [type, userId, msgId] = interaction.customId.split('🤔');
101117

102-
if (type !== 'DELETE_MODMAIL') {
103-
return
104-
}
105-
await interaction.deferReply({ ephemeral: true })
106-
const member = await interaction.guild.members.fetch(userId)
118+
if (type !== 'DELETE_MODMAIL') {
119+
return;
120+
}
121+
await interaction.deferReply({ ephemeral: true });
122+
const member = await interaction.guild.members.fetch(userId);
107123

108-
if(!member) {
109-
interaction.editReply("The user could not be found in the server.")
110-
return
111-
}
124+
if (!member) {
125+
interaction.editReply('The user could not be found in the server.');
126+
return;
127+
}
112128

113-
const dmChannel = await member.createDM()
114-
const message = await dmChannel.messages.fetch(msgId)
115-
try {
116-
await message.delete()
117-
if("edit" in interaction.message) {
118-
interaction.message.edit({
119-
content: `*The message below was deleted by <@${interaction.user.id}> at <t:${Math.floor(Date.now()/1000)}>*`,
120-
embeds: [
121-
interaction.message.embeds[0].setColor(COLOR_DELETED_MSG)
122-
],
123-
components: []
124-
})
125-
interaction.message.react("🗑")
126-
}
127-
interaction.editReply("Message deleted, the message will remain here for auditings sake")
128-
} catch {
129-
interaction.editReply("Failed to delete the message.")
129+
const dmChannel = await member.createDM();
130+
const message = await dmChannel.messages.fetch(msgId);
131+
try {
132+
await message.delete();
133+
if ('edit' in interaction.message) {
134+
interaction.message.edit({
135+
content: `*The message below was deleted by <@${
136+
interaction.user.id
137+
}> at <t:${Math.floor(Date.now() / 1000)}>*`,
138+
embeds: [interaction.message.embeds[0].setColor(COLOR_DELETED_MSG)],
139+
components: [],
140+
});
141+
interaction.message.react('🗑');
130142
}
131-
})
132-
}
133-
143+
interaction.editReply(
144+
'Message deleted, the message will remain here for auditings sake'
145+
);
146+
} catch {
147+
interaction.editReply('Failed to delete the message.');
148+
}
149+
});
150+
},
134151
};
152+
async function sendDm(
153+
guild: Guild,
154+
dmChannel: DMChannel,
155+
msgContent: string | MessagePayload | MessageOptions
156+
) {
157+
try {
158+
return await dmChannel.send(msgContent);
159+
} catch {
160+
return sendFakeDM(guild, dmChannel, msgContent);
161+
}
162+
}
163+
164+
async function sendFakeDM(
165+
guild: Guild,
166+
dmChannel: DMChannel,
167+
content: string | MessagePayload | MessageOptions
168+
): Promise<Message> {
169+
const userId = dmChannel.recipient.id;
170+
171+
const dmThread = await DMThread.findOneAndUpdate(
172+
{
173+
guildId: guild.id,
174+
userId,
175+
},
176+
{
177+
channelId: DM_ALT_CHANNEL_ID,
178+
},
179+
{ upsert: true, new: true }
180+
).exec();
181+
182+
console.log(dmThread)
183+
184+
const channel = (await guild.channels.fetch(
185+
dmThread.channelId
186+
)) as TextChannel;
187+
let thread: ThreadChannel;
188+
189+
if (dmThread.threadId) {
190+
thread = await channel.threads.fetch(dmThread.threadId);
191+
} else {
192+
try {
193+
thread = await channel.threads.create({
194+
name: `${dmChannel.recipient.username}_${dmChannel.recipient.discriminator} [${userId}]`,
195+
type: 'GUILD_PRIVATE_THREAD',
196+
});
197+
} catch {
198+
thread = await channel.threads.create({
199+
name: `${dmChannel.recipient.username}_${dmChannel.recipient.discriminator} [${userId}]`,
200+
type: 'GUILD_PUBLIC_THREAD',
201+
});
202+
}
203+
204+
dmThread.updateOne({ threadId: thread.id }).exec();
205+
}
206+
207+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
208+
return thread.send(typeof content === 'string' ? `${dmChannel.recipient} ${content}` : { content: `${dmChannel.recipient} ${(content as {content?: string }).content ?? ''}`, ...content})
209+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import mongoose from 'mongoose';
2+
3+
const { model, Schema } = mongoose
4+
5+
const schema = new Schema({
6+
guildId: {
7+
required: true,
8+
type: String,
9+
},
10+
channelId: {
11+
required: true,
12+
type: String
13+
},
14+
threadId: {
15+
required: true,
16+
type: String,
17+
},
18+
userId: {
19+
required: true,
20+
type: String,
21+
},
22+
}, {
23+
timestamps: true
24+
});
25+
26+
export const DMThread = model<DMThreadType>('dmThread', schema);
27+
export type DMThreadType = {
28+
guildId: string,
29+
threadId: string,
30+
channelId: string,
31+
userId: string,
32+
createdAt: Date,
33+
updatedAt: Date
34+
};

src/v2/modules/modmail/util/createModMailThread.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Guild, ThreadChannel, User } from 'discord.js';
1+
import type { Guild, ThreadChannel, User } from 'discord.js';
2+
23
import { MODMAIL_CHANNEL_ID } from '../../../env.js';
34
import { ModMailThread } from '../db/modmail_thread.js';
45
import { cache } from "./cache";
@@ -26,9 +27,8 @@ export const createModMailThread = async (
2627
);
2728

2829
const threadChannel = await modmailChannel.threads.create({
29-
name: `${user.username}_${user.discriminator} ${date}`,
30+
name: `${user.username}_${user.discriminator} ${date} [${user.id}]`,
3031
autoArchiveDuration: 'MAX',
31-
reason: 'Test',
3232
});
3333

3434
await ModMailThread.create({

0 commit comments

Comments
 (0)