mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: premium application subscriptions (#9907)
* feat: premium application subscriptions * types: readonly array Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * fix: requested changes Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * fix: core client types --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { ChannelsAPI } from './channel.js';
|
||||
import { GuildsAPI } from './guild.js';
|
||||
import { InteractionsAPI } from './interactions.js';
|
||||
import { InvitesAPI } from './invite.js';
|
||||
import { MonetizationAPI } from './monetization.js';
|
||||
import { OAuth2API } from './oauth2.js';
|
||||
import { RoleConnectionsAPI } from './roleConnections.js';
|
||||
import { StageInstancesAPI } from './stageInstances.js';
|
||||
@@ -20,6 +21,7 @@ export * from './channel.js';
|
||||
export * from './guild.js';
|
||||
export * from './interactions.js';
|
||||
export * from './invite.js';
|
||||
export * from './monetization.js';
|
||||
export * from './oauth2.js';
|
||||
export * from './roleConnections.js';
|
||||
export * from './stageInstances.js';
|
||||
@@ -42,6 +44,8 @@ export class API {
|
||||
|
||||
public readonly invites: InvitesAPI;
|
||||
|
||||
public readonly monetization: MonetizationAPI;
|
||||
|
||||
public readonly oauth2: OAuth2API;
|
||||
|
||||
public readonly roleConnections: RoleConnectionsAPI;
|
||||
@@ -64,6 +68,7 @@ export class API {
|
||||
this.channels = new ChannelsAPI(rest);
|
||||
this.guilds = new GuildsAPI(rest);
|
||||
this.invites = new InvitesAPI(rest);
|
||||
this.monetization = new MonetizationAPI(rest);
|
||||
this.roleConnections = new RoleConnectionsAPI(rest);
|
||||
this.oauth2 = new OAuth2API(rest);
|
||||
this.stageInstances = new StageInstancesAPI(rest);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
/* eslint-disable jsdoc/check-param-names */
|
||||
|
||||
import type { RawFile, RequestData, REST } from '@discordjs/rest';
|
||||
import { InteractionResponseType, Routes } from 'discord-api-types/v10';
|
||||
import type {
|
||||
APICommandAutocompleteInteractionResponseCallbackData,
|
||||
APIInteractionResponseCallbackData,
|
||||
APIModalInteractionResponseCallbackData,
|
||||
RESTGetAPIWebhookWithTokenMessageResult,
|
||||
Snowflake,
|
||||
APIInteractionResponseDeferredChannelMessageWithSource,
|
||||
import {
|
||||
InteractionResponseType,
|
||||
Routes,
|
||||
type APICommandAutocompleteInteractionResponseCallbackData,
|
||||
type APIInteractionResponseCallbackData,
|
||||
type APIInteractionResponseDeferredChannelMessageWithSource,
|
||||
type APIModalInteractionResponseCallbackData,
|
||||
type APIPremiumRequiredInteractionResponse,
|
||||
type RESTGetAPIWebhookWithTokenMessageResult,
|
||||
type Snowflake,
|
||||
} from 'discord-api-types/v10';
|
||||
import type { WebhooksAPI } from './webhook.js';
|
||||
|
||||
@@ -248,4 +250,26 @@ export class InteractionsAPI {
|
||||
signal,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a premium required response to an interaction
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#create-interaction-response}
|
||||
* @param interactionId - The id of the interaction
|
||||
* @param interactionToken - The token of the interaction
|
||||
* @param options - The options for sending the premium required response
|
||||
*/
|
||||
public async sendPremiumRequired(
|
||||
interactionId: Snowflake,
|
||||
interactionToken: string,
|
||||
{ signal }: Pick<RequestData, 'signal'> = {},
|
||||
) {
|
||||
await this.rest.post(Routes.interactionCallback(interactionId, interactionToken), {
|
||||
auth: false,
|
||||
body: {
|
||||
type: InteractionResponseType.PremiumRequired,
|
||||
} satisfies APIPremiumRequiredInteractionResponse,
|
||||
signal,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
80
packages/core/src/api/monetization.ts
Normal file
80
packages/core/src/api/monetization.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/* eslint-disable jsdoc/check-param-names */
|
||||
|
||||
import { makeURLSearchParams, type RequestData, type REST } from '@discordjs/rest';
|
||||
import {
|
||||
Routes,
|
||||
type RESTGetAPIEntitlementsQuery,
|
||||
type RESTGetAPIEntitlementsResult,
|
||||
type RESTGetAPISKUsResult,
|
||||
type RESTPostAPIEntitlementBody,
|
||||
type RESTPostAPIEntitlementResult,
|
||||
type Snowflake,
|
||||
} from 'discord-api-types/v10';
|
||||
|
||||
export class MonetizationAPI {
|
||||
public constructor(private readonly rest: REST) {}
|
||||
|
||||
/**
|
||||
* Fetches the SKUs for an application.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/monetization/skus#list-skus}
|
||||
* @param options - The options for fetching the SKUs.
|
||||
*/
|
||||
public async getSKUs(applicationId: Snowflake, { signal }: Pick<RequestData, 'signal'> = {}) {
|
||||
return this.rest.get(Routes.skus(applicationId), { signal }) as Promise<RESTGetAPISKUsResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the entitlements for an application.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/monetization/entitlements#list-entitlements}
|
||||
* @param applicationId - The application id to fetch entitlements for
|
||||
* @param query - The query options for fetching entitlements
|
||||
* @param options - The options for fetching entitlements
|
||||
*/
|
||||
public async getEntitlements(
|
||||
applicationId: Snowflake,
|
||||
query: RESTGetAPIEntitlementsQuery,
|
||||
{ signal }: Pick<RequestData, 'signal'> = {},
|
||||
) {
|
||||
return this.rest.get(Routes.entitlements(applicationId), {
|
||||
signal,
|
||||
query: makeURLSearchParams(query),
|
||||
}) as Promise<RESTGetAPIEntitlementsResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test entitlement for an application's SKU.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/monetization/entitlements#create-test-entitlement}
|
||||
* @param applicationId - The application id to create the entitlement for
|
||||
* @param body - The data for creating the entitlement
|
||||
* @param options - The options for creating the entitlement
|
||||
*/
|
||||
public async createTestEntitlement(
|
||||
applicationId: Snowflake,
|
||||
body: RESTPostAPIEntitlementBody,
|
||||
{ signal }: Pick<RequestData, 'signal'> = {},
|
||||
) {
|
||||
return this.rest.post(Routes.entitlements(applicationId), {
|
||||
body,
|
||||
signal,
|
||||
}) as Promise<RESTPostAPIEntitlementResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a test entitlement for an application's SKU.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/monetization/entitlements#delete-test-entitlement}
|
||||
* @param applicationId - The application id to delete the entitlement for
|
||||
* @param entitlementId - The entitlement id to delete
|
||||
* @param options - The options for deleting the entitlement
|
||||
*/
|
||||
public async deleteTestEntitlement(
|
||||
applicationId: Snowflake,
|
||||
entitlementId: Snowflake,
|
||||
{ signal }: Pick<RequestData, 'signal'> = {},
|
||||
) {
|
||||
await this.rest.delete(Routes.entitlement(applicationId, entitlementId), { signal });
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ import {
|
||||
type GatewayChannelDeleteDispatchData,
|
||||
type GatewayChannelPinsUpdateDispatchData,
|
||||
type GatewayChannelUpdateDispatchData,
|
||||
type GatewayEntitlementCreateDispatchData,
|
||||
type GatewayEntitlementDeleteDispatchData,
|
||||
type GatewayEntitlementUpdateDispatchData,
|
||||
type GatewayGuildAuditLogEntryCreateDispatchData,
|
||||
type GatewayGuildBanAddDispatchData,
|
||||
type GatewayGuildBanRemoveDispatchData,
|
||||
@@ -103,6 +106,9 @@ export interface MappedEvents {
|
||||
[GatewayDispatchEvents.ChannelDelete]: [WithIntrinsicProps<GatewayChannelDeleteDispatchData>];
|
||||
[GatewayDispatchEvents.ChannelPinsUpdate]: [WithIntrinsicProps<GatewayChannelPinsUpdateDispatchData>];
|
||||
[GatewayDispatchEvents.ChannelUpdate]: [WithIntrinsicProps<GatewayChannelUpdateDispatchData>];
|
||||
[GatewayDispatchEvents.EntitlementCreate]: [WithIntrinsicProps<GatewayEntitlementCreateDispatchData>];
|
||||
[GatewayDispatchEvents.EntitlementDelete]: [WithIntrinsicProps<GatewayEntitlementDeleteDispatchData>];
|
||||
[GatewayDispatchEvents.EntitlementUpdate]: [WithIntrinsicProps<GatewayEntitlementUpdateDispatchData>];
|
||||
[GatewayDispatchEvents.GuildAuditLogEntryCreate]: [WithIntrinsicProps<GatewayGuildAuditLogEntryCreateDispatchData>];
|
||||
[GatewayDispatchEvents.GuildBanAdd]: [WithIntrinsicProps<GatewayGuildBanAddDispatchData>];
|
||||
[GatewayDispatchEvents.GuildBanRemove]: [WithIntrinsicProps<GatewayGuildBanRemoveDispatchData>];
|
||||
@@ -192,9 +198,8 @@ export class Client extends AsyncEventEmitter<MappedEvents> {
|
||||
|
||||
this.gateway.on(WebSocketShardEvents.Dispatch, ({ data: dispatch, shardId }) => {
|
||||
this.emit(
|
||||
// TODO: move this expect-error down to the next line once entitlements get merged, so missing dispatch types result in errors
|
||||
// @ts-expect-error event props can't be resolved properly, but they are correct
|
||||
dispatch.t,
|
||||
// @ts-expect-error event props can't be resolved properly, but they are correct
|
||||
this.wrapIntrinsicProps(dispatch.d, shardId),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -19,6 +19,9 @@ class ActionsManager {
|
||||
this.register(require('./ChannelCreate'));
|
||||
this.register(require('./ChannelDelete'));
|
||||
this.register(require('./ChannelUpdate'));
|
||||
this.register(require('./EntitlementCreate'));
|
||||
this.register(require('./EntitlementDelete'));
|
||||
this.register(require('./EntitlementUpdate'));
|
||||
this.register(require('./GuildAuditLogEntryCreate'));
|
||||
this.register(require('./GuildBanAdd'));
|
||||
this.register(require('./GuildBanRemove'));
|
||||
|
||||
23
packages/discord.js/src/client/actions/EntitlementCreate.js
Normal file
23
packages/discord.js/src/client/actions/EntitlementCreate.js
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
const Action = require('./Action');
|
||||
const Events = require('../../util/Events');
|
||||
|
||||
class EntitlementCreateAction extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
|
||||
const entitlement = client.application.entitlements._add(data);
|
||||
|
||||
/**
|
||||
* Emitted whenever an entitlement is created.
|
||||
* @event Client#entitlementCreate
|
||||
* @param {Entitlement} entitlement The entitlement that was created
|
||||
*/
|
||||
client.emit(Events.EntitlementCreate, entitlement);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EntitlementCreateAction;
|
||||
27
packages/discord.js/src/client/actions/EntitlementDelete.js
Normal file
27
packages/discord.js/src/client/actions/EntitlementDelete.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const Action = require('./Action');
|
||||
const Events = require('../../util/Events');
|
||||
|
||||
class EntitlementDeleteAction extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
|
||||
const entitlement = client.application.entitlements._add(data, false);
|
||||
|
||||
client.application.entitlements.cache.delete(entitlement.id);
|
||||
|
||||
/**
|
||||
* Emitted whenever an entitlement is deleted.
|
||||
* <warn>Entitlements are not deleted when they expire.
|
||||
* This is only triggered when Discord issues a refund or deletes the entitlement manually.</warn>
|
||||
* @event Client#entitlementDelete
|
||||
* @param {Entitlement} entitlement The entitlement that was deleted
|
||||
*/
|
||||
client.emit(Events.EntitlementDelete, entitlement);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EntitlementDeleteAction;
|
||||
25
packages/discord.js/src/client/actions/EntitlementUpdate.js
Normal file
25
packages/discord.js/src/client/actions/EntitlementUpdate.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
const Action = require('./Action');
|
||||
const Events = require('../../util/Events');
|
||||
|
||||
class EntitlementUpdateAction extends Action {
|
||||
handle(data) {
|
||||
const client = this.client;
|
||||
|
||||
const oldEntitlement = client.application.entitlements.cache.get(data.id)?._clone() ?? null;
|
||||
const newEntitlement = client.application.entitlements._add(data);
|
||||
|
||||
/**
|
||||
* Emitted whenever an entitlement is updated - i.e. when a user's subscription renews.
|
||||
* @event Client#entitlementUpdate
|
||||
* @param {?Entitlement} oldEntitlement The entitlement before the update
|
||||
* @param {Entitlement} newEntitlement The entitlement after the update
|
||||
*/
|
||||
client.emit(Events.EntitlementUpdate, oldEntitlement, newEntitlement);
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EntitlementUpdateAction;
|
||||
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.EntitlementCreate.handle(packet.d);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.EntitlementDelete.handle(packet.d);
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = (client, packet) => {
|
||||
client.actions.EntitlementUpdate.handle(packet.d);
|
||||
};
|
||||
@@ -10,6 +10,9 @@ const handlers = Object.fromEntries([
|
||||
['CHANNEL_DELETE', require('./CHANNEL_DELETE')],
|
||||
['CHANNEL_PINS_UPDATE', require('./CHANNEL_PINS_UPDATE')],
|
||||
['CHANNEL_UPDATE', require('./CHANNEL_UPDATE')],
|
||||
['ENTITLEMENT_CREATE', require('./ENTITLEMENT_CREATE')],
|
||||
['ENTITLEMENT_DELETE', require('./ENTITLEMENT_DELETE')],
|
||||
['ENTITLEMENT_UPDATE', require('./ENTITLEMENT_UPDATE')],
|
||||
['GUILD_AUDIT_LOG_ENTRY_CREATE', require('./GUILD_AUDIT_LOG_ENTRY_CREATE')],
|
||||
['GUILD_BAN_ADD', require('./GUILD_BAN_ADD')],
|
||||
['GUILD_BAN_REMOVE', require('./GUILD_BAN_REMOVE')],
|
||||
|
||||
@@ -173,6 +173,8 @@
|
||||
* @property {'GuildForumMessageRequired'} GuildForumMessageRequired
|
||||
|
||||
* @property {'SweepFilterReturn'} SweepFilterReturn
|
||||
|
||||
* @property {'EntitlementCreateInvalidOwner'} EntitlementCreateInvalidOwner
|
||||
*/
|
||||
|
||||
const keys = [
|
||||
@@ -323,6 +325,8 @@ const keys = [
|
||||
'SweepFilterReturn',
|
||||
|
||||
'GuildForumMessageRequired',
|
||||
|
||||
'EntitlementCreateInvalidOwner',
|
||||
];
|
||||
|
||||
// JSDoc for IntelliSense purposes
|
||||
|
||||
@@ -165,6 +165,9 @@ const Messages = {
|
||||
[DjsErrorCodes.SweepFilterReturn]: 'The return value of the sweepFilter function was not false or a Function',
|
||||
|
||||
[DjsErrorCodes.GuildForumMessageRequired]: 'You must provide a message to create a guild forum thread',
|
||||
|
||||
[DjsErrorCodes.EntitlementCreateInvalidOwner]:
|
||||
'You must provide either a guild or a user to create an entitlement, but not both',
|
||||
};
|
||||
|
||||
module.exports = Messages;
|
||||
|
||||
@@ -38,6 +38,7 @@ exports.Partials = require('./util/Partials');
|
||||
exports.PermissionsBitField = require('./util/PermissionsBitField');
|
||||
exports.RoleFlagsBitField = require('./util/RoleFlagsBitField');
|
||||
exports.ShardEvents = require('./util/ShardEvents');
|
||||
exports.SKUFlagsBitField = require('./util/SKUFlagsBitField').SKUFlagsBitField;
|
||||
exports.Status = require('./util/Status');
|
||||
exports.SnowflakeUtil = require('@sapphire/snowflake').DiscordSnowflake;
|
||||
exports.Sweepers = require('./util/Sweepers');
|
||||
@@ -58,6 +59,7 @@ exports.ChannelManager = require('./managers/ChannelManager');
|
||||
exports.ClientVoiceManager = require('./client/voice/ClientVoiceManager');
|
||||
exports.DataManager = require('./managers/DataManager');
|
||||
exports.DMMessageManager = require('./managers/DMMessageManager');
|
||||
exports.EntitlementManager = require('./managers/EntitlementManager').EntitlementManager;
|
||||
exports.GuildApplicationCommandManager = require('./managers/GuildApplicationCommandManager');
|
||||
exports.GuildBanManager = require('./managers/GuildBanManager');
|
||||
exports.GuildChannelManager = require('./managers/GuildChannelManager');
|
||||
@@ -121,6 +123,7 @@ exports.DMChannel = require('./structures/DMChannel');
|
||||
exports.Embed = require('./structures/Embed');
|
||||
exports.EmbedBuilder = require('./structures/EmbedBuilder');
|
||||
exports.Emoji = require('./structures/Emoji').Emoji;
|
||||
exports.Entitlement = require('./structures/Entitlement').Entitlement;
|
||||
exports.ForumChannel = require('./structures/ForumChannel');
|
||||
exports.Guild = require('./structures/Guild').Guild;
|
||||
exports.GuildAuditLogs = require('./structures/GuildAuditLogs');
|
||||
@@ -188,6 +191,7 @@ exports.RoleSelectMenuInteraction = require('./structures/RoleSelectMenuInteract
|
||||
exports.StringSelectMenuInteraction = require('./structures/StringSelectMenuInteraction');
|
||||
exports.UserSelectMenuInteraction = require('./structures/UserSelectMenuInteraction');
|
||||
exports.SelectMenuOptionBuilder = require('./structures/SelectMenuOptionBuilder');
|
||||
exports.SKU = require('./structures/SKU').SKU;
|
||||
exports.StringSelectMenuOptionBuilder = require('./structures/StringSelectMenuOptionBuilder');
|
||||
exports.StageChannel = require('./structures/StageChannel');
|
||||
exports.StageInstance = require('./structures/StageInstance').StageInstance;
|
||||
|
||||
129
packages/discord.js/src/managers/EntitlementManager.js
Normal file
129
packages/discord.js/src/managers/EntitlementManager.js
Normal file
@@ -0,0 +1,129 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { makeURLSearchParams } = require('@discordjs/rest');
|
||||
const { Routes, EntitlementOwnerType } = require('discord-api-types/v10');
|
||||
const CachedManager = require('./CachedManager');
|
||||
const { ErrorCodes, DiscordjsTypeError } = require('../errors/index');
|
||||
const { Entitlement } = require('../structures/Entitlement');
|
||||
const { resolveSKUId } = require('../util/Util');
|
||||
|
||||
/**
|
||||
* Manages API methods for entitlements and stores their cache.
|
||||
* @extends {CachedManager}
|
||||
*/
|
||||
class EntitlementManager extends CachedManager {
|
||||
constructor(client, iterable) {
|
||||
super(client, Entitlement, iterable);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cache of this manager
|
||||
* @type {Collection<Snowflake, Entitlement>}
|
||||
* @name EntitlementManager#cache
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that resolves to give an Entitlement object. This can be:
|
||||
* * An Entitlement object
|
||||
* * A Snowflake
|
||||
* @typedef {Entitlement|Snowflake} EntitlementResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data that resolves to give a SKU object. This can be:
|
||||
* * A SKU object
|
||||
* * A Snowflake
|
||||
* @typedef {SKU|Snowflake} SKUResolvable
|
||||
*/
|
||||
|
||||
/**
|
||||
* Options used to fetch entitlements
|
||||
* @typedef {Object} FetchEntitlementsOptions
|
||||
* @property {number} [limit] The maximum number of entitlements to fetch
|
||||
* @property {GuildResolvable} [guild] The guild to fetch entitlements for
|
||||
* @property {UserResolvable} [user] The user to fetch entitlements for
|
||||
* @property {SKUResolvable[]} [skus] The SKUs to fetch entitlements for
|
||||
* @property {boolean} [excludeEnded] Whether to exclude ended entitlements
|
||||
* @property {boolean} [cache=true] Whether to cache the fetched entitlements
|
||||
* @property {Snowflake} [before] Consider only entitlements before this entitlement id
|
||||
* @property {Snowflake} [after] Consider only entitlements after this entitlement id
|
||||
* <warn>If both `before` and `after` are provided, only `before` is respected</warn>
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetches entitlements for this application
|
||||
* @param {FetchEntitlementsOptions} [options={}] Options for fetching the entitlements
|
||||
* @returns {Promise<Collection<Snowflake, Entitlement>>}
|
||||
*/
|
||||
async fetch({ limit, guild, user, skus, excludeEnded, cache = true, before, after } = {}) {
|
||||
const query = makeURLSearchParams({
|
||||
limit,
|
||||
guild_id: guild && this.client.guilds.resolveId(guild),
|
||||
user_id: user && this.client.users.resolveId(user),
|
||||
sku_ids: skus?.map(sku => resolveSKUId(sku)).join(','),
|
||||
exclude_ended: excludeEnded,
|
||||
before,
|
||||
after,
|
||||
});
|
||||
|
||||
const entitlements = await this.client.rest.get(Routes.entitlements(this.client.application.id), { query });
|
||||
return entitlements.reduce(
|
||||
(coll, entitlement) => coll.set(entitlement.id, this._add(entitlement, cache)),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options used to create a test entitlement
|
||||
* <info>Either `guild` or `user` must be provided, but not both</info>
|
||||
* @typedef {Object} EntitlementCreateOptions
|
||||
* @property {SKUResolvable} sku The id of the SKU to create the entitlement for
|
||||
* @property {GuildResolvable} [guild] The guild to create the entitlement for
|
||||
* @property {UserResolvable} [user] The user to create the entitlement for
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a test entitlement
|
||||
* @param {EntitlementCreateOptions} options Options for creating the test entitlement
|
||||
* @returns {Promise<Entitlement>}
|
||||
*/
|
||||
async createTest({ sku, guild, user }) {
|
||||
const skuId = resolveSKUId(sku);
|
||||
if (!skuId) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'sku', 'SKUResolvable');
|
||||
|
||||
if ((guild && user) || (!guild && !user)) {
|
||||
throw new DiscordjsTypeError(ErrorCodes.EntitlementCreateInvalidOwner);
|
||||
}
|
||||
|
||||
const resolved = guild ? this.client.guilds.resolveId(guild) : this.client.users.resolveId(user);
|
||||
if (!resolved) {
|
||||
const name = guild ? 'guild' : 'user';
|
||||
const type = guild ? 'GuildResolvable' : 'UserResolvable';
|
||||
throw new DiscordjsTypeError(ErrorCodes.InvalidType, name, type);
|
||||
}
|
||||
|
||||
const entitlement = await this.client.rest.post(Routes.entitlements(this.client.application.id), {
|
||||
body: {
|
||||
sku_id: skuId,
|
||||
owner_id: resolved,
|
||||
owner_type: guild ? EntitlementOwnerType.Guild : EntitlementOwnerType.User,
|
||||
},
|
||||
});
|
||||
return new Entitlement(this.client, entitlement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a test entitlement
|
||||
* @param {EntitlementResolvable} entitlement The entitlement to delete
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async deleteTest(entitlement) {
|
||||
const resolved = this.resolveId(entitlement);
|
||||
if (!resolved) throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'entitlement', 'EntitlementResolvable');
|
||||
|
||||
await this.client.rest.delete(Routes.entitlement(this.client.application.id, resolved));
|
||||
}
|
||||
}
|
||||
|
||||
exports.EntitlementManager = EntitlementManager;
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { deprecate } = require('node:util');
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { DiscordSnowflake } = require('@sapphire/snowflake');
|
||||
const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10');
|
||||
const Base = require('./Base');
|
||||
@@ -133,6 +134,15 @@ class BaseInteraction extends Base {
|
||||
* @type {?Locale}
|
||||
*/
|
||||
this.guildLocale = data.guild_locale ?? null;
|
||||
|
||||
/**
|
||||
* The entitlements for the invoking user, representing access to premium SKUs
|
||||
* @type {Collection<Snowflake, Entitlement>}
|
||||
*/
|
||||
this.entitlements = data.entitlements.reduce(
|
||||
(coll, entitlement) => coll.set(entitlement.id, this.client.application.entitlements._add(entitlement)),
|
||||
new Collection(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { Routes } = require('discord-api-types/v10');
|
||||
const { ApplicationRoleConnectionMetadata } = require('./ApplicationRoleConnectionMetadata');
|
||||
const { SKU } = require('./SKU');
|
||||
const Team = require('./Team');
|
||||
const Application = require('./interfaces/Application');
|
||||
const ApplicationCommandManager = require('../managers/ApplicationCommandManager');
|
||||
const { EntitlementManager } = require('../managers/EntitlementManager');
|
||||
const ApplicationFlagsBitField = require('../util/ApplicationFlagsBitField');
|
||||
const { resolveImage } = require('../util/DataResolver');
|
||||
const PermissionsBitField = require('../util/PermissionsBitField');
|
||||
@@ -28,6 +31,12 @@ class ClientApplication extends Application {
|
||||
* @type {ApplicationCommandManager}
|
||||
*/
|
||||
this.commands = new ApplicationCommandManager(this.client);
|
||||
|
||||
/**
|
||||
* The entitlement manager for this application
|
||||
* @type {EntitlementManager}
|
||||
*/
|
||||
this.entitlements = new EntitlementManager(this.client);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
@@ -287,6 +296,15 @@ class ClientApplication extends Application {
|
||||
|
||||
return newRecords.map(data => new ApplicationRoleConnectionMetadata(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this application's SKUs
|
||||
* @returns {Promise<Collection<Snowflake, SKU>>}
|
||||
*/
|
||||
async fetchSKUs() {
|
||||
const skus = await this.client.rest.get(Routes.skus(this.id));
|
||||
return skus.reduce((coll, sku) => coll.set(sku.id, new SKU(this.client, sku)), new Collection());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ClientApplication;
|
||||
|
||||
@@ -153,6 +153,7 @@ class CommandInteraction extends BaseInteraction {
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
showModal() {}
|
||||
sendPremiumRequired() {}
|
||||
awaitModalSubmit() {}
|
||||
}
|
||||
|
||||
|
||||
164
packages/discord.js/src/structures/Entitlement.js
Normal file
164
packages/discord.js/src/structures/Entitlement.js
Normal file
@@ -0,0 +1,164 @@
|
||||
'use strict';
|
||||
|
||||
const Base = require('./Base');
|
||||
|
||||
/**
|
||||
* Represents an Entitlement
|
||||
* @extends {Base}
|
||||
*/
|
||||
class Entitlement extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
/**
|
||||
* The id of the entitlement
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
this._patch(data);
|
||||
}
|
||||
|
||||
_patch(data) {
|
||||
if ('sku_id' in data) {
|
||||
/**
|
||||
* The id of the associated SKU
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.skuId = data.sku_id;
|
||||
}
|
||||
|
||||
if ('user_id' in data) {
|
||||
/**
|
||||
* The id of the user that is granted access to this entitlement's SKU
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.userId = data.user_id;
|
||||
}
|
||||
|
||||
if ('guild_id' in data) {
|
||||
/**
|
||||
* The id of the guild that is granted access to this entitlement's SKU
|
||||
* @type {?Snowflake}
|
||||
*/
|
||||
this.guildId = data.guild_id;
|
||||
} else {
|
||||
this.guildId ??= null;
|
||||
}
|
||||
|
||||
if ('application_id' in data) {
|
||||
/**
|
||||
* The id of the parent application
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.applicationId = data.application_id;
|
||||
}
|
||||
|
||||
if ('type' in data) {
|
||||
/**
|
||||
* The type of this entitlement
|
||||
* @type {EntitlementType}
|
||||
*/
|
||||
this.type = data.type;
|
||||
}
|
||||
|
||||
if ('deleted' in data) {
|
||||
/**
|
||||
* Whether this entitlement was deleted
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.deleted = data.deleted;
|
||||
}
|
||||
|
||||
if ('starts_at' in data) {
|
||||
/**
|
||||
* The timestamp at which this entitlement is valid
|
||||
* <info>This is only `null` for test entitlements</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.startsTimestamp = Date.parse(data.starts_at);
|
||||
} else {
|
||||
this.startsTimestamp ??= null;
|
||||
}
|
||||
|
||||
if ('ends_at' in data) {
|
||||
/**
|
||||
* The timestamp at which this entitlement is no longer valid
|
||||
* <info>This is only `null` for test entitlements</info>
|
||||
* @type {?number}
|
||||
*/
|
||||
this.endsTimestamp = Date.parse(data.ends_at);
|
||||
} else {
|
||||
this.endsTimestamp ??= null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The guild that is granted access to this entitlement's SKU
|
||||
* @type {?Guild}
|
||||
*/
|
||||
get guild() {
|
||||
if (!this.guildId) return null;
|
||||
return this.client.guilds.cache.get(this.guildId) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start date at which this entitlement is valid
|
||||
* <info>This is only `null` for test entitlements</info>
|
||||
* @type {?Date}
|
||||
*/
|
||||
get startsAt() {
|
||||
return this.startsTimestamp && new Date(this.startsTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* The end date at which this entitlement is no longer valid
|
||||
* <info>This is only `null` for test entitlements</info>
|
||||
* @type {?Date}
|
||||
*/
|
||||
get endsAt() {
|
||||
return this.endsTimestamp && new Date(this.endsTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this entitlement is active
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isActive() {
|
||||
return !this.deleted && (!this.endsTimestamp || this.endsTimestamp > Date.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this entitlement is a test entitlement
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isTest() {
|
||||
return this.startsTimestamp === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this entitlement is a user subscription
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isUserSubscription() {
|
||||
return this.guildId === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this entitlement is a guild subscription
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isGuildSubscription() {
|
||||
return this.guildId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the user that is granted access to this entitlement's SKU
|
||||
* @returns {Promise<User>}
|
||||
*/
|
||||
fetchUser() {
|
||||
return this.client.users.fetch(this.userId);
|
||||
}
|
||||
}
|
||||
|
||||
exports.Entitlement = Entitlement;
|
||||
@@ -99,6 +99,7 @@ class MessageComponentInteraction extends BaseInteraction {
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
showModal() {}
|
||||
sendPremiumRequired() {}
|
||||
awaitModalSubmit() {}
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ class ModalSubmitInteraction extends BaseInteraction {
|
||||
followUp() {}
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
sendPremiumRequired() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(ModalSubmitInteraction, 'showModal');
|
||||
|
||||
52
packages/discord.js/src/structures/SKU.js
Normal file
52
packages/discord.js/src/structures/SKU.js
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const Base = require('./Base');
|
||||
const { SKUFlagsBitField } = require('../util/SKUFlagsBitField');
|
||||
|
||||
/**
|
||||
* Represents a premium application SKU.
|
||||
* @extends {Base}
|
||||
*/
|
||||
class SKU extends Base {
|
||||
constructor(client, data) {
|
||||
super(client);
|
||||
|
||||
/**
|
||||
* The id of the SKU
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.id = data.id;
|
||||
|
||||
/**
|
||||
* The type of the SKU
|
||||
* @type {SKUType}
|
||||
*/
|
||||
this.type = data.type;
|
||||
|
||||
/**
|
||||
* The id of the parent application
|
||||
* @type {Snowflake}
|
||||
*/
|
||||
this.applicationId = data.application_id;
|
||||
|
||||
/**
|
||||
* The customer-facing name of the premium offering
|
||||
* @type {string}
|
||||
*/
|
||||
this.name = data.name;
|
||||
|
||||
/**
|
||||
* The system-generated URL slug based on this SKU's name
|
||||
* @type {string}
|
||||
*/
|
||||
this.slug = data.slug;
|
||||
|
||||
/**
|
||||
* Flags that describe the SKU
|
||||
* @type {Readonly<SKUFlagsBitField>}
|
||||
*/
|
||||
this.flags = new SKUFlagsBitField(data.flags).freeze();
|
||||
}
|
||||
}
|
||||
|
||||
exports.SKU = SKU;
|
||||
@@ -262,6 +262,22 @@ class InteractionResponses {
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds to the interaction with an upgrade button.
|
||||
* <info>Only available for applications with monetization enabled.</info>
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async sendPremiumRequired() {
|
||||
if (this.deferred || this.replied) throw new DiscordjsError(ErrorCodes.InteractionAlreadyReplied);
|
||||
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
|
||||
body: {
|
||||
type: InteractionResponseType.PremiumRequired,
|
||||
},
|
||||
auth: false,
|
||||
});
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing the same properties as {@link CollectorOptions}, but a few less:
|
||||
* @typedef {Object} AwaitModalSubmitOptions
|
||||
@@ -305,6 +321,7 @@ class InteractionResponses {
|
||||
'deferUpdate',
|
||||
'update',
|
||||
'showModal',
|
||||
'sendPremiumRequired',
|
||||
'awaitModalSubmit',
|
||||
];
|
||||
|
||||
|
||||
@@ -280,6 +280,11 @@
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ComponentType}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external EntitlementType
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/EntitlementType}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external ForumLayoutType
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ForumLayoutType}
|
||||
@@ -460,6 +465,16 @@
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-rest/common/enum/RESTJSONErrorCodes}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external SKUFlags
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/SKUFlags}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external SKUType
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/SKUType}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @external SortOrderType
|
||||
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/SortOrderType}
|
||||
|
||||
@@ -14,6 +14,7 @@ exports.MaxBulkDeletableMessageAge = 1_209_600_000;
|
||||
* * `applicationCommands` - both global and guild commands
|
||||
* * `bans`
|
||||
* * `emojis`
|
||||
* * `entitlements`
|
||||
* * `invites` - accepts the `lifetime` property, using it will sweep based on expires timestamp
|
||||
* * `guildMembers`
|
||||
* * `messages` - accepts the `lifetime` property, using it will sweep based on edited or created timestamp
|
||||
@@ -32,6 +33,7 @@ exports.SweeperKeys = [
|
||||
'applicationCommands',
|
||||
'bans',
|
||||
'emojis',
|
||||
'entitlements',
|
||||
'invites',
|
||||
'guildMembers',
|
||||
'messages',
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
* @property {string} ChannelUpdate channelUpdate
|
||||
* @property {string} ClientReady ready
|
||||
* @property {string} Debug debug
|
||||
* @property {string} EntitlementCreate entitlementCreate
|
||||
* @property {string} EntitlementUpdate entitlementUpdate
|
||||
* @property {string} EntitlementDelete entitlementDelete
|
||||
* @property {string} Error error
|
||||
* @property {string} GuildAuditLogEntryCreate guildAuditLogEntryCreate
|
||||
* @property {string} GuildAvailable guildAvailable
|
||||
@@ -96,6 +99,9 @@ module.exports = {
|
||||
ChannelUpdate: 'channelUpdate',
|
||||
ClientReady: 'ready',
|
||||
Debug: 'debug',
|
||||
EntitlementCreate: 'entitlementCreate',
|
||||
EntitlementUpdate: 'entitlementUpdate',
|
||||
EntitlementDelete: 'entitlementDelete',
|
||||
Error: 'error',
|
||||
GuildAuditLogEntryCreate: 'guildAuditLogEntryCreate',
|
||||
GuildAvailable: 'guildAvailable',
|
||||
|
||||
26
packages/discord.js/src/util/SKUFlagsBitField.js
Normal file
26
packages/discord.js/src/util/SKUFlagsBitField.js
Normal file
@@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const { SKUFlags } = require('discord-api-types/v10');
|
||||
const BitField = require('./BitField');
|
||||
|
||||
/**
|
||||
* Data structure that makes it easy to interact with an {@link SKU#flags} bitfield.
|
||||
* @extends {BitField}
|
||||
*/
|
||||
class SKUFlagsBitField extends BitField {
|
||||
/**
|
||||
* Numeric SKU flags.
|
||||
* @type {SKUFlags}
|
||||
* @memberof SKUFlagsBitField
|
||||
*/
|
||||
static Flags = SKUFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name SKUFlagsBitField
|
||||
* @kind constructor
|
||||
* @memberof SKUFlagsBitField
|
||||
* @param {BitFieldResolvable} [bits=0] Bit(s) to read from
|
||||
*/
|
||||
|
||||
exports.SKUFlagsBitField = SKUFlagsBitField;
|
||||
@@ -106,6 +106,23 @@ class Sweepers {
|
||||
return this._sweepGuildDirectProp('emojis', filter).items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeps all client application entitlements and removes the ones which are indicated by the filter.
|
||||
* @param {Function} filter The function used to determine which entitlements will be removed from the caches.
|
||||
* @returns {number} Amount of entitlements that were removed from the caches
|
||||
*/
|
||||
sweepEntitlements(filter) {
|
||||
if (typeof filter !== 'function') {
|
||||
throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'filter', 'function');
|
||||
}
|
||||
|
||||
const entitlements = this.client.application.entitlements.cache.sweep(filter);
|
||||
|
||||
this.client.emit(Events.CacheSweep, `Swept ${entitlements} entitlements.`);
|
||||
|
||||
return entitlements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sweeps all guild invites and removes the ones which are indicated by the filter.
|
||||
* @param {Function} filter The function used to determine which invites will be removed from the caches.
|
||||
|
||||
@@ -479,6 +479,17 @@ function transformResolved(
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a SKU id from a SKU resolvable.
|
||||
* @param {SKUResolvable} resolvable The SKU resolvable to resolve
|
||||
* @returns {?Snowflake} The resolved SKU id, or `null` if the resolvable was invalid
|
||||
*/
|
||||
function resolveSKUId(resolvable) {
|
||||
if (typeof resolvable === 'string') return resolvable;
|
||||
if (resolvable instanceof SKU) return resolvable.id;
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
flatten,
|
||||
fetchRecommendedShardCount,
|
||||
@@ -497,8 +508,10 @@ module.exports = {
|
||||
cleanCodeBlockContent,
|
||||
parseWebhookURL,
|
||||
transformResolved,
|
||||
resolveSKUId,
|
||||
};
|
||||
|
||||
// Fixes Circular
|
||||
const Attachment = require('../structures/Attachment');
|
||||
const GuildChannel = require('../structures/GuildChannel');
|
||||
const { SKU } = require('../structures/SKU.js');
|
||||
|
||||
59
packages/discord.js/test/monetization.js
Normal file
59
packages/discord.js/test/monetization.js
Normal file
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
const { token, owner } = require('./auth.js');
|
||||
const { Client, Events, codeBlock, GatewayIntentBits } = require('../src');
|
||||
|
||||
const client = new Client({ intents: GatewayIntentBits.Guilds | GatewayIntentBits.GuildMessages });
|
||||
|
||||
client.on('raw', console.log);
|
||||
|
||||
client.on(Events.ClientReady, async () => {
|
||||
const commands = await client.application.commands.fetch();
|
||||
if (!commands.size) {
|
||||
await client.application.commands.set([
|
||||
{
|
||||
name: 'test',
|
||||
description: 'yeet',
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
const skus = await client.application.fetchSKUs();
|
||||
console.log('skus', skus);
|
||||
|
||||
const entitlements = await client.application.entitlements.fetch();
|
||||
console.log('entitlements', entitlements);
|
||||
});
|
||||
|
||||
client.on(Events.EntitlementCreate, entitlement => console.log('EntitlementCreate', entitlement));
|
||||
client.on(Events.EntitlementDelete, entitlement => console.log('EntitlementDelete', entitlement));
|
||||
client.on(Events.EntitlementUpdate, (oldEntitlement, newEntitlement) =>
|
||||
console.log('EntitlementUpdate', oldEntitlement, newEntitlement),
|
||||
);
|
||||
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
console.log('interaction.entitlements', interaction.entitlements);
|
||||
|
||||
if (interaction.commandName === 'test') {
|
||||
await interaction.sendPremiumRequired();
|
||||
}
|
||||
});
|
||||
|
||||
client.on(Events.MessageCreate, async message => {
|
||||
const prefix = `<@${client.user.id}> `;
|
||||
|
||||
if (message.author.id !== owner || !message.content.startsWith(prefix)) return;
|
||||
let res;
|
||||
try {
|
||||
res = await eval(message.content.slice(prefix.length));
|
||||
if (typeof res !== 'string') res = require('node:util').inspect(res);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err.stack);
|
||||
res = err.message;
|
||||
}
|
||||
|
||||
await message.channel.send(codeBlock('js', res));
|
||||
});
|
||||
|
||||
client.login(token);
|
||||
98
packages/discord.js/typings/index.d.ts
vendored
98
packages/discord.js/typings/index.d.ts
vendored
@@ -170,6 +170,11 @@ import {
|
||||
TeamMemberRole,
|
||||
GuildWidgetStyle,
|
||||
GuildOnboardingMode,
|
||||
APISKU,
|
||||
SKUFlags,
|
||||
SKUType,
|
||||
APIEntitlement,
|
||||
EntitlementType,
|
||||
} from 'discord-api-types/v10';
|
||||
import { ChildProcess } from 'node:child_process';
|
||||
import { EventEmitter } from 'node:events';
|
||||
@@ -585,6 +590,7 @@ export abstract class CommandInteraction<Cached extends CacheType = CacheType> e
|
||||
| ModalComponentData
|
||||
| APIModalInteractionResponseCallbackData,
|
||||
): Promise<void>;
|
||||
public sendPremiumRequired(): Promise<void>;
|
||||
public awaitModalSubmit(
|
||||
options: AwaitModalSubmitOptions<ModalSubmitInteraction>,
|
||||
): Promise<ModalSubmitInteraction<Cached>>;
|
||||
@@ -1033,6 +1039,7 @@ export class ClientApplication extends Application {
|
||||
public botRequireCodeGrant: boolean | null;
|
||||
public bot: User | null;
|
||||
public commands: ApplicationCommandManager;
|
||||
public entitlements: EntitlementManager;
|
||||
public guildId: Snowflake | null;
|
||||
public get guild(): Guild | null;
|
||||
public cover: string | null;
|
||||
@@ -1049,6 +1056,7 @@ export class ClientApplication extends Application {
|
||||
public edit(options: ClientApplicationEditOptions): Promise<ClientApplication>;
|
||||
public fetch(): Promise<ClientApplication>;
|
||||
public fetchRoleConnectionMetadataRecords(): Promise<ApplicationRoleConnectionMetadata[]>;
|
||||
public fetchSKUs(): Promise<Collection<Snowflake, SKU>>;
|
||||
public editRoleConnectionMetadataRecords(
|
||||
records: ApplicationRoleConnectionMetadataEditOptions[],
|
||||
): Promise<ApplicationRoleConnectionMetadata[]>;
|
||||
@@ -1305,6 +1313,32 @@ export class Emoji extends Base {
|
||||
public toString(): string;
|
||||
}
|
||||
|
||||
export class Entitlement extends Base {
|
||||
private constructor(client: Client<true>, data: APIEntitlement);
|
||||
public id: Snowflake;
|
||||
public skuId: Snowflake;
|
||||
public userId: Snowflake;
|
||||
public guildId: Snowflake | null;
|
||||
public applicationId: Snowflake;
|
||||
public type: EntitlementType;
|
||||
public deleted: boolean;
|
||||
public startsTimestamp: number | null;
|
||||
public endsTimestamp: number | null;
|
||||
public get guild(): Guild | null;
|
||||
public get startsAt(): Date | null;
|
||||
public get endsAt(): Date | null;
|
||||
public fetchUser(): Promise<User>;
|
||||
public isActive(): boolean;
|
||||
public isTest(): this is this & {
|
||||
startsTimestamp: null;
|
||||
endsTimestamp: null;
|
||||
get startsAt(): null;
|
||||
get endsAt(): null;
|
||||
};
|
||||
public isUserSubscription(): this is this & { guildId: null; get guild(): null };
|
||||
public isGuildSubscription(): this is this & { guildId: Snowflake; guild: Guild };
|
||||
}
|
||||
|
||||
export class Guild extends AnonymousGuild {
|
||||
private constructor(client: Client<true>, data: RawGuildData);
|
||||
private _sortedRoles(): Collection<Snowflake, Role>;
|
||||
@@ -1829,6 +1863,7 @@ export class BaseInteraction<Cached extends CacheType = CacheType> extends Base
|
||||
public memberPermissions: CacheTypeReducer<Cached, Readonly<PermissionsBitField>>;
|
||||
public locale: Locale;
|
||||
public guildLocale: CacheTypeReducer<Cached, Locale>;
|
||||
public entitlements: Collection<Snowflake, Entitlement>;
|
||||
public inGuild(): this is BaseInteraction<'raw' | 'cached'>;
|
||||
public inCachedGuild(): this is BaseInteraction<'cached'>;
|
||||
public inRawGuild(): this is BaseInteraction<'raw'>;
|
||||
@@ -2178,6 +2213,7 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
|
||||
| ModalComponentData
|
||||
| APIModalInteractionResponseCallbackData,
|
||||
): Promise<void>;
|
||||
public sendPremiumRequired(): Promise<void>;
|
||||
public awaitModalSubmit(
|
||||
options: AwaitModalSubmitOptions<ModalSubmitInteraction>,
|
||||
): Promise<ModalSubmitInteraction<Cached>>;
|
||||
@@ -2376,6 +2412,7 @@ export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extend
|
||||
options: InteractionDeferUpdateOptions & { fetchReply: true },
|
||||
): Promise<Message<BooleanCache<Cached>>>;
|
||||
public deferUpdate(options?: InteractionDeferUpdateOptions): Promise<InteractionResponse<BooleanCache<Cached>>>;
|
||||
public sendPremiumRequired(): Promise<void>;
|
||||
public inGuild(): this is ModalSubmitInteraction<'raw' | 'cached'>;
|
||||
public inCachedGuild(): this is ModalSubmitInteraction<'cached'>;
|
||||
public inRawGuild(): this is ModalSubmitInteraction<'raw'>;
|
||||
@@ -2875,6 +2912,23 @@ export {
|
||||
DeconstructedSnowflake,
|
||||
} from '@sapphire/snowflake';
|
||||
|
||||
export class SKU extends Base {
|
||||
private constructor(client: Client<true>, data: APISKU);
|
||||
public id: Snowflake;
|
||||
public type: SKUType;
|
||||
public applicationId: Snowflake;
|
||||
public name: string;
|
||||
public slug: string;
|
||||
public flags: Readonly<SKUFlagsBitField>;
|
||||
}
|
||||
|
||||
export type SKUFlagsString = keyof typeof SKUFlags;
|
||||
|
||||
export class SKUFlagsBitField extends BitField<SKUFlagsString> {
|
||||
public static FLAGS: typeof SKUFlags;
|
||||
public static resolve(bit?: BitFieldResolvable<SKUFlagsString, number>): number;
|
||||
}
|
||||
|
||||
export class StageChannel extends BaseGuildVoiceChannel {
|
||||
public get stageInstance(): StageInstance | null;
|
||||
public topic: string | null;
|
||||
@@ -2974,6 +3028,9 @@ export class Sweepers {
|
||||
public sweepEmojis(
|
||||
filter: CollectionSweepFilter<SweeperDefinitions['emojis'][0], SweeperDefinitions['emojis'][1]>,
|
||||
): number;
|
||||
public sweepEntitlements(
|
||||
filter: CollectionSweepFilter<SweeperDefinitions['entitlements'][0], SweeperDefinitions['entitlements'][1]>,
|
||||
): number;
|
||||
public sweepInvites(
|
||||
filter: CollectionSweepFilter<SweeperDefinitions['invites'][0], SweeperDefinitions['invites'][1]>,
|
||||
): number;
|
||||
@@ -3281,6 +3338,7 @@ export function transformResolved<Cached extends CacheType>(
|
||||
supportingData: SupportingInteractionResolvedData,
|
||||
data?: APIApplicationCommandInteractionData['resolved'],
|
||||
): CommandInteractionResolvedData<Cached>;
|
||||
export function resolveSKUId(resolvable: SKUResolvable): Snowflake | null;
|
||||
|
||||
export interface MappedComponentBuilderTypes {
|
||||
[ComponentType.Button]: ButtonBuilder;
|
||||
@@ -3819,6 +3877,8 @@ export enum DiscordjsErrorCodes {
|
||||
SweepFilterReturn = 'SweepFilterReturn',
|
||||
|
||||
GuildForumMessageRequired = 'GuildForumMessageRequired',
|
||||
|
||||
EntitlementCreateInvalidOwner = 'EntitlementCreateInvalidOwner',
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -4002,6 +4062,37 @@ export class ChannelManager extends CachedManager<Snowflake, Channel, ChannelRes
|
||||
public fetch(id: Snowflake, options?: FetchChannelOptions): Promise<Channel | null>;
|
||||
}
|
||||
|
||||
export type EntitlementResolvable = Snowflake | Entitlement;
|
||||
export type SKUResolvable = Snowflake | SKU;
|
||||
|
||||
export interface GuildEntitlementCreateOptions {
|
||||
sku: SKUResolvable;
|
||||
guild: GuildResolvable;
|
||||
}
|
||||
|
||||
export interface UserEntitlementCreateOptions {
|
||||
sku: SKUResolvable;
|
||||
user: UserResolvable;
|
||||
}
|
||||
|
||||
export interface FetchEntitlementsOptions {
|
||||
limit?: number;
|
||||
guild?: GuildResolvable;
|
||||
user?: UserResolvable;
|
||||
skus?: readonly SKUResolvable[];
|
||||
excludeEnded?: boolean;
|
||||
cache?: boolean;
|
||||
before?: Snowflake;
|
||||
after?: Snowflake;
|
||||
}
|
||||
|
||||
export class EntitlementManager extends CachedManager<Snowflake, Entitlement, EntitlementResolvable> {
|
||||
private constructor(client: Client<true>, iterable: Iterable<APIEntitlement>);
|
||||
public fetch(options?: FetchEntitlementsOptions): Promise<Collection<Snowflake, Entitlement>>;
|
||||
public createTest(options: GuildEntitlementCreateOptions | UserEntitlementCreateOptions): Promise<Entitlement>;
|
||||
public deleteTest(entitlement: EntitlementResolvable): Promise<void>;
|
||||
}
|
||||
|
||||
export interface FetchGuildApplicationCommandFetchOptions extends Omit<FetchApplicationCommandOptions, 'guildId'> {}
|
||||
|
||||
export class GuildApplicationCommandManager extends ApplicationCommandManager<ApplicationCommand, {}, Guild> {
|
||||
@@ -4980,6 +5071,9 @@ export interface ClientEvents {
|
||||
emojiCreate: [emoji: GuildEmoji];
|
||||
emojiDelete: [emoji: GuildEmoji];
|
||||
emojiUpdate: [oldEmoji: GuildEmoji, newEmoji: GuildEmoji];
|
||||
entitlementCreate: [entitlement: Entitlement];
|
||||
entitlementDelete: [entitlement: Entitlement];
|
||||
entitlementUpdate: [oldEntitlement: Entitlement | null, newEntitlement: Entitlement];
|
||||
error: [error: Error];
|
||||
guildAuditLogEntryCreate: [auditLogEntry: GuildAuditLogsEntry, guild: Guild];
|
||||
guildAvailable: [guild: Guild];
|
||||
@@ -5192,6 +5286,9 @@ export enum Events {
|
||||
AutoModerationRuleDelete = 'autoModerationRuleDelete',
|
||||
AutoModerationRuleUpdate = 'autoModerationRuleUpdate',
|
||||
ClientReady = 'ready',
|
||||
EntitlementCreate = 'entitlementCreate',
|
||||
EntitlementDelete = 'entitlementDelete',
|
||||
EntitlementUpdate = 'entitlementUpdate',
|
||||
GuildAuditLogEntryCreate = 'guildAuditLogEntryCreate',
|
||||
GuildAvailable = 'guildAvailable',
|
||||
GuildCreate = 'guildCreate',
|
||||
@@ -6475,6 +6572,7 @@ export interface SweeperDefinitions {
|
||||
autoModerationRules: [Snowflake, AutoModerationRule];
|
||||
bans: [Snowflake, GuildBan];
|
||||
emojis: [Snowflake, GuildEmoji];
|
||||
entitlements: [Snowflake, Entitlement];
|
||||
invites: [string, Invite, true];
|
||||
guildMembers: [Snowflake, GuildMember];
|
||||
messages: [Snowflake, Message, true];
|
||||
|
||||
@@ -188,6 +188,8 @@ import {
|
||||
Awaitable,
|
||||
Channel,
|
||||
DirectoryChannel,
|
||||
Entitlement,
|
||||
SKU,
|
||||
} from '.';
|
||||
import { expectAssignable, expectNotAssignable, expectNotType, expectType } from 'tsd';
|
||||
import type { ContextMenuCommandBuilder, SlashCommandBuilder } from '@discordjs/builders';
|
||||
@@ -2426,3 +2428,55 @@ declare const emoji: Emoji;
|
||||
expectType<PartialEmojiOnlyId>(resolvePartialEmoji('12345678901234567'));
|
||||
expectType<PartialEmoji | null>(resolvePartialEmoji(emoji));
|
||||
}
|
||||
|
||||
declare const application: ClientApplication;
|
||||
declare const entitlement: Entitlement;
|
||||
declare const sku: SKU;
|
||||
{
|
||||
expectType<Collection<Snowflake, SKU>>(await application.fetchSKUs());
|
||||
expectType<Collection<Snowflake, Entitlement>>(await application.entitlements.fetch());
|
||||
|
||||
await application.entitlements.fetch({
|
||||
guild,
|
||||
skus: ['12345678901234567', sku],
|
||||
user,
|
||||
excludeEnded: true,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
await application.entitlements.createTest({ sku: '12345678901234567', user });
|
||||
await application.entitlements.createTest({ sku, guild });
|
||||
|
||||
await application.entitlements.deleteTest(entitlement);
|
||||
|
||||
expectType<boolean>(entitlement.isActive());
|
||||
|
||||
if (entitlement.isUserSubscription()) {
|
||||
expectType<Snowflake>(entitlement.userId);
|
||||
expectType<User>(await entitlement.fetchUser());
|
||||
expectType<null>(entitlement.guildId);
|
||||
expectType<null>(entitlement.guild);
|
||||
|
||||
await application.entitlements.deleteTest(entitlement);
|
||||
} else if (entitlement.isGuildSubscription()) {
|
||||
expectType<Snowflake>(entitlement.guildId);
|
||||
expectType<Guild>(entitlement.guild);
|
||||
|
||||
await application.entitlements.deleteTest(entitlement);
|
||||
}
|
||||
|
||||
if (entitlement.isTest()) {
|
||||
expectType<null>(entitlement.startsTimestamp);
|
||||
expectType<null>(entitlement.endsTimestamp);
|
||||
expectType<null>(entitlement.startsAt);
|
||||
expectType<null>(entitlement.endsAt);
|
||||
}
|
||||
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
expectType<Collection<Snowflake, Entitlement>>(interaction.entitlements);
|
||||
|
||||
if (interaction.isRepliable()) {
|
||||
await interaction.sendPremiumRequired();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user