Skip to content

Commit

Permalink
fix(network): Deprecate non-webhook messages (#15)
Browse files Browse the repository at this point in the history
* fix(network): Deprecate non-webhook messages

* fix(network): Fixed  being able to switch to other connected channels

* feat(network): Use embeds for compact replies

* fix: Updated editing message for new replies/webhooks

* fix: temp error handling for webhook creations
  • Loading branch information
dev-737 committed Jun 11, 2023
1 parent 8ae712a commit 6c3de66
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 309 deletions.
87 changes: 30 additions & 57 deletions src/Commands/Network/editMsg.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ContextMenuCommandBuilder, MessageContextMenuCommandInteraction, ApplicationCommandType, ModalBuilder, ActionRowBuilder, TextInputBuilder, TextInputStyle, WebhookClient, EmbedBuilder, GuildTextBasedChannel } from 'discord.js';
import { ContextMenuCommandBuilder, MessageContextMenuCommandInteraction, ApplicationCommandType, ModalBuilder, ActionRowBuilder, TextInputBuilder, TextInputStyle, WebhookClient, EmbedBuilder } from 'discord.js';
import { networkMsgUpdate } from '../../Scripts/networkLogs/msgUpdate';
import { checkIfStaff, getDb, topgg } from '../../Utils/functions/utils';
import { checkIfStaff, getDb, getGuildName, topgg } from '../../Utils/functions/utils';
import wordFiler from '../../Utils/functions/wordFilter';
import logger from '../../Utils/logger';

Expand All @@ -15,7 +15,7 @@ export default {
const isStaff = checkIfStaff(interaction.user.id);

if (!hasVoted && !isStaff) {
interaction.reply({
await interaction.reply({
content: `${interaction.client.emotes.normal.no} You must [vote](<https://top.gg/bot/${interaction.client.user.id}/vote>) to use this command.`,
ephemeral: true,
});
Expand All @@ -29,7 +29,7 @@ export default {

if (!messageInDb) {
await interaction.reply({
content: 'This message has expired :(',
content: 'This message has expired. If not, please wait a few seconds and try again.',
ephemeral: true,
});
return;
Expand All @@ -40,8 +40,7 @@ export default {
return;
}

const replyRegex = /> .*/g;
const placeholder = target.embeds[0]?.fields[0]?.value || target.content.replace(`**${interaction.user.tag}:**`, '');
const placeholder = target.content || target.embeds[0]?.description;

const modal = new ModalBuilder()
.setCustomId(interaction.id)
Expand All @@ -50,10 +49,10 @@ export default {
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
.setRequired(true)
.setCustomId('editMessage')
.setCustomId('newMessage')
.setStyle(TextInputStyle.Paragraph)
.setLabel('Please enter your new message.')
.setValue(placeholder.replace(replyRegex, '').trim())
.setValue(`${placeholder}`)
.setMaxLength(950),
),
);
Expand All @@ -63,70 +62,44 @@ export default {
interaction.awaitModalSubmit({ filter: (i) => i.user.id === interaction.user.id && i.customId === modal.data.custom_id, time: 30_000 })
.then(i => {
// get the input from user
const editMessage = i.fields.getTextInputValue('editMessage');
const censoredEditMessage = wordFiler.censor(editMessage);

let editEmbed = new EmbedBuilder(target.embeds[0]?.toJSON());
const reply = editEmbed?.data.fields?.at(0)?.value.match(replyRegex)?.at(0) || target.content.match(replyRegex)?.at(0);

editEmbed.setFields({
name: 'Message',
value: reply ? `${reply}\n${editMessage}` : editMessage,
const newMessage = i.fields.getTextInputValue('newMessage');
const censoredNewMessage = wordFiler.censor(newMessage);

// get the reply from the target message
const newEmbed = !target.content
? EmbedBuilder.from(target.embeds[0])
: new EmbedBuilder()
.setAuthor({ name: target.author.username, iconURL: target.author.displayAvatarURL() })
.setDescription(target.content)
.setColor(target.member?.displayHexColor ?? 'Random')
.addFields(target.embeds[0] ? [{ name: 'Reply-to', value: `${target.embeds[0].description}` }] : [])
.setFooter({ text: `Server: ${getGuildName(interaction.client, messageInDb.serverId)}` });

const censoredEmbed = EmbedBuilder.from(newEmbed).setDescription(censoredNewMessage);

i.reply({
content: `${interaction.client.emotes.normal.yes} Message Edited. Please give a few seconds for it to reflect in all connections.`,
ephemeral: true,
});
let censoredEmbed = new EmbedBuilder(target.embeds[0]?.toJSON())
.setFields({
name: 'Message',
value: reply ? `${reply}\n${censoredEditMessage}` : censoredEditMessage,
});

// loop through all the channels in the network and edit the message
messageInDb.channelAndMessageIds.forEach(async obj => {
const channelSettings = await db.connectedList.findFirst({ where: { channelId: obj.channelId } });
const channel = await interaction.client.channels.fetch(obj.channelId) as GuildTextBasedChannel;
const message = await channel?.messages?.fetch(obj.messageId).catch(() => null);

// if target message is in compact mode, get the normal mode from another message in the network
if (!target.embeds[0] && message?.embeds[0]) {
target.embeds[0] = message.embeds[0]; // updating for message logs
editEmbed = new EmbedBuilder(message.embeds[0].data).setFields({
name: 'Message',
value: reply ? `${reply}\n${editMessage}` : editMessage,
});
censoredEmbed = new EmbedBuilder(message.embeds[0].data).setFields({
name: 'Message',
value: reply ? `${reply}\n${censoredEditMessage}` : censoredEditMessage,
});
}

if (channelSettings?.webhook) {
const { id, token } = channelSettings.webhook;

const replyCompact = `${reply}\n ${channelSettings?.profFilter ? editMessage : censoredEditMessage}`;
const compact = channelSettings?.profFilter ? editMessage : censoredEditMessage;
const webhookEmbed = channelSettings?.profFilter ? censoredEmbed : editEmbed;
const webhook = new WebhookClient({ id, token });
const webhook = new WebhookClient({ id: channelSettings.webhook.id, token: channelSettings.webhook.token });
const compact = channelSettings?.profFilter ? newMessage : censoredNewMessage;
const webhookEmbed = channelSettings?.profFilter ? censoredEmbed : newEmbed;

channelSettings?.compact
? webhook.editMessage(obj.messageId, reply ? replyCompact : compact)
? webhook.editMessage(obj.messageId, compact)
: webhook.editMessage(obj.messageId, { files: [], embeds: [webhookEmbed] });
}

else {
const replyFormat = `${reply}\n**${i.user.tag}:** ${channelSettings?.profFilter ? censoredEditMessage : editMessage}`;
const compactFormat = `**${i.user.tag}:** ${channelSettings?.profFilter ? censoredEditMessage : editMessage}`;
const normalEmbed = channelSettings?.profFilter ? censoredEmbed : editEmbed;

channelSettings?.compact
? message?.edit(reply ? replyFormat : compactFormat)
: message?.edit({ files: [], embeds: [normalEmbed] });
}
});

i.reply({ content: `${interaction.client.emotes.normal.yes} Message Edited.`, ephemeral: true });

const newMessageObject = {
id: target.id,
content: editMessage,
content: newMessage,
timestamp: target.editedAt ?? target.createdAt,
};

Expand Down
181 changes: 92 additions & 89 deletions src/Events/messageCreate.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import checks from '../Scripts/message/checks';
import addBadges from '../Scripts/message/addBadges';
import messageContentModifiers from '../Scripts/message/messageContentModifiers';
import messageFormats from '../Scripts/message/messageFormatting';
import cleanup from '../Scripts/message/cleanup';
import { ActionRowBuilder, APIMessage, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, Message, MessageCreateOptions, User, WebhookClient, WebhookMessageCreateOptions } from 'discord.js';
import { getDb, colors } from '../Utils/functions/utils';
import { ActionRowBuilder, APIMessage, ButtonBuilder, ButtonStyle, ChannelType, EmbedBuilder, Message, User, WebhookClient } from 'discord.js';
import { getDb, colors, constants } from '../Utils/functions/utils';
import { censor } from '../Utils/functions/wordFilter';
import { connectedList, messageData } from '@prisma/client';
import { messageData } from '@prisma/client';

export interface NetworkMessage extends Message {
compact_message: string,
censored_compact_message: string,
censored_content: string,
}

Expand All @@ -32,20 +30,30 @@ export default {
const channelInDb = await db.connectedList.findFirst({ where: { channelId: message.channel.id } });

if (channelInDb?.connected) {
const otherChannelsInHub = await db.connectedList.findMany({
where: {
hubId: channelInDb.hubId,
connected: true,
},
});

// ignore the message if it is not in an active network channel
if (!await checks.execute(message, channelInDb)) return;
message.compact_message = `**${message.author.tag}:** ${message.content}`;

message.censored_content = censor(message.content);
const attachment = messageContentModifiers.getAttachment(message);
const attachmentURL = !attachment ? await messageContentModifiers.getAttachmentURL(message) : undefined;

const embed = new EmbedBuilder()
.setDescription(message.content)
.setImage(attachment ? `attachment://${attachment.name}` : attachmentURL || null)
.setColor(colors('random'))
.setAuthor({
name: `@${message.author.tag}`,
iconURL: message.author.displayAvatarURL() || message.author.defaultAvatarURL,
url: `https://discord.com/users/${message.author.id}`,
})
.setFooter({
text: `Server: ${message.guild?.name}`,
iconURL: message.guild?.iconURL() || undefined,
});

// author of the message being replied to
let referredAuthor: User | undefined;
let replyInDb: messageData | null;
// fetched author of the message being replied to
let repliedAuthor: User | undefined;
let referedMsgEmbed: EmbedBuilder | undefined; // for compact messages

if (message.reference) {
const referredMessage = await message.fetchReference().catch(() => null);
Expand All @@ -56,52 +64,46 @@ export default {
},
});

// Add quoted reply to original message and embed
await messageContentModifiers.appendReply(message, referredMessage, replyInDb);
repliedAuthor = replyInDb
referredAuthor = replyInDb
? await message.client.users.fetch(replyInDb?.authorId).catch(() => undefined)
: undefined;

// Add quoted reply to embeds
embed.addFields({
name: 'Reply-to:',
value: `${messageContentModifiers.getReferredContent(message, referredMessage)}`,
});
referedMsgEmbed = new EmbedBuilder()
.setColor(embed.data.color || 'Random')
.setDescription(`${messageContentModifiers.getReferredContent(message, referredMessage)?.replace(/^/gm, '> ')}`)
.setAuthor({
name: `@${referredAuthor?.username}`,
iconURL: referredAuthor?.avatarURL() || undefined,
});
}
}

const embed = new EmbedBuilder()
.setTimestamp()
.setColor(colors('random'))
.addFields({ name: 'Message', value: message.content || '\u200B' })
.setAuthor({
name: message.author.tag,
iconURL: message.author.avatarURL() || message.author.defaultAvatarURL,
url: `https://discord.com/users/${message.author.id}`,
})
.setFooter({
text: `From: ${message.guild}`,
iconURL: message.guild?.iconURL()?.toString(),
});

// Once reply is appended to the message, run it through the word fillter
message.censored_content = censor(message.content);
message.censored_compact_message = censor(message.compact_message);
const censoredEmbed = new EmbedBuilder(embed.data).setFields({ name: 'Message', value: message.censored_content || '\u200B' });

const attachments = await messageContentModifiers.attachImageToEmbed(message, embed, censoredEmbed);
await addBadges.execute(message, db, embed, censoredEmbed);

const channelAndMessageIds: Promise<NetworkWebhookSendResult | NetworkSendResult>[] = [];
// define censored embed after reply is added to reflect that in censored embed as well
const censoredEmbed = new EmbedBuilder(embed.data).setDescription(message.censored_content);
// await addBadges.execute(message, db, embed, censoredEmbed);

// send the message to all connected channels in apropriate format (webhook/compact/normal)
otherChannelsInHub?.forEach((connection) => {
const messageResults: Promise<NetworkWebhookSendResult | NetworkSendResult>[] = [];
const hubConnections = await db.connectedList.findMany({
where: { hubId: channelInDb.hubId, connected: true },
});
hubConnections?.forEach((connection) => {
const result = (async () => {
const channelToSend = message.client.channels.cache.get(connection.channelId);
if (!channelToSend || !channelToSend.isTextBased()) return { channelId: connection.channelId } as NetworkSendResult;

const reply = replyInDb?.channelAndMessageIds.find((msg) => msg.channelId === connection.channelId);

const replyButton = reply
? new ActionRowBuilder<ButtonBuilder>().addComponents(new ButtonBuilder()
.setLabel((
repliedAuthor && repliedAuthor.tag.length >= 80
? repliedAuthor.tag.slice(0, 76) + '...'
: repliedAuthor?.tag) || 'Jump',
.setLabel(
(referredAuthor && referredAuthor.tag.length >= 80
? referredAuthor.tag.slice(0, 76) + '...'
: referredAuthor?.tag) || 'Jump',
)
.setStyle(ButtonStyle.Link)
.setEmoji(message.client.emotes.normal.reply)
Expand All @@ -110,58 +112,59 @@ export default {

if (connection?.webhook) {
const webhook = new WebhookClient({ id: `${connection?.webhook?.id}`, token: `${connection?.webhook?.token}` });
const webhookMessage = createWebhookOptions(message, attachments, replyButton, connection, censoredEmbed, embed);
const webhookMessage = messageFormats.createWebhookOptions(message, connection, replyButton,
{ censored: censoredEmbed, normal: embed, reply: referedMsgEmbed }, attachment);
const webhookSendRes = await webhook.send(webhookMessage).catch(() => null);
return { webhookId: webhook.id, message: webhookSendRes } as NetworkWebhookSendResult;
}

const normalOptions = createSendOptions(message, attachments, replyButton, connection, censoredEmbed, embed);
// TODO: recheck if everything is good to go
const guild = message.client.guilds.cache.get(connection.serverId);
const channel = guild?.channels.cache.get(connection.channelId);

if (channel?.type === ChannelType.GuildText) {
try {
channel.send(
message.client.emotes.normal.loading + 'All Networks must now use webhooks for faster performance and to avoid frequent rate limits. Attempting to create webhook...',
).catch(() => null);

const hook = await channel.createWebhook({
name: `${message.author.username} Network`,
avatar: message.client.user.avatarURL(),
reason: 'All networks must use webhooks now.',
});

if (hook.token) {
await db.connectedList.update({
where: { id: connection.id },
data: { webhook: { set: { id: hook.id, token: hook.token, url: hook.url } } },
});
}

}
catch {
channel.send(
message.client.emotes.normal.no + 'Failed to create webhook. Please make sure I have the `Manage Webhooks` permission in this channel and reconnect to the network (`/network manage`).',
).catch(() => null);
const logChannel = message.client.channels.cache.get(constants.channel.networklogs) as any;
logChannel?.send(`Failed to create webhook in \`#${channel.name}\` (${channel.id}) in ${guild} (${guild?.id})`).catch(() => null);
await db.connectedList.update({ where: { id: connection.id }, data: { connected: false } });
}
}

const normalOptions = messageFormats.createEmbedOptions(attachment, replyButton, connection,
{ censored: censoredEmbed, normal: embed });
const sendResult = await channelToSend.send(normalOptions).catch(() => null);
return { channelId: channelToSend.id, message: sendResult } as NetworkSendResult;
})();

channelAndMessageIds.push(result);
messageResults.push(result);
});

// disconnect unknown channels & insert message into messageData collection for future use
cleanup.execute(message, channelAndMessageIds, channelInDb.hubId);
const resolvedMessages = await Promise.all(messageResults);
cleanup.execute(message, resolvedMessages, channelInDb.hubId);
}
},
};


// decides which type of (normal) message to send depending on the settings of channel
const createSendOptions = (message: NetworkMessage, attachments: AttachmentBuilder | undefined, replyButton: ActionRowBuilder<ButtonBuilder> | null, channelInSetup: connectedList, censoredEmbed: EmbedBuilder, embed: EmbedBuilder) => {
const options: MessageCreateOptions = {
files: attachments ? [attachments] : [],
components: replyButton ? [replyButton] : [],
allowedMentions: { parse: [] },
};

channelInSetup.compact
? options.content = channelInSetup.profFilter ? message.censored_compact_message : message.compact_message
: options.embeds = [channelInSetup.profFilter ? censoredEmbed : embed];

return options;
};

// decides which type of (webhook) message to send depending on the settings of channel
const createWebhookOptions = (message: NetworkMessage, attachments: AttachmentBuilder | undefined, replyButton: ActionRowBuilder<ButtonBuilder> | null, channelInSetup: connectedList, censoredEmbed: EmbedBuilder, embed: EmbedBuilder) => {
const webhookMessage: WebhookMessageCreateOptions = {
username: message.author.tag,
avatarURL: message.author.avatarURL() || message.author.defaultAvatarURL,
files: attachments ? [attachments] : [],
components: replyButton ? [replyButton] : [],
allowedMentions: { parse: [] },
};

if (channelInSetup.compact) {
webhookMessage.content = channelInSetup?.profFilter ? message.censored_content : message.content;
}
else {
webhookMessage.embeds = [channelInSetup?.profFilter ? censoredEmbed : embed];
webhookMessage.username = message.client.user.username;
webhookMessage.avatarURL = message.client.user.avatarURL() || undefined;
}
return webhookMessage;
};
Loading

0 comments on commit 6c3de66

Please sign in to comment.