diff --git a/structures/Message.ts b/structures/Message.ts index 98c9819..a84722f 100644 --- a/structures/Message.ts +++ b/structures/Message.ts @@ -1,7 +1,8 @@ import type { Model } from "./Base.ts"; import type { Snowflake } from "../util/Snowflake.ts"; import type { Session } from "../session/Session.ts"; -import type { AllowedMentionsTypes, DiscordMessage, FileContent } from "../vendor/external.ts"; +import type { AllowedMentionsTypes, DiscordMessage, DiscordUser, FileContent } from "../vendor/external.ts"; +import type { GetReactions } from "../util/Routes.ts"; import { MessageFlags } from "../util/shared/flags.ts"; import User from "./User.ts"; import Member from "./Member.ts"; @@ -42,6 +43,11 @@ export interface EditMessage extends Partial { flags?: MessageFlags; } +export type ReactionResolvable = string | { + name: string; + id: Snowflake; +}; + /** * Represents a message * @link https://discord.com/developers/docs/resources/channel#message-object @@ -176,6 +182,74 @@ export class Message implements Model { return new Message(this.session, message); } + /** + * alias for Message.addReaction + * */ + get react() { + return this.addReaction; + } + + async addReaction(reaction: ReactionResolvable) { + const r = typeof reaction === "string" ? reaction : `${reaction.name}:${reaction.id}`; + + await this.session.rest.runMethod( + this.session.rest, + "PUT", + Routes.CHANNEL_MESSAGE_REACTION_ME(this.channelId, this.id, r), + {}, + ); + } + + async removeReaction(reaction: ReactionResolvable, options?: { userId: Snowflake }) { + const r = typeof reaction === "string" ? reaction : `${reaction.name}:${reaction.id}`; + + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + options?.userId + ? Routes.CHANNEL_MESSAGE_REACTION_USER( + this.channelId, + this.id, + r, + options.userId, + ) + : Routes.CHANNEL_MESSAGE_REACTION_ME(this.channelId, this.id, r) + ); + } + + /** + * Get users who reacted with this emoji + * */ + async fetchReactions(reaction: ReactionResolvable, options?: GetReactions): Promise { + const r = typeof reaction === "string" ? reaction : `${reaction.name}:${reaction.id}`; + + const users = await this.session.rest.runMethod( + this.session.rest, + "GET", + Routes.CHANNEL_MESSAGE_REACTION(this.channelId, this.id, encodeURIComponent(r), options), + ); + + return users.map((user) => new User(this.session, user)); + } + + async removeReactionEmoji(reaction: ReactionResolvable) { + const r = typeof reaction === "string" ? reaction : `${reaction.name}:${reaction.id}`; + + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.CHANNEL_MESSAGE_REACTION(this.channelId, this.id, r) + ); + } + + async nukeReactions() { + await this.session.rest.runMethod( + this.session.rest, + "DELETE", + Routes.CHANNEL_MESSAGE_REACTIONS(this.channelId, this.id) + ); + } + inGuild(): this is { guildId: Snowflake } & Message { return !!this.guildId; } diff --git a/structures/TextChannel.ts b/structures/TextChannel.ts index aa090a2..a332079 100644 --- a/structures/TextChannel.ts +++ b/structures/TextChannel.ts @@ -1,7 +1,8 @@ import type { Session } from "../session/Session.ts"; import type { Snowflake } from "../util/Snowflake.ts"; -import type { GetMessagesOptions } from "../util/Routes.ts"; +import type { GetMessagesOptions, GetReactions } from "../util/Routes.ts"; import type { DiscordChannel, DiscordInvite, DiscordMessage, TargetTypes } from "../vendor/external.ts"; +import type { ReactionResolvable } from "./Message.ts"; import GuildChannel from "./GuildChannel.ts"; import Guild from "./Guild.ts"; import ThreadChannel from "./ThreadChannel.ts"; @@ -115,6 +116,28 @@ export class TextChannel extends GuildChannel { async unpinMessage(messageId: Snowflake) { await Message.prototype.unpin.call({ id: messageId, channelId: this.id, session: this.session }); } + + async addReaction(messageId: Snowflake, reaction: ReactionResolvable) { + await Message.prototype.addReaction.call({ channelId: this.id, id: messageId }, reaction); + } + + async removeReaction(messageId: Snowflake, reaction: ReactionResolvable, options?: { userId: Snowflake }) { + await Message.prototype.removeReaction.call({ channelId: this.id, id: messageId }, reaction, options); + } + + async removeReactionEmoji(messageId: Snowflake, reaction: ReactionResolvable) { + await Message.prototype.removeReactionEmoji.call({ channelId: this.id, id: messageId }, reaction); + } + + async nukeReactions(messageId: Snowflake) { + await Message.prototype.nukeReactions.call({ channelId: this.id, id: messageId }); + } + + async fetchReactions(messageId: Snowflake, reaction: ReactionResolvable, options?: GetReactions) { + const users = await Message.prototype.fetchReactions.call({ channelId: this.id, id: messageId }, reaction, options); + + return users; + } } export default TextChannel; diff --git a/util/Routes.ts b/util/Routes.ts index 7004e3f..9204472 100644 --- a/util/Routes.ts +++ b/util/Routes.ts @@ -177,3 +177,33 @@ export function CHANNEL_PIN(channelId: Snowflake, messageId: Snowflake) { export function CHANNEL_PINS(channelId: Snowflake) { return `/channels/${channelId}/pins`; } + + +export function CHANNEL_MESSAGE_REACTION_ME(channelId: Snowflake, messageId: Snowflake, emoji: string) { + return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me`; +} + +export function CHANNEL_MESSAGE_REACTION_USER(channelId: Snowflake, messageId: Snowflake, emoji: string, userId: Snowflake) { + return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/${userId}`; +} + +export function CHANNEL_MESSAGE_REACTIONS(channelId: Snowflake, messageId: Snowflake) { + return `/channels/${channelId}/messages/${messageId}/reactions`; +} + +/** + * @link https://discord.com/developers/docs/resources/channel#get-reactions-query-string-params + * */ +export interface GetReactions { + after?: string; + limit?: number; +} + +export function CHANNEL_MESSAGE_REACTION(channelId: Snowflake, messageId: Snowflake, emoji: string, options?: GetReactions) { + let url = `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}?`; + + if (options?.after) url += `after=${options.after}`; + if (options?.limit) url += `&limit=${options.limit}`; + + return url; +}