import { eventHandlers } from "../bot.ts"; import { cache, cacheHandlers } from "../cache.ts"; import { sendDirectMessage } from "../helpers/members/send_direct_message.ts"; import { addReaction } from "../helpers/messages/add_reaction.ts"; import { addReactions } from "../helpers/messages/add_reactions.ts"; import { deleteMessage } from "../helpers/messages/delete_message.ts"; import { editMessage } from "../helpers/messages/edit_message.ts"; import { pinMessage } from "../helpers/messages/pin_message.ts"; import { removeAllReactions } from "../helpers/messages/remove_all_reactions.ts"; import { removeReaction } from "../helpers/messages/remove_reaction.ts"; import { removeReactionEmoji } from "../helpers/messages/remove_reaction_emoji.ts"; import { sendMessage } from "../helpers/messages/send_message.ts"; import { GuildMember } from "../types/guilds/guild_member.ts"; import { CreateMessage } from "../types/messages/create_message.ts"; import { EditMessage } from "../types/messages/edit_message.ts"; import { Message } from "../types/messages/message.ts"; import { bigintToSnowflake, snowflakeToBigint } from "../util/bigint.ts"; import { CHANNEL_MENTION_REGEX } from "../util/constants.ts"; import { createNewProp } from "../util/utils.ts"; import { DiscordenoChannel } from "./channel.ts"; import { DiscordenoGuild } from "./guild.ts"; import { DiscordenoMember } from "./member.ts"; import { DiscordenoRole } from "./role.ts"; const MESSAGE_SNOWFLAKES = [ "id", "channelId", "guildId", "webhookId", ]; const baseMessage: Partial = { get channel() { if (this.guildId) return cache.channels.get(this.channelId!); return cache.channels.get(this.authorId!); }, get guild() { if (!this.guildId) return undefined; return cache.guilds.get(this.guildId); }, get member() { if (!this.authorId) return undefined; return cache.members.get(this.authorId); }, get guildMember() { if (!this.guildId) return undefined; return this.member?.guilds.get(this.guildId); }, get link() { return `https://discord.com/channels/${this.guildId || "@me"}/${this.channelId}/${this.id}`; }, get mentionedRoles() { return this.mentionedRoleIds?.map((id) => this.guild?.roles.get(id)) || []; }, get mentionedChannels() { return this.mentionedChannelIds?.map((id) => cache.channels.get(id)) || []; }, get mentionedMembers() { return this.mentionedUserIds?.map((id) => cache.members.get(id)) || []; }, // METHODS delete(reason, delayMilliseconds) { return deleteMessage(this.channelId!, this.id!, reason, delayMilliseconds); }, edit(content) { return editMessage(this as DiscordenoMessage, content); }, pin() { return pinMessage(this.channelId!, this.id!); }, addReaction(reaction) { return addReaction(this.channelId!, this.id!, reaction); }, addReactions(reactions, ordered) { return addReactions(this.channelId!, this.id!, reactions, ordered); }, reply(content) { const contentWithMention: CreateMessage = typeof content === "string" ? { content, allowedMentions: { repliedUser: true, }, messageReference: { messageId: bigintToSnowflake(this.id!), failIfNotExists: false, }, } : { ...content, allowedMentions: { ...(content.allowedMentions || {}), repliedUser: true, }, messageReference: { messageId: bigintToSnowflake(this.id!), failIfNotExists: content.messageReference?.failIfNotExists === true, }, }; if (this.guildId) return sendMessage(this.channelId!, contentWithMention); return sendDirectMessage(this.authorId!, contentWithMention); }, send(content) { if (this.guildId) return sendMessage(this.channelId!, content); return sendDirectMessage(this.authorId!, content); }, alert(content, timeout = 10, reason = "") { if (this.guildId) { return sendMessage(this.channelId!, content).then((response) => { response.delete(reason, timeout * 1000).catch(console.error); }); } return sendDirectMessage(this.authorId!, content).then((response) => { response.delete(reason, timeout * 1000).catch(console.error); }); }, alertReply(content, timeout = 10, reason = "") { return this.reply!(content).then((response) => response.delete(reason, timeout * 1000).catch(console.error) ); }, removeAllReactions() { return removeAllReactions(this.channelId!, this.id!); }, removeReactionEmoji(reaction) { return removeReactionEmoji(this.channelId!, this.id!, reaction); }, removeReaction(reaction, userId) { return removeReaction(this.channelId!, this.id!, reaction, { userId }); }, }; export async function createDiscordenoMessage(data: Message) { const { guildId = "", mentionChannels = [], mentions = [], mentionRoles = [], editedTimestamp, author, messageReference, ...rest } = data; const props: Record> = {}; for (const [key, value] of Object.entries(rest)) { eventHandlers.debug?.( "loop", `Running for of loop in createDiscordenoMessage function.`, ); props[key] = createNewProp( MESSAGE_SNOWFLAKES.includes(key) ? value ? snowflakeToBigint(value) : undefined : value, ); } props.authorId = createNewProp(snowflakeToBigint(author.id)); props.isBot = createNewProp(author.bot || false); props.tag = createNewProp(`${author.username}#${author.discriminator}`); // Discord doesnt give guild id for getMessage() so this will fill it in const guildIdFinal = guildId || (await cacheHandlers.get("channels", snowflakeToBigint(data.channelId))) ?.guildId || ""; const message: DiscordenoMessage = Object.create(baseMessage, { ...props, content: createNewProp(data.content || ""), guildId: createNewProp(guildIdFinal), mentionedUserIds: createNewProp( mentions.map((m) => snowflakeToBigint(m.id)), ), mentionedRoleIds: createNewProp( mentionRoles.map((id) => snowflakeToBigint(id)), ), mentionedChannelIds: createNewProp([ // Keep any ids that discord sends ...mentionChannels.map((m) => snowflakeToBigint(m.id)), // Add any other ids that can be validated in a channel mention format ...(rest.content?.match(CHANNEL_MENTION_REGEX) || []).map((text) => // converts the <#123> into 123 snowflakeToBigint(text.substring(2, text.length - 1)) ), ]), timestamp: createNewProp(Date.parse(data.timestamp)), editedTimestamp: createNewProp( editedTimestamp ? Date.parse(editedTimestamp) : undefined, ), messageReference: createNewProp( messageReference ? { messageId: messageReference.messageId ? snowflakeToBigint(messageReference.messageId) : undefined, channelId: messageReference.channelId ? snowflakeToBigint(messageReference.channelId) : undefined, guildId: messageReference.guildId ? snowflakeToBigint(messageReference.guildId) : undefined, } : undefined, ), }); return message; } export interface DiscordenoMessage extends Omit< Message, | "id" | "webhookId" | "timestamp" | "editedTimestamp" | "guildId" | "channelId" | "member" | "author" > { id: bigint; author: undefined; /** Whether or not this message was sent by a bot */ isBot: boolean; /** The username#discrimnator for the user who sent this message */ tag: string; // For better user experience /** Id of the guild which the massage has been send in. "0n" if it a DM */ guildId: bigint; /** id of the channel the message was sent in */ channelId: bigint; /** If the message is generated by a webhook, this is the webhook's id */ webhookId?: bigint; /** The id of the user who sent this message */ authorId: bigint; /** The message content for this message. Empty string if no content was sent like an attachment only. */ content: string; /** Ids of users specifically mentioned in the message */ mentionedUserIds: bigint[]; /** Ids of roles specifically mentioned in this message */ mentionedRoleIds: bigint[]; /** Channels specifically mentioned in this message */ mentionedChannelIds?: bigint[]; /** When this message was sent */ timestamp: number; /** When this message was edited (or undefined if never) */ editedTimestamp?: number; // GETTERS /** The channel where this message was sent. Can be undefined if uncached. */ channel?: DiscordenoChannel; /** The guild of this message. Can be undefined if not in cache or in DM */ guild?: DiscordenoGuild; /** The member for the user who sent the message. Can be undefined if not in cache or in dm. */ member?: DiscordenoMember; /** The guild member details for this guild and member. Can be undefined if not in cache or in dm. */ guildMember?: Omit & { joinedAt: number; premiumSince?: number; roles: bigint[]; }; /** The url link to this message */ link: string; /** The role objects for all the roles that were mentioned in this message */ mentionedRoles: (DiscordenoRole | undefined)[]; /** The channel objects for all the channels that were mentioned in this message. */ mentionedChannels: (DiscordenoChannel | undefined)[]; /** The member objects for all the members that were mentioned in this message. */ mentionedMembers: (DiscordenoMember | undefined)[]; // METHODS /** Delete the message */ delete( reason?: string, delayMilliseconds?: number, ): ReturnType; /** Edit the message */ edit(content: string | EditMessage): ReturnType; /** Pins the message in the channel */ pin(): ReturnType; /** Add a reaction to the message */ addReaction(reaction: string): ReturnType; /** Add multiple reactions to the message without or without order. */ addReactions( reactions: string[], ordered?: boolean, ): ReturnType; /** Send a inline reply to this message */ reply(content: string | CreateMessage): ReturnType; /** Send a message to this channel where this message is */ send(content: string | CreateMessage): ReturnType; /** Send a message to this channel and then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */ alert( content: string | CreateMessage, timeout?: number, reason?: string, ): Promise; /** Send a inline reply to this message but then delete it after a bit. By default it will delete after 10 seconds with no reason provided. */ alertReply( content: string | CreateMessage, timeout?: number, reason?: string, ): Promise; /** Removes all reactions for all emojis on this message */ removeAllReactions(): ReturnType; /** Removes all reactions for a single emoji on this message */ removeReactionEmoji(reaction: string): ReturnType; /** Removes a reaction from the given user on this message, defaults to bot */ removeReaction( reaction: string, userId?: bigint, ): ReturnType; }