feat: proper authorizing integration owners structure (#11366)

* feat: proper authorizing integration owners structure

chore: fix ci

chore: fix types

chore: fix types

chore: nits

chore: tests

chore: requested changes

chore: drop it from apitypes

chore: requested 2

chore: rofl

chore: docs

* chore: docs

* chore: docs

* Apply suggestions from code review

Co-authored-by: Almeida <github@almeidx.dev>
Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

---------

Co-authored-by: Almeida <github@almeidx.dev>
Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Vlad Frangu
2025-12-17 20:55:05 +02:00
committed by GitHub
parent 5f93fbd0f2
commit 51b5d38342
8 changed files with 109 additions and 13 deletions

View File

@@ -105,6 +105,8 @@ exports.ActionRow = require('./structures/ActionRow.js').ActionRow;
exports.Activity = require('./structures/Presence.js').Activity; exports.Activity = require('./structures/Presence.js').Activity;
exports.AnnouncementChannel = require('./structures/AnnouncementChannel.js').AnnouncementChannel; exports.AnnouncementChannel = require('./structures/AnnouncementChannel.js').AnnouncementChannel;
exports.AnonymousGuild = require('./structures/AnonymousGuild.js').AnonymousGuild; exports.AnonymousGuild = require('./structures/AnonymousGuild.js').AnonymousGuild;
exports.AuthorizingIntegrationOwners =
require('./structures/AuthorizingIntegrationOwners.js').AuthorizingIntegrationOwners;
exports.Application = require('./structures/interfaces/Application.js').Application; exports.Application = require('./structures/interfaces/Application.js').Application;
exports.ApplicationCommand = require('./structures/ApplicationCommand.js').ApplicationCommand; exports.ApplicationCommand = require('./structures/ApplicationCommand.js').ApplicationCommand;
exports.ApplicationEmoji = require('./structures/ApplicationEmoji.js').ApplicationEmoji; exports.ApplicationEmoji = require('./structures/ApplicationEmoji.js').ApplicationEmoji;

View File

@@ -0,0 +1,64 @@
'use strict';
const { ApplicationIntegrationType } = require('discord-api-types/v10');
const { Base } = require('./Base.js');
/**
* Represents the owners of an authorizing integration.
*
* @extends {Base}
*/
class AuthorizingIntegrationOwners extends Base {
constructor(client, data) {
super(client);
Object.defineProperty(this, 'data', { value: data });
// Support accessing authorizingIntegrationOwners[ApplicationIntegrationType.GuildInstall] or similar, + forward compatibility if new installation types get added
for (const value of Object.values(ApplicationIntegrationType)) {
if (typeof value !== 'number') {
continue;
}
Object.defineProperty(this, value, { value: this.data[value] });
}
/**
* The id of the guild where the integration is installed, if applicable.
*
* @type {?Snowflake}
*/
this.guildId = this.data[ApplicationIntegrationType.GuildInstall] ?? null;
/**
* The id of the user on which the integration is installed, if applicable.
*
* @type {?Snowflake}
*/
this.userId = this.data[ApplicationIntegrationType.UserInstall] ?? null;
}
/**
* The guild where the integration is installed, if applicable.
*
* @type {?Guild}
*/
get guild() {
return (this.guildId && this.client.guilds.cache.get(this.guildId)) ?? null;
}
/**
* The user on which the integration is installed, if applicable.
*
* @type {?User}
*/
get user() {
return (this.userId && this.client.users.cache.get(this.userId)) ?? null;
}
toJSON() {
return this.data;
}
}
exports.AuthorizingIntegrationOwners = AuthorizingIntegrationOwners;

View File

@@ -5,6 +5,7 @@ const { DiscordSnowflake } = require('@sapphire/snowflake');
const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10'); const { InteractionType, ApplicationCommandType, ComponentType } = require('discord-api-types/v10');
const { SelectMenuTypes } = require('../util/Constants.js'); const { SelectMenuTypes } = require('../util/Constants.js');
const { PermissionsBitField } = require('../util/PermissionsBitField.js'); const { PermissionsBitField } = require('../util/PermissionsBitField.js');
const { AuthorizingIntegrationOwners } = require('./AuthorizingIntegrationOwners.js');
const { Base } = require('./Base.js'); const { Base } = require('./Base.js');
/** /**
@@ -123,12 +124,15 @@ class BaseInteraction extends Base {
); );
/** /**
* Mapping of installation contexts that the interaction was authorized for the related user or guild ids * Mapping of integration types that the application was authorized for the related user or guild ids
* *
* @type {APIAuthorizingIntegrationOwnersMap} * @type {AuthorizingIntegrationOwners}
* @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-authorizing-integration-owners-object} * @see {@link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-authorizing-integration-owners-object}
*/ */
this.authorizingIntegrationOwners = data.authorizing_integration_owners; this.authorizingIntegrationOwners = new AuthorizingIntegrationOwners(
this.client,
data.authorizing_integration_owners,
);
/** /**
* Context where the interaction was triggered from * Context where the interaction was triggered from

View File

@@ -414,8 +414,8 @@ class Message extends Base {
* @property {Snowflake} id The interaction's id * @property {Snowflake} id The interaction's id
* @property {InteractionType} type The type of the interaction * @property {InteractionType} type The type of the interaction
* @property {User} user The user that invoked the interaction * @property {User} user The user that invoked the interaction
* @property {APIAuthorizingIntegrationOwnersMap} authorizingIntegrationOwners * @property {AuthorizingIntegrationOwners} authorizingIntegrationOwners
* Ids for installation context(s) related to an interaction * Mapping of integration types that the application was authorized for the related user or guild ids
* @property {?Snowflake} originalResponseMessageId * @property {?Snowflake} originalResponseMessageId
* Id of the original response message. Present only on follow-up messages * Id of the original response message. Present only on follow-up messages
* @property {?Snowflake} interactedMessageId * @property {?Snowflake} interactedMessageId

View File

@@ -35,11 +35,6 @@
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationIntegrationType} * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationIntegrationType}
*/ */
/**
* @external APIAuthorizingIntegrationOwnersMap
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10#APIAuthorizingIntegrationOwnersMap}
*/
/** /**
* @external APIAutoModerationAction * @external APIAutoModerationAction
* @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIAutoModerationAction} * @see {@link https://discord-api-types.dev/api/discord-api-types-v10/interface/APIAutoModerationAction}

View File

@@ -2,6 +2,7 @@
const { isJSONEncodable } = require('@discordjs/util'); const { isJSONEncodable } = require('@discordjs/util');
const snakeCase = require('lodash.snakecase'); const snakeCase = require('lodash.snakecase');
const { AuthorizingIntegrationOwners } = require('../structures/AuthorizingIntegrationOwners.js');
/** /**
* Transforms camel-cased keys into snake cased keys * Transforms camel-cased keys into snake cased keys
@@ -48,7 +49,10 @@ function _transformAPIMessageInteractionMetadata(client, messageInteractionMetad
id: messageInteractionMetadata.id, id: messageInteractionMetadata.id,
type: messageInteractionMetadata.type, type: messageInteractionMetadata.type,
user: client.users._add(messageInteractionMetadata.user), user: client.users._add(messageInteractionMetadata.user),
authorizingIntegrationOwners: messageInteractionMetadata.authorizing_integration_owners, authorizingIntegrationOwners: new AuthorizingIntegrationOwners(
client,
messageInteractionMetadata.authorizing_integration_owners,
),
originalResponseMessageId: messageInteractionMetadata.original_response_message_id ?? null, originalResponseMessageId: messageInteractionMetadata.original_response_message_id ?? null,
interactedMessageId: messageInteractionMetadata.interacted_message_id ?? null, interactedMessageId: messageInteractionMetadata.interacted_message_id ?? null,
triggeringInteractionMetadata: messageInteractionMetadata.triggering_interaction_metadata triggeringInteractionMetadata: messageInteractionMetadata.triggering_interaction_metadata

View File

@@ -1927,7 +1927,7 @@ export class BaseInteraction<Cached extends CacheType = CacheType> extends Base
private readonly _cacheType: Cached; private readonly _cacheType: Cached;
protected constructor(client: Client<true>, data: GatewayInteractionCreateDispatchData); protected constructor(client: Client<true>, data: GatewayInteractionCreateDispatchData);
public applicationId: Snowflake; public applicationId: Snowflake;
public authorizingIntegrationOwners: APIAuthorizingIntegrationOwnersMap; public authorizingIntegrationOwners: AuthorizingIntegrationOwners;
public get channel(): CacheTypeReducer< public get channel(): CacheTypeReducer<
Cached, Cached,
GuildTextBasedChannel | null, GuildTextBasedChannel | null,
@@ -2265,6 +2265,22 @@ export class Message<InGuild extends boolean = boolean> extends Base {
public inGuild(): this is Message<true>; public inGuild(): this is Message<true>;
} }
export class AuthorizingIntegrationOwners extends Base {
private constructor(client: Client<true>, data: APIAuthorizingIntegrationOwnersMap);
private readonly data: APIAuthorizingIntegrationOwnersMap;
// Getters from types
public readonly [ApplicationIntegrationType.GuildInstall]?: Snowflake;
public readonly [ApplicationIntegrationType.UserInstall]?: Snowflake;
public readonly guildId: Snowflake | null;
public get guild(): Guild | null;
public readonly userId: Snowflake | null;
public get user(): User | null;
public toJSON(): APIAuthorizingIntegrationOwnersMap;
}
export class Attachment { export class Attachment {
private constructor(data: APIAttachment); private constructor(data: APIAttachment);
private readonly attachment: BufferResolvable | Stream; private readonly attachment: BufferResolvable | Stream;
@@ -6642,7 +6658,7 @@ export interface MessageChannelComponentCollectorOptions<
} }
export interface MessageInteractionMetadata { export interface MessageInteractionMetadata {
authorizingIntegrationOwners: APIAuthorizingIntegrationOwnersMap; authorizingIntegrationOwners: AuthorizingIntegrationOwners;
id: Snowflake; id: Snowflake;
interactedMessageId: Snowflake | null; interactedMessageId: Snowflake | null;
originalResponseMessageId: Snowflake | null; originalResponseMessageId: Snowflake | null;

View File

@@ -24,6 +24,7 @@ import {
ApplicationCommandOptionType, ApplicationCommandOptionType,
ApplicationCommandPermissionType, ApplicationCommandPermissionType,
ApplicationCommandType, ApplicationCommandType,
ApplicationIntegrationType,
AuditLogEvent, AuditLogEvent,
ButtonStyle, ButtonStyle,
ChannelType, ChannelType,
@@ -200,6 +201,7 @@ import type {
VoiceChannel, VoiceChannel,
Invite, Invite,
GuildInvite, GuildInvite,
AuthorizingIntegrationOwners,
} from './index.js'; } from './index.js';
import { import {
ActionRowBuilder, ActionRowBuilder,
@@ -3044,3 +3046,12 @@ await textChannel.send({
], ],
flags: MessageFlags.IsVoiceMessage, flags: MessageFlags.IsVoiceMessage,
}); });
declare const authorizingIntegrationOwners: AuthorizingIntegrationOwners;
{
expectType<Snowflake | null>(authorizingIntegrationOwners.guildId);
expectType<Guild | null>(authorizingIntegrationOwners.guild);
expectType<Snowflake | null>(authorizingIntegrationOwners.userId);
expectType<User | null>(authorizingIntegrationOwners.user);
expectType<Snowflake | undefined>(authorizingIntegrationOwners[ApplicationIntegrationType.GuildInstall]);
}