diff --git a/packages/bot/src/desiredProperties.ts b/packages/bot/src/desiredProperties.ts index 1b6eff51c..e96eebb05 100644 --- a/packages/bot/src/desiredProperties.ts +++ b/packages/bot/src/desiredProperties.ts @@ -32,6 +32,7 @@ import type { MessageCall, MessageInteraction, MessageInteractionMetadata, + MessagePin, MessageReference, MessageSnapshot, Nameplate, @@ -92,6 +93,7 @@ export interface TransformersObjects { messageCall: MessageCall messageInteraction: MessageInteraction messageInteractionMetadata: MessageInteractionMetadata + messagePin: MessagePin messageReference: MessageReference messageSnapshot: MessageSnapshot nameplate: Nameplate @@ -544,6 +546,11 @@ export function createDesiredPropertiesObject +import type { RestManager } from '@discordeno/rest' import type { AddDmRecipientOptions, AddGuildMemberOptions, @@ -68,6 +70,7 @@ import type { ExecuteWebhook, GetApplicationCommandPermissionOptions, GetBans, + GetChannelPinsOptions, GetEntitlements, GetGroupDmOptions, GetGuildAuditLog, @@ -129,6 +132,7 @@ import type { LobbyMember, Member, Message, + MessagePin, Role, ScheduledEvent, Sku, @@ -442,6 +446,14 @@ export function createBotHelpers { return bot.transformers.message(bot, { message: snakelize(await bot.rest.getOriginalInteractionResponse(token)), shardId: 0 }) }, + getChannelPins: async (channelId, options) => { + const res = snakelize(await bot.rest.getChannelPins(channelId, options)) + + return { + hasMore: res.has_more, + items: bot.transformers.messagePin(bot, res.items), + } + }, getPinnedMessages: async (channelId) => { return (await bot.rest.getPinnedMessages(channelId)).map((res) => bot.transformers.message(bot, { message: snakelize(res), shardId: 0 })) }, @@ -1007,6 +1019,11 @@ export type BotHelpers Promise getStickerPacks: () => Promise getOriginalInteractionResponse: (token: string) => Promise> + getChannelPins: ( + channelId: BigString, + options?: GetChannelPinsOptions, + ) => Promise<{ items: SetupDesiredProps; hasMore: boolean }> + /** @deprecated Use {@link BotHelpers.getChannelPins} instead */ getPinnedMessages: (channelId: BigString) => Promise[]> getPrivateArchivedThreads: (channelId: BigString, options?: ListArchivedThreads) => Promise> getPrivateJoinedArchivedThreads: (channelId: BigString, options?: ListArchivedThreads) => Promise> diff --git a/packages/bot/src/transformers.ts b/packages/bot/src/transformers.ts index 60c70b81b..b174a4087 100644 --- a/packages/bot/src/transformers.ts +++ b/packages/bot/src/transformers.ts @@ -50,6 +50,7 @@ import type { DiscordMessageCall, DiscordMessageComponent, DiscordMessageInteractionMetadata, + DiscordMessagePin, DiscordMessageSnapshot, DiscordNameplate, DiscordPoll, @@ -132,6 +133,7 @@ import { type Message, type MessageCall, type MessageInteractionMetadata, + type MessagePin, type MessageSnapshot, type Nameplate, type Poll, @@ -369,6 +371,7 @@ export type Transformers, ) => any + messagePin: (bot: Bot, payload: DiscordMessagePin, call: SetupDesiredProps) => any messageSnapshot: ( bot: Bot, payload: DiscordMessageSnapshot, @@ -526,6 +529,7 @@ export type Transformers, payload: DiscordMessageInteractionMetadata, ) => SetupDesiredProps + messagePin: (bot: Bot, payload: DiscordMessagePin) => SetupDesiredProps messageSnapshot: ( bot: Bot, payload: { messageSnapshot: DiscordMessageSnapshot; shardId: number }, diff --git a/packages/bot/src/transformers/message.ts b/packages/bot/src/transformers/message.ts index a77a69e86..3dd6e7d2e 100644 --- a/packages/bot/src/transformers/message.ts +++ b/packages/bot/src/transformers/message.ts @@ -4,6 +4,7 @@ import { type DiscordMessage, type DiscordMessageCall, type DiscordMessageInteractionMetadata, + type DiscordMessagePin, type DiscordMessageSnapshot, MessageFlags, } from '@discordeno/types' @@ -13,6 +14,7 @@ import { type Message, type MessageCall, type MessageInteractionMetadata, + type MessagePin, type MessageSnapshot, snowflakeToTimestamp, } from '../index.js' @@ -261,6 +263,16 @@ export function transformMessage( return bot.transformers.customizers.message(bot, payload.message, message) } +export function transformMessagePin(bot: InternalBot, payload: DiscordMessagePin): MessagePin { + const props = bot.transformers.desiredProperties.messagePin + const messagePin = {} as MessagePin + + if (props.pinnedAt && payload.pinned_at) messagePin.pinnedAt = Date.parse(payload.pinned_at) + if (props.message && payload.message) messagePin.message = bot.transformers.message(bot, { message: payload.message, shardId: 0 }) + + return bot.transformers.customizers.messagePin(bot, payload, messagePin) +} + export function transformMessageSnapshot( bot: InternalBot, payload: { messageSnapshot: DiscordMessageSnapshot; shardId: number }, diff --git a/packages/bot/src/transformers/types.ts b/packages/bot/src/transformers/types.ts index 35e1017ef..6f986407d 100644 --- a/packages/bot/src/transformers/types.ts +++ b/packages/bot/src/transformers/types.ts @@ -1329,6 +1329,13 @@ export interface MessageCall { endedTimestamp: number } +export interface MessagePin { + /** the time the message was pinned */ + pinnedAt: number + /** the pinned message */ + message: Message +} + export interface Reaction { /** Whether the current user reacted using this emoji */ me: boolean diff --git a/packages/rest/src/manager.ts b/packages/rest/src/manager.ts index 0a2679f6d..39c211ae4 100644 --- a/packages/rest/src/manager.ts +++ b/packages/rest/src/manager.ts @@ -1330,6 +1330,10 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage return await rest.get(rest.routes.interactions.responses.original(rest.applicationId, token), { unauthorized: true }) }, + async getChannelPins(channelId, options) { + return await rest.get(rest.routes.channels.messagePins(channelId, options)) + }, + async getPinnedMessages(channelId) { return await rest.get(rest.routes.channels.pins(channelId)) }, @@ -1590,7 +1594,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage }, async pinMessage(channelId, messageId, reason) { - await rest.put(rest.routes.channels.pin(channelId, messageId), { reason }) + await rest.put(rest.routes.channels.messagePin(channelId, messageId), { reason }) }, async pruneMembers(guildId, body, reason) { @@ -1621,7 +1625,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage }, async unpinMessage(channelId, messageId, reason) { - await rest.delete(rest.routes.channels.pin(channelId, messageId), { reason }) + await rest.delete(rest.routes.channels.messagePin(channelId, messageId), { reason }) }, async triggerTypingIndicator(channelId) { diff --git a/packages/rest/src/routes.ts b/packages/rest/src/routes.ts index 03b5cafc5..29b93f1a2 100644 --- a/packages/rest/src/routes.ts +++ b/packages/rest/src/routes.ts @@ -48,6 +48,19 @@ export function createRoutes(): RestRoutes { pins: (channelId) => { return `/channels/${channelId}/pins` }, + messagePins: (channelId, options) => { + let url = `/channels/${channelId}/messages/pins?` + + if (options) { + if (options.before) url += `before=${options.before}` + if (options.limit) url += `&limit=${options.limit}` + } + + return url + }, + messagePin: (channelId, messageId) => { + return `/channels/${channelId}/messages/pins/${messageId}` + }, reactions: { bot: (channelId, messageId, emoji) => { return `/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(emoji)}/@me` diff --git a/packages/rest/src/types.ts b/packages/rest/src/types.ts index de927f986..8aea44c71 100644 --- a/packages/rest/src/types.ts +++ b/packages/rest/src/types.ts @@ -48,6 +48,7 @@ import type { DiscordEntitlement, DiscordFollowedChannel, DiscordGetAnswerVotesResponse, + DiscordGetChannelPins, DiscordGetGatewayBot, DiscordGuild, DiscordGuildApplicationCommandPermissions, @@ -103,6 +104,7 @@ import type { FileContent, GetApplicationCommandPermissionOptions, GetBans, + GetChannelPinsOptions, GetEntitlements, GetGroupDmOptions, GetGuildAuditLog, @@ -2079,6 +2081,21 @@ export interface RestManager { * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#get-original-interaction-response} */ getOriginalInteractionResponse: (token: string) => Promise> + /** + * Retrieves the list of pins in a channel. + * + * @param channelId - The ID of the channel to get the pins for. + * @param options - The options for the fetching of the pins. + * @returns A {@link DiscordGetChannelPins} objects + * + * @remarks + * Requires the `VIEW_CHANNEL` permission. + * + * If the user is missing the `READ_MESSAGE_HISTORY` permission in the channel, then no pins will be returned. + * + * @see {@link https://discord.com/developers/docs/resources/message#get-channel-pins} + */ + getChannelPins: (channelId: BigString, options?: GetChannelPinsOptions) => Promise> /** * Gets the pinned messages for a channel. * @@ -2091,7 +2108,8 @@ export interface RestManager { * If getting a message from a guild channel: * - Requires the `READ_MESSAGE_HISTORY` permission. * - * @see {@link https://discord.com/developers/docs/resources/channel#get-pinned-messages} + * @see {@link https://discord.com/developers/docs/resources/message#get-pinned-messages-deprecated} + * @deprecated Use {@link getChannelPins} instead. */ getPinnedMessages: (channelId: BigString) => Promise[]> /** @@ -2896,15 +2914,11 @@ export interface RestManager { * @param {string} [reason] - An optional reason for the action, to be included in the audit log. * * @remarks - * Requires that the bot user be able to see the contents of the channel in which the messages were posted. - * * Requires the `MANAGE_MESSAGES` permission. * - * ⚠️ There can only be at max 50 messages pinned in a channel. - * * Fires a _Channel Pins Update_ event. * - * @see {@link https://discord.com/developers/docs/resources/channel#pin-message} + * @see {@link https://discord.com/developers/docs/resources/message#pin-message} */ pinMessage: (channelId: BigString, messageId: BigString, reason?: string) => Promise /** @@ -2957,20 +2971,18 @@ export interface RestManager { */ unbanMember: (guildId: BigString, userId: BigString, reason?: string) => Promise /** - * Unpins a pinned message in a channel. + * Unpin a message in a channel. * * @param channelId - The ID of the channel where the message is pinned. * @param messageId - The ID of the message to unpin. * @param {string} [reason] - An optional reason for the action, to be included in the audit log. * * @remarks - * Requires that the bot user be able to see the contents of the channel in which the messages were posted. - * * Requires the `MANAGE_MESSAGES` permission. * * Fires a _Channel Pins Update_ event. * - * @see {@link https://discord.com/developers/docs/resources/channel#unpin-message} + * @see {@link https://discord.com/developers/docs/resources/message#unpin-message} */ unpinMessage: (channelId: BigString, messageId: BigString, reason?: string) => Promise /** diff --git a/packages/rest/src/typings/routes.ts b/packages/rest/src/typings/routes.ts index 066ea434e..91f279a18 100644 --- a/packages/rest/src/typings/routes.ts +++ b/packages/rest/src/typings/routes.ts @@ -1,6 +1,7 @@ import type { BigString, GetBans, + GetChannelPinsOptions, GetEntitlements, GetGuildAuditLog, GetGuildPruneCountQuery, @@ -45,8 +46,12 @@ export interface RestRoutes { dmRecipient: (channelId: BigString, userId: BigString) => string /** Route for handling a specific pin. */ pin: (channelId: BigString, messageId: BigString) => string - /** Route for handling a channels pins. */ + /** Route for handling a channel's pins. */ pins: (channelId: BigString) => string + /** Route for handling a channel's pins. */ + messagePins: (channelId: BigString, options?: GetChannelPinsOptions) => string + /** Route for handling a specific pin. */ + messagePin: (channelId: BigString, messageId: BigString) => string /** Route for non-specific webhook in a channel. */ webhooks: (channelId: BigString) => string /** Route for a specific channel. */ diff --git a/packages/types/src/discord/message.ts b/packages/types/src/discord/message.ts index 1520569ec..394d6a2fe 100644 --- a/packages/types/src/discord/message.ts +++ b/packages/types/src/discord/message.ts @@ -532,6 +532,14 @@ export interface DiscordAllowedMentions { // TODO: Implement RoleSubscriptionData https://discord.com/developers/docs/resources/message#role-subscription-data-object-role-subscription-data-object-structure +/** https://discord.com/developers/docs/resources/message#message-pin-object-message-pin-object-struture */ +export interface DiscordMessagePin { + /** the time the message was pinned */ + pinned_at: string + /** the pinned message */ + message: DiscordMessage +} + /** https://discord.com/developers/docs/resources/message#create-message-jsonform-params */ export interface DiscordCreateMessage { /** The message contents (up to 2000 characters) */ @@ -569,3 +577,9 @@ export enum DiscordReactionType { Normal, Burst, } + +/** https://discord.com/developers/docs/resources/message#get-channel-pins-response-structure */ +export interface DiscordGetChannelPins { + items: DiscordMessagePin + has_more: boolean +} diff --git a/packages/types/src/discordeno.ts b/packages/types/src/discordeno.ts index 365726f51..e80840010 100644 --- a/packages/types/src/discordeno.ts +++ b/packages/types/src/discordeno.ts @@ -1392,6 +1392,14 @@ export interface EditMessage { components?: MessageComponents } +/** https://discord.com/developers/docs/resources/message#get-channel-pins-query-string-params */ +export interface GetChannelPinsOptions { + /** Get messages pinned before this timestamp */ + before?: string + /** Max number of pins to return (1-50), defaults to 50 */ + limit?: number +} + /** Additional properties for https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command-permissions and https://discord.com/developers/docs/interactions/application-commands#get-guild-application-command-permissions */ export interface GetApplicationCommandPermissionOptions { /** Access token of the user. Requires the `applications.commands.permissions.update` scope */