Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: premium app subscriptions #114

Merged
merged 21 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions esm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const {
Constants,
DiscordHTTPError,
DiscordRESTError,
Entitlement,
ExtendedUser,
ForumChannel,
Guild,
Expand Down
105 changes: 100 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,11 @@ declare namespace Dysnomia {
// Message
type ActionRowComponents = Button | SelectMenu;
type BaseSelectMenuTypes = Exclude<SelectMenuTypes, SelectMenuExtendedTypes>;
type Button = InteractionButton | URLButton;
type Button = InteractionButton | PremiumButton | URLButton;
type ButtonStyles = Constants["ButtonStyles"][keyof Constants["ButtonStyles"]];
type ButtonStyleNormal = Exclude<ButtonStyles, ButtonStyleLink>;
type ButtonStyleNormal = Exclude<ButtonStyles, ButtonStyleLink | ButtonStylePremium>;
type ButtonStyleLink = Constants["ButtonStyles"]["LINK"];
type ButtonStylePremium = Constants["ButtonStyles"]["PREMIUM"];
type Component = ActionRow | ActionRowComponents;
type ComponentTypes = Constants["ComponentTypes"][keyof Constants["ComponentTypes"]];
type ImageFormat = Constants["ImageFormats"][number];
Expand Down Expand Up @@ -197,6 +198,11 @@ declare namespace Dysnomia {
type MessageWebhookContent = Pick<WebhookPayload, "content" | "embeds" | "allowedMentions" | "components" | "attachments" | "threadID">;
type WebhookTypes = Constants["WebhookTypes"][keyof Constants["WebhookTypes"]];

// Subscriptions
type EntitlementOwnerTypes = Constants["EntitlementOwnerTypes"][keyof Constants["EntitlementOwnerTypes"]];
type EntitlementTypes = Constants["EntitlementTypes"][keyof Constants["EntitlementTypes"]];
type SKUTypes = Constants["SKUTypes"][keyof Constants["SKUTypes"]];

// INTERFACES
// Internals
interface JSONCache {
Expand Down Expand Up @@ -736,6 +742,9 @@ declare namespace Dysnomia {
connect: [id: number];
debug: [message: string, id?: number];
disconnect: [];
entitlementCreate: [entitlement: Entitlement];
entitlementUpdate: [entitlement: Entitlement];
entitlementDelete: [entitlement: Entitlement];
error: [err: Error, id?: number];
guildAuditLogEntryCreate: [entry: GuildAuditLogEntry];
guildAvailable: [guild: Guild];
Expand Down Expand Up @@ -1249,7 +1258,7 @@ declare namespace Dysnomia {
}
interface InteractionResponseMessage {
data: RawInteractionContent;
type: Constants["InteractionResponseTypes"]["CHANNEL_MESSAGE_WITH_SOURCE" | "UPDATE_MESSAGE"];
type: Constants["InteractionResponseTypes"]["CHANNEL_MESSAGE_WITH_SOURCE" | "UPDATE_MESSAGE" | "PREMIUM_REQUIRED"];
}
interface InteractionResponseModal {
data: InteractionModalContent;
Expand Down Expand Up @@ -1398,8 +1407,6 @@ declare namespace Dysnomia {
}
interface ButtonBase {
disabled?: boolean;
emoji?: PartialEmoji;
label?: string;
type: Constants["ComponentTypes"]["BUTTON"];
}
interface ChannelSelectMenu extends SelectMenuBase {
Expand Down Expand Up @@ -1483,6 +1490,8 @@ declare namespace Dysnomia {

interface InteractionButton extends ButtonBase {
custom_id: string;
emoji?: PartialEmoji;
label?: string;
style: ButtonStyleNormal;
}
interface MessageActivity {
Expand Down Expand Up @@ -1563,6 +1572,11 @@ declare namespace Dysnomia {
is_finalized: boolean;
answer_counts: PollAnswerCount[];
}
interface PremiumButton extends ButtonBase {
sku_id: string;
style: Constants["ButtonStyles"]["PREMIUM"];

}
interface RoleSubscriptionData {
isRenewal: boolean;
roleSubscriptionListingID: string;
Expand Down Expand Up @@ -1597,6 +1611,8 @@ declare namespace Dysnomia {
}
interface URLButton extends ButtonBase {
style: Constants["ButtonStyles"]["LINK"];
emoji?: PartialEmoji;
label?: string;
url: string;
}

Expand Down Expand Up @@ -2071,6 +2087,7 @@ declare namespace Dysnomia {
SUCCESS: 3;
DANGER: 4;
LINK: 5;
PREMIUM: 6;
};
ChannelTypes: {
GUILD_TEXT: 0;
Expand Down Expand Up @@ -2111,6 +2128,20 @@ declare namespace Dysnomia {
ALL_MESSAGES: 0;
ONLY_MENTIONS: 1;
};
EntitlementOwnerTypes: {
GUILD: 1;
USER: 2;
};
EntitlementTypes: {
PURCHASE: 1;
PREMIUM_SUBSCRIPTION: 2;
DEVELOPER_GIFT: 3;
TEST_MODE_PURCHASE: 4;
FREE_PURCHASE: 5;
USER_GIFT: 6;
PREMIUM_PURCHASE: 7;
APPLICATION_SUBSCRIPTION: 8;
};
ExplicitContentFilterLevels: {
DISABLED: 0;
MEMBERS_WITHOUT_ROLES: 1;
Expand Down Expand Up @@ -2227,6 +2258,8 @@ declare namespace Dysnomia {
UPDATE_MESSAGE: 7;
APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: 8;
MODAL: 9;
/** @deprecated */
PREMIUM_REQUIRED: 10;
};
InteractionTypes: {
PING: 1;
Expand Down Expand Up @@ -2420,6 +2453,17 @@ declare namespace Dysnomia {
RoleFlags: {
IN_PROMPT: 1;
};
SKUFlags: {
AVAILABLE: 4;
GUILD_SUBSCRIPTION: 128;
USER_SUBSCRIPTION: 256;
};
SKUTypes: {
DURABLE: 2;
CONSUMABLE: 3;
SUBSCRIPTION: 5;
SUBSCRIPTION_GROUP: 6;
};
StageInstancePrivacyLevel: {
/** @deprecated */
PUBLIC: 1;
Expand Down Expand Up @@ -2543,6 +2587,32 @@ declare namespace Dysnomia {
}
/* eslint-enable @stylistic/key-spacing, @stylistic/no-multi-spaces */

// Subscriptions
interface CreateTestEntitlementOptions {
skuID: string;
ownerID: string;
ownerType: EntitlementOwnerTypes;
}

interface GetEntitlementsOptions {
after?: number;
before?: number;
excludeEnded?: boolean;
guildID?: string;
limit?: number;
skuIDs?: string[];
userID?: string;
}

interface SKU {
id: string;
application_id: string;
type: SKUTypes;
name: string;
slug: string;
flags: number;
}

// Classes
export class AutocompleteInteraction<T extends PossiblyUncachedInteractionChannel = TextableChannel> extends Interaction {
appPermissions?: Permission;
Expand Down Expand Up @@ -2683,6 +2753,7 @@ declare namespace Dysnomia {
bulkEditGuildCommands(guildID: string, commands: ApplicationCommandStructure[]): Promise<AnyApplicationCommand<true>[]>;
closeVoiceConnection(guildID: string): void;
connect(): Promise<void>;
consumeEntitlement(entitlementID: string): Promise<void>;
createApplicationEmoji(options: EmojiOptions): Promise<Emoji>;
createAutoModerationRule(guildID: string, rule: CreateAutoModerationRuleOptions): Promise<AutoModerationRule>;
createChannel(guildID: string, name: string): Promise<TextChannel>;
Expand Down Expand Up @@ -2757,6 +2828,7 @@ declare namespace Dysnomia {
createRole(guildID: string, options?: RoleOptions, reason?: string): Promise<Role>;
createRole(guildID: string, options?: Role, reason?: string): Promise<Role>;
createStageInstance(channelID: string, options: CreateStageInstanceOptions): Promise<StageInstance>;
createTestEntitlement(options: CreateTestEntitlementOptions): Promise<Entitlement>;
createThread(channelID: string, options: CreateThreadWithoutMessageOptions): Promise<ThreadChannel>;
createThreadWithMessage(channelID: string, messageID: string, options: CreateThreadOptions): Promise<NewsThreadChannel | PublicThreadChannel>;
crosspostMessage(channelID: string, messageID: string): Promise<Message>;
Expand All @@ -2777,6 +2849,7 @@ declare namespace Dysnomia {
deleteMessages(channelID: string, messageIDs: string[], reason?: string): Promise<void>;
deleteRole(guildID: string, roleID: string, reason?: string): Promise<void>;
deleteStageInstance(channelID: string): Promise<void>;
deleteTestEntitlement(entitlementID: string): Promise<void>;
deleteWebhook(webhookID: string, token?: string, reason?: string): Promise<void>;
deleteWebhookMessage(webhookID: string, token: string, messageID: string, threadID?: string): Promise<void>;
disconnect(options: { reconnect?: boolean | "auto" }): void;
Expand Down Expand Up @@ -2863,6 +2936,7 @@ declare namespace Dysnomia {
getCommandPermissions(guildID: string, commandID: string): Promise<GuildApplicationCommandPermissions>;
getCommands<W extends boolean = false>(withLocalizations?: W): Promise<AnyApplicationCommand<W>[]>;
getDMChannel(userID: string): Promise<PrivateChannel>;
getEntitlements(options?: GetEntitlementsOptions): Promise<Entitlement[]>;
getGateway(): Promise<{ url: string }>;
getGuildAuditLog(guildID: string, options?: GetGuildAuditLogOptions): Promise<GuildAuditLog>;
getGuildBan(guildID: string, userID: string): Promise<GuildBan>;
Expand Down Expand Up @@ -2912,6 +2986,7 @@ declare namespace Dysnomia {
getRESTUser(userID: string): Promise<User>;
getRoleConnectionMetadata(): Promise<ApplicationRoleConnectionMetadata[]>;
getSelf(): Promise<ExtendedUser>;
getSKUs(): Promise<SKU[]>;
getStageInstance(channelID: string): Promise<StageInstance>;
getStickerPack(packID: string): Promise<StickerPack>;
getStickerPacks(): Promise<{ sticker_packs: StickerPack[] }>;
Expand Down Expand Up @@ -2983,6 +3058,8 @@ declare namespace Dysnomia {
editMessage(messageID: string, content: string | InteractionContentEdit): Promise<Message>;
editOriginalMessage(content: string | InteractionContentEdit): Promise<Message>;
getOriginalMessage(): Promise<Message>;
/** @deprecated */
requirePremium(): Promise<void>;
}

export class ComponentInteraction<T extends PossiblyUncachedInteractionChannel = TextableChannel> extends Interaction {
Expand All @@ -3006,6 +3083,8 @@ declare namespace Dysnomia {
editOriginalMessage(content: string | InteractionContentEdit): Promise<Message>;
editParent(content: InteractionContentEdit): Promise<void>;
getOriginalMessage(): Promise<Message>;
/** @deprecated */
requirePremium(): Promise<void>;
}

export class DiscordHTTPError extends Error {
Expand All @@ -3030,6 +3109,19 @@ declare namespace Dysnomia {
flattenErrors(errors: HTTPResponse, keyPrefix?: string): string[];
}

export class Entitlement extends Base {
applicationID: string;
consumed: boolean;
deleted: boolean;
endsAt: number | null;
guildID?: string;
skuID: string;
startsAt: number | null;
type: EntitlementTypes;
userID?: string;
consume(): Promise<void>;
}

export class ExtendedUser extends User {
email?: string | null;
mfaEnabled?: boolean;
Expand Down Expand Up @@ -3365,6 +3457,7 @@ declare namespace Dysnomia {
export class Interaction extends Base {
acknowledged: boolean;
applicationID: string;
entitlements: Entitlement[];
id: string;
token: string;
type: number;
Expand Down Expand Up @@ -3526,6 +3619,8 @@ declare namespace Dysnomia {
editOriginalMessage(content: string | InteractionContentEdit): Promise<Message>;
editParent(content: InteractionContentEdit): Promise<void>;
getOriginalMessage(): Promise<Message>;
/** @deprecated */
requirePremium(): Promise<void>;
}

// News channel rate limit is always 0
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Dysnomia.Collection = require("./lib/util/Collection");
Dysnomia.Constants = require("./lib/Constants");
Dysnomia.DiscordHTTPError = require("./lib/errors/DiscordHTTPError");
Dysnomia.DiscordRESTError = require("./lib/errors/DiscordRESTError");
Dysnomia.Entitlement = require("./lib/structures/Entitlement");
Dysnomia.ExtendedUser = require("./lib/structures/ExtendedUser");
Dysnomia.ForumChannel = require("./lib/structures/ForumChannel");
Dysnomia.Guild = require("./lib/structures/Guild");
Expand Down
59 changes: 59 additions & 0 deletions lib/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const VoiceConnectionManager = require("./voice/VoiceConnectionManager");
const AutoModerationRule = require("./structures/AutoModerationRule");
const emitDeprecation = require("./util/emitDeprecation");
const VoiceState = require("./structures/VoiceState");
const Entitlement = require("./structures/Entitlement");

let EventEmitter;
try {
Expand Down Expand Up @@ -445,6 +446,15 @@ class Client extends EventEmitter {
}
}

/**
* Consumes a one-time purchasable entitlement
* @param {String} entitlementID The ID of the entitlement to consume
* @returns {Promise}
*/
consumeEntitlement(entitlementID) {
return this.requestHandler.request("POST", Endpoints.ENTITLEMENT_CONSUME(this.application.id, entitlementID), true);
}

/**
* Create an application emoji object
* @param {Object} options Emoji options
Expand Down Expand Up @@ -920,6 +930,18 @@ class Client extends EventEmitter {
}).then((instance) => new StageInstance(instance, this));
}

/**
* Creates a testing entitlement for a user/guild
* @param {Object} options The options for this request
* @param {String} options.skuID The ID of the SKU to grant the entitlement to
* @param {String} options.ownerID The ID of the guild or user to grant the entitlement to
* @param {Number} options.ownerType The type of the subscription to grant. `1` for a guild subscription, `2` for a user subscription.
* @returns {Promise<Entitlement>}
*/
createTestEntitlement(options) {
return this.requestHandler.request("POST", Endpoints.ENTITLEMENTS(this.application.id), true, options).then((entitlement) => new Entitlement(entitlement, this));
}

/**
* Create a thread in a channel
* @param {String} channelID The ID of the channel
Expand Down Expand Up @@ -1209,6 +1231,15 @@ class Client extends EventEmitter {
return this.requestHandler.request("DELETE", Endpoints.STAGE_INSTANCE(channelID), true);
}

/**
* Deletes a testing entitlement
* @param {String} entitlementID The test entitlement ID to remove
* @returns {Promise}
*/
deleteTestEntitlement(entitlementID) {
return this.requestHandler.request("DELETE", Endpoints.ENTITLEMENT(this.application.id, entitlementID), true);
}

/**
* Delete a webhook
* @param {String} webhookID The ID of the webhook
Expand Down Expand Up @@ -2268,6 +2299,26 @@ class Client extends EventEmitter {
}).then((privateChannel) => new PrivateChannel(privateChannel, this));
}

/**
* Gets a list of entitlements for this application
* @param {Object} [options] THe options for the request
* @param {String} [options.userID] The user ID to look entitlements up for
* @param {Array<String>} [options.skuIDs] The SKU IDs to look entitlements up for
* @param {Number} [options.before] Look entitlements up before this ID
* @param {Number} [options.after] Look entitlements up after this ID
* @param {Number} [options.limit=100] The amount of entitlements to retrieve (1-100)
* @param {String} [options.guildID] The guild ID to look entitlements up for
* @param {Boolean} [options.excludeEnded] Whether to omit already expired entitlements or not
* @returns {Promise<Array<Entitlement>>}
*/
getEntitlements(options = {}) {
options.user_id = options.userID;
options.sku_ids = options.skuIDs;
options.guild_id = options.guildID;
options.exclude_ended = options.excludeEnded;
return this.requestHandler.request("GET", Endpoints.ENTITLEMENTS(this.application.id), true, options).then((entitlements) => entitlements.map((entitlement) => new Entitlement(entitlement, this)));
}

/**
* Get info on connecting to the Discord gateway
* @returns {Promise<Object>} Resolves with an object containing gateway connection info
Expand Down Expand Up @@ -2942,6 +2993,14 @@ class Client extends EventEmitter {
return this.requestHandler.request("GET", Endpoints.USER("@me"), true).then((data) => new ExtendedUser(data, this));
}

/**
* Gets the list of SKUs associated with the current application
* @returns {Promise<Array<Object>>} An array of [SKU objects](https://discord.com/developers/docs/monetization/skus#sku-object)
*/
getSKUs() {
return this.requestHandler.request("GET", Endpoints.SKUS(this.application.id), true);
}

/**
* Get the stage instance associated with a stage channel
* @param {String} channelID The stage channel ID
Expand Down
Loading