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.AnnouncementChannel = require('./structures/AnnouncementChannel.js').AnnouncementChannel;
exports.AnonymousGuild = require('./structures/AnonymousGuild.js').AnonymousGuild;
exports.AuthorizingIntegrationOwners =
require('./structures/AuthorizingIntegrationOwners.js').AuthorizingIntegrationOwners;
exports.Application = require('./structures/interfaces/Application.js').Application;
exports.ApplicationCommand = require('./structures/ApplicationCommand.js').ApplicationCommand;
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 { SelectMenuTypes } = require('../util/Constants.js');
const { PermissionsBitField } = require('../util/PermissionsBitField.js');
const { AuthorizingIntegrationOwners } = require('./AuthorizingIntegrationOwners.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}
*/
this.authorizingIntegrationOwners = data.authorizing_integration_owners;
this.authorizingIntegrationOwners = new AuthorizingIntegrationOwners(
this.client,
data.authorizing_integration_owners,
);
/**
* 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 {InteractionType} type The type of the interaction
* @property {User} user The user that invoked the interaction
* @property {APIAuthorizingIntegrationOwnersMap} authorizingIntegrationOwners
* Ids for installation context(s) related to an interaction
* @property {AuthorizingIntegrationOwners} authorizingIntegrationOwners
* Mapping of integration types that the application was authorized for the related user or guild ids
* @property {?Snowflake} originalResponseMessageId
* Id of the original response message. Present only on follow-up messages
* @property {?Snowflake} interactedMessageId

View File

@@ -35,11 +35,6 @@
* @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
* @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 snakeCase = require('lodash.snakecase');
const { AuthorizingIntegrationOwners } = require('../structures/AuthorizingIntegrationOwners.js');
/**
* Transforms camel-cased keys into snake cased keys
@@ -48,7 +49,10 @@ function _transformAPIMessageInteractionMetadata(client, messageInteractionMetad
id: messageInteractionMetadata.id,
type: messageInteractionMetadata.type,
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,
interactedMessageId: messageInteractionMetadata.interacted_message_id ?? null,
triggeringInteractionMetadata: messageInteractionMetadata.triggering_interaction_metadata

View File

@@ -1927,7 +1927,7 @@ export class BaseInteraction<Cached extends CacheType = CacheType> extends Base
private readonly _cacheType: Cached;
protected constructor(client: Client<true>, data: GatewayInteractionCreateDispatchData);
public applicationId: Snowflake;
public authorizingIntegrationOwners: APIAuthorizingIntegrationOwnersMap;
public authorizingIntegrationOwners: AuthorizingIntegrationOwners;
public get channel(): CacheTypeReducer<
Cached,
GuildTextBasedChannel | null,
@@ -2265,6 +2265,22 @@ export class Message<InGuild extends boolean = boolean> extends Base {
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 {
private constructor(data: APIAttachment);
private readonly attachment: BufferResolvable | Stream;
@@ -6642,7 +6658,7 @@ export interface MessageChannelComponentCollectorOptions<
}
export interface MessageInteractionMetadata {
authorizingIntegrationOwners: APIAuthorizingIntegrationOwnersMap;
authorizingIntegrationOwners: AuthorizingIntegrationOwners;
id: Snowflake;
interactedMessageId: Snowflake | null;
originalResponseMessageId: Snowflake | null;

View File

@@ -24,6 +24,7 @@ import {
ApplicationCommandOptionType,
ApplicationCommandPermissionType,
ApplicationCommandType,
ApplicationIntegrationType,
AuditLogEvent,
ButtonStyle,
ChannelType,
@@ -200,6 +201,7 @@ import type {
VoiceChannel,
Invite,
GuildInvite,
AuthorizingIntegrationOwners,
} from './index.js';
import {
ActionRowBuilder,
@@ -3044,3 +3046,12 @@ await textChannel.send({
],
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]);
}