feat(bot,rest,types): Add subscriptions (#3892)

* Add subscriptions

* Apply suggestions from code review

Co-authored-by: Awesome Stickz <awesome@stickz.dev>

---------

Co-authored-by: Awesome Stickz <awesome@stickz.dev>
This commit is contained in:
Fleny
2024-11-05 13:38:33 +01:00
committed by GitHub
co-authored by Awesome Stickz
parent b1036b24cf
commit 87e6cd0c2e
19 changed files with 214 additions and 0 deletions
+4
View File
@@ -32,6 +32,7 @@ import type {
Role,
ScheduledEvent,
Sticker,
Subscription,
ThreadMember,
User,
VoiceState,
@@ -282,6 +283,9 @@ export interface EventHandlers {
entitlementCreate: (entitlement: Entitlement) => unknown
entitlementUpdate: (entitlement: Entitlement) => unknown
entitlementDelete: (entitlement: Entitlement) => unknown
subscriptionCreate: (subscription: Subscription) => unknown
subscriptionUpdate: (subscription: Subscription) => unknown
subscriptionDelete: (subscription: Subscription) => unknown
messagePollVoteAdd: (payload: { userId: bigint; channelId: bigint; messageId: bigint; guildId?: bigint; answerId: number }) => unknown
messagePollVoteRemove: (payload: { userId: bigint; channelId: bigint; messageId: bigint; guildId?: bigint; answerId: number }) => unknown
}
+3
View File
@@ -72,6 +72,9 @@ export function createBotGatewayHandlers(
ENTITLEMENT_DELETE: options.ENTITLEMENT_DELETE ?? handlers.handleEntitlementDelete,
MESSAGE_POLL_VOTE_ADD: options.MESSAGE_POLL_VOTE_ADD ?? handlers.handleMessagePollVoteAdd,
MESSAGE_POLL_VOTE_REMOVE: options.MESSAGE_POLL_VOTE_REMOVE ?? handlers.handleMessagePollVoteRemove,
SUBSCRIPTION_CREATE: options.SUBSCRIPTION_CREATE ?? handlers.handleSubscriptionCreate,
SUBSCRIPTION_UPDATE: options.SUBSCRIPTION_UPDATE ?? handlers.handleSubscriptionUpdate,
SUBSCRIPTION_DELETE: options.SUBSCRIPTION_DELETE ?? handlers.handleSubscriptionDelete,
}
}
+1
View File
@@ -12,3 +12,4 @@ export * from './poll/index.js'
export * from './roles/index.js'
export * from './voice/index.js'
export * from './webhooks/index.js'
export * from './subscriptions/index.js'
@@ -0,0 +1,9 @@
import type { DiscordGatewayPayload, DiscordSubscription } from '@discordeno/types'
import type { Bot } from '../../bot.js'
export async function handleSubscriptionCreate(bot: Bot, data: DiscordGatewayPayload): Promise<void> {
if (!bot.events.subscriptionCreate) return
const payload = data.d as DiscordSubscription
bot.events.subscriptionCreate(bot.transformers.subscription(bot, payload))
}
@@ -0,0 +1,9 @@
import type { DiscordGatewayPayload, DiscordSubscription } from '@discordeno/types'
import type { Bot } from '../../bot.js'
export async function handleSubscriptionDelete(bot: Bot, data: DiscordGatewayPayload): Promise<void> {
if (!bot.events.subscriptionDelete) return
const payload = data.d as DiscordSubscription
bot.events.subscriptionDelete(bot.transformers.subscription(bot, payload))
}
@@ -0,0 +1,9 @@
import type { DiscordGatewayPayload, DiscordSubscription } from '@discordeno/types'
import type { Bot } from '../../bot.js'
export async function handleSubscriptionUpdate(bot: Bot, data: DiscordGatewayPayload): Promise<void> {
if (!bot.events.subscriptionUpdate) return
const payload = data.d as DiscordSubscription
bot.events.subscriptionUpdate(bot.transformers.subscription(bot, payload))
}
@@ -0,0 +1,3 @@
export * from './SUBSCRIPTION_CREATE.js'
export * from './SUBSCRIPTION_DELETE.js'
export * from './SUBSCRIPTION_UPDATE.js'
+10
View File
@@ -50,6 +50,7 @@ import type {
DiscordActivityInstance,
DiscordEntitlement,
DiscordMessage,
DiscordSubscription,
EditApplication,
EditAutoModerationRuleOptions,
EditBotMemberOptions,
@@ -80,6 +81,7 @@ import type {
InteractionResponse,
ListArchivedThreads,
ListGuildMembers,
ListSkuSubscriptionsOptions,
MfaLevels,
ModifyApplicationEmoji,
ModifyChannel,
@@ -762,6 +764,12 @@ export function createBotHelpers(bot: Bot): BotHelpers {
listSkus: async (applicationId) => {
return (await bot.rest.listSkus(applicationId)).map((sku) => bot.transformers.sku(bot, snakelize(sku)))
},
getSubscription: async (skuId, subscriptionId) => {
return await bot.rest.getSubscription(skuId, subscriptionId)
},
listSubscriptions: async (skuId, options) => {
return await bot.rest.listSubscriptions(skuId, options)
},
}
}
@@ -1005,4 +1013,6 @@ export interface BotHelpers {
createTestEntitlement: (applicationId: BigString, body: CreateEntitlement) => Promise<Partial<Entitlement>>
deleteTestEntitlement: (applicationId: BigString, entitlementId: BigString) => Promise<void>
listSkus: (applicationId: BigString) => Promise<Sku[]>
listSubscriptions: (skuId: BigString, options?: ListSkuSubscriptionsOptions) => Promise<Camelize<DiscordSubscription[]>>
getSubscription: (skuId: BigString, subscriptionId: BigString) => Promise<Camelize<DiscordSubscription>>
}
+30
View File
@@ -54,6 +54,7 @@ import type {
DiscordStageInstance,
DiscordSticker,
DiscordStickerPack,
DiscordSubscription,
DiscordTeam,
DiscordTemplate,
DiscordThreadMember,
@@ -118,6 +119,7 @@ import {
type StageInstance,
type Sticker,
type StickerPack,
type Subscription,
type Team,
type Template,
type ThreadMember,
@@ -204,6 +206,7 @@ import {
transformCreateApplicationCommandToDiscordCreateApplicationCommand,
transformInteractionResponseToDiscordInteractionResponse,
} from './transformers/reverse/index.js'
import { transformSubscription } from './transformers/subscription.js'
import type {
BotInteractionResponse,
DiscordComponent,
@@ -280,6 +283,7 @@ export interface Transformers {
stageInstance: (bot: Bot, payload: DiscordStageInstance, stageInstance: StageInstance) => any
sticker: (bot: Bot, payload: DiscordSticker, sticker: Sticker) => any
stickerPack: (bot: Bot, payload: DiscordStickerPack, stickerPack: StickerPack) => any
subscription: (bot: Bot, payload: DiscordSubscription, subscription: Subscription) => any
team: (bot: Bot, payload: DiscordTeam, team: Team) => any
template: (bot: Bot, payload: DiscordTemplate, template: Template) => any
threadMember: (bot: Bot, payload: DiscordThreadMember, threadMember: ThreadMember) => any
@@ -360,6 +364,7 @@ export interface Transformers {
stageInstance: (bot: Bot, payload: DiscordStageInstance) => StageInstance
sticker: (bot: Bot, payload: DiscordSticker) => Sticker
stickerPack: (bot: Bot, payload: DiscordStickerPack) => StickerPack
subscription: (bot: Bot, payload: DiscordSubscription) => Subscription
team: (bot: Bot, payload: DiscordTeam) => Team
template: (bot: Bot, payload: DiscordTemplate) => Template
threadMember: (bot: Bot, payload: DiscordThreadMember) => ThreadMember
@@ -722,6 +727,17 @@ export interface TransformersDesiredProperties {
user: boolean
sortValue: boolean
}
subscription: {
id: boolean
userId: boolean
skuIds: boolean
entitlementIds: boolean
currentPeriodStart: boolean
currentPeriodEnd: boolean
status: boolean
canceledAt: boolean
country: boolean
}
user: {
username: boolean
globalName: boolean
@@ -905,6 +921,7 @@ export function createTransformers(options: RecursivePartial<Transformers>, opts
stageInstance: options.customizers?.stageInstance ?? defaultCustomizer,
sticker: options.customizers?.sticker ?? defaultCustomizer,
stickerPack: options.customizers?.stickerPack ?? defaultCustomizer,
subscription: options.customizers?.subscription ?? defaultCustomizer,
team: options.customizers?.team ?? defaultCustomizer,
template: options.customizers?.template ?? defaultCustomizer,
threadMember: options.customizers?.threadMember ?? defaultCustomizer,
@@ -986,6 +1003,7 @@ export function createTransformers(options: RecursivePartial<Transformers>, opts
stageInstance: options.stageInstance ?? transformStageInstance,
sticker: options.sticker ?? transformSticker,
stickerPack: options.stickerPack ?? transformStickerPack,
subscription: options.subscription ?? transformSubscription,
team: options.team ?? transformTeam,
template: options.template ?? transformTemplate,
threadMember: options.threadMember ?? transformThreadMember,
@@ -1379,6 +1397,18 @@ export function createDesiredPropertiesObject(
sortValue: defaultValue,
...desiredProperties.sticker,
},
subscription: {
id: defaultValue,
userId: defaultValue,
skuIds: defaultValue,
entitlementIds: defaultValue,
currentPeriodStart: defaultValue,
currentPeriodEnd: defaultValue,
status: defaultValue,
canceledAt: defaultValue,
country: defaultValue,
...desiredProperties.subscription,
},
user: {
username: defaultValue,
globalName: defaultValue,
@@ -0,0 +1,19 @@
import type { DiscordSubscription } from '@discordeno/types'
import type { Bot, Subscription } from '../index.js'
export function transformSubscription(bot: Bot, payload: DiscordSubscription): Subscription {
const props = bot.transformers.desiredProperties.subscription
const subscription = {} as Subscription
if (props.id && payload.id) subscription.id = bot.transformers.snowflake(payload.id)
if (props.userId && payload.user_id) subscription.userId = bot.transformers.snowflake(payload.user_id)
if (props.skuIds && payload.sku_ids) subscription.skuIds = payload.sku_ids.map((skuId) => bot.transformers.snowflake(skuId))
if (props.entitlementIds && payload.entitlement_ids) subscription.entitlementIds = payload.entitlement_ids.map((entitlementId) => bot.transformers.snowflake(entitlementId))
if (props.currentPeriodStart && payload.current_period_start) subscription.currentPeriodStart = Date.parse(payload.current_period_start)
if (props.currentPeriodEnd && payload.current_period_end) subscription.currentPeriodEnd = Date.parse(payload.current_period_end)
if (props.status && payload.status) subscription.status = payload.status
if (props.canceledAt && payload.canceled_at) subscription.canceledAt = Date.parse(payload.canceled_at)
if (props.country && payload.country) subscription.country = payload.country
return bot.transformers.customizers.subscription(bot, payload, subscription)
}
+22
View File
@@ -31,6 +31,7 @@ import type {
DiscordScheduledEventRecurrenceRuleNWeekday,
DiscordScheduledEventRecurrenceRuleWeekday,
DiscordSkuType,
DiscordSubscriptionStatus,
DiscordTeamMemberRole,
DiscordTemplateSerializedSourceGuild,
DiscordWebhookEventType,
@@ -1741,3 +1742,24 @@ export interface GuildWidgetSettings {
channelId?: string
enabled: boolean
}
export interface Subscription {
/** ID of the subscription */
id: bigint
/** ID of the user who is subscribed */
userId: bigint
/** List of SKUs subscribed to */
skuIds: bigint[]
/** List of entitlements granted for this subscription */
entitlementIds: bigint[]
/** Start of the current subscription period */
currentPeriodStart: number
/** End of the current subscription period */
currentPeriodEnd: number
/** Current status of the subscription */
status: DiscordSubscriptionStatus
/** When the subscription was canceled */
canceledAt: number
/** ISO3166-1 alpha-2 country code of the payment source used to purchase the subscription. Missing unless queried with a private OAuth scope. */
country?: string
}
+3
View File
@@ -207,6 +207,9 @@ export interface BotGatewayHandlerOptions {
ENTITLEMENT_CREATE: typeof handlers.handleEntitlementCreate
ENTITLEMENT_UPDATE: typeof handlers.handleEntitlementUpdate
ENTITLEMENT_DELETE: typeof handlers.handleEntitlementDelete
SUBSCRIPTION_CREATE: typeof handlers.handleSubscriptionCreate
SUBSCRIPTION_UPDATE: typeof handlers.handleSubscriptionUpdate
SUBSCRIPTION_DELETE: typeof handlers.handleSubscriptionDelete
MESSAGE_POLL_VOTE_ADD: typeof handlers.handleMessagePollVoteAdd
MESSAGE_POLL_VOTE_REMOVE: typeof handlers.handleMessagePollVoteRemove
}
+9
View File
@@ -41,6 +41,7 @@ import {
type DiscordStageInstance,
type DiscordSticker,
type DiscordStickerPack,
type DiscordSubscription,
type DiscordTemplate,
type DiscordThreadMember,
type DiscordUser,
@@ -1629,6 +1630,14 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
return await rest.get<DiscordSku[]>(rest.routes.monetization.skus(applicationId))
},
async listSubscriptions(skuId, options) {
return await rest.get<DiscordSubscription[]>(rest.routes.monetization.subscriptions(skuId, options))
},
async getSubscription(skuId, subscriptionId) {
return await rest.get<DiscordSubscription>(rest.routes.monetization.subscription(skuId, subscriptionId))
},
preferSnakeCase(enabled: boolean) {
const camelizer = enabled ? (x: any) => x : camelize
+17
View File
@@ -593,6 +593,23 @@ export function createRoutes(): RestRoutes {
skus: (applicationId) => {
return `/applications/${applicationId}/skus`
},
subscription: (skuId, subscriptionId) => {
return `/skus/${skuId}/subscriptions/${subscriptionId}`
},
subscriptions: (skuId, options) => {
let url = `/skus/${skuId}/subscriptions?`
if (options) {
if (options.after) url += `after=${options.after}`
if (options.before) url += `&before=${options.before}`
if (options.userId) url += `&user_id=${options.userId}`
if (options.limit) url += `&limit=${options.limit}`
}
return url
},
},
applicationEmoji(applicationId, emojiId) {
+14
View File
@@ -86,6 +86,7 @@ import type {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
DiscordInteraction,
DiscordInteractionCallbackResponse,
DiscordSubscription,
EditApplication,
EditAutoModerationRuleOptions,
EditBotMemberOptions,
@@ -119,6 +120,7 @@ import type {
InteractionResponse,
ListArchivedThreads,
ListGuildMembers,
ListSkuSubscriptionsOptions,
MfaLevels,
ModifyApplicationEmoji,
ModifyChannel,
@@ -3028,6 +3030,18 @@ export interface RestManager {
* @param applicationId - The id of the application to get the SKUs
*/
listSkus: (applicationId: BigString) => Promise<CamelizedDiscordSku[]>
/**
* Returns all subscriptions containing the SKU, filtered by user.
*
* @param skuId - The id of the sku of get the subscriptions for
*/
listSubscriptions: (skuId: BigString, options?: ListSkuSubscriptionsOptions) => Promise<Camelize<DiscordSubscription[]>>
/**
* Get a subscription by its ID.
*
* @param skuId - The id of the sku of get the subscriptions for
*/
getSubscription: (skuId: BigString, subscriptionId: BigString) => Promise<Camelize<DiscordSubscription>>
}
export type RequestMethods = 'GET' | 'POST' | 'DELETE' | 'PATCH' | 'PUT'
+5
View File
@@ -13,6 +13,7 @@ import type {
InteractionCallbackOptions,
ListArchivedThreads,
ListGuildMembers,
ListSkuSubscriptionsOptions,
} from '@discordeno/types'
export interface RestRoutes {
@@ -274,6 +275,10 @@ export interface RestRoutes {
consumeEntitlement: (applicationId: BigString, entitlementId: BigString) => string
/** Route to list the SKUs */
skus: (applicationId: BigString) => string
/** Route to list the SKU subscriptions */
subscriptions: (skuId: BigString, options?: ListSkuSubscriptionsOptions) => string
/** Route to get a SKU subscription */
subscription: (skuId: BigString, subscriptionId: BigString) => string
}
/** Route to list / create an application emoji */
applicationEmojis: (applicationId: BigString) => string
+32
View File
@@ -3778,6 +3778,38 @@ export enum DiscordSkuType {
SubscriptionGroup = 6,
}
/** https://discord.com/developers/docs/resources/subscription#subscription-object */
export interface DiscordSubscription {
/** ID of the subscription */
id: string
/** ID of the user who is subscribed */
user_id: string
/** List of SKUs subscribed to */
sku_ids: string[]
/** List of entitlements granted for this subscription */
entitlement_ids: string[]
/** Start of the current subscription period */
current_period_start: string
/** End of the current subscription period */
current_period_end: string
/** Current status of the subscription */
status: DiscordSubscriptionStatus
/** When the subscription was canceled */
canceled_at: string | null
/** ISO3166-1 alpha-2 country code of the payment source used to purchase the subscription. Missing unless queried with a private OAuth scope. */
country?: string
}
/** https://discord.com/developers/docs/resources/subscription#subscription-statuses */
export enum DiscordSubscriptionStatus {
/** Subscription is active and scheduled to renew. */
Active,
/** Subscription is active but will not renew. */
Ending,
/** Subscription is inactive and not being charged. */
Inactive,
}
/** https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-context-types */
export enum DiscordInteractionContextType {
/** Interaction can be used within servers */
+12
View File
@@ -1586,3 +1586,15 @@ export interface GetPollAnswerVotes {
*/
limit?: number
}
/** https://discord.com/developers/docs/resources/subscription#query-string-params */
export interface ListSkuSubscriptionsOptions {
/** List subscriptions before this ID */
before?: BigString
/** List subscriptions after this ID */
after?: BigString
/** Number of results to return (1-100) */
limit?: number
/** User ID for which to return subscriptions. Required except for OAuth queries. */
userId?: BigString
}
+3
View File
@@ -941,6 +941,9 @@ export type GatewayDispatchEventNames =
| 'ENTITLEMENT_CREATE'
| 'ENTITLEMENT_UPDATE'
| 'ENTITLEMENT_DELETE'
| 'SUBSCRIPTION_CREATE'
| 'SUBSCRIPTION_UPDATE'
| 'SUBSCRIPTION_DELETE'
| 'MESSAGE_POLL_VOTE_ADD'
| 'MESSAGE_POLL_VOTE_REMOVE'