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
2026-07-14 21:29:48 +01:00
committed by Jiralite
co-authored by Almeida Qjuh kodiakhq[bot]
parent 3b738eef58
commit b6db455b99
8 changed files with 111 additions and 13 deletions
+1
View File
@@ -111,6 +111,7 @@ exports.AutocompleteInteraction = require('./structures/AutocompleteInteraction'
exports.AutoModerationActionExecution = require('./structures/AutoModerationActionExecution');
exports.AutoModerationRule = require('./structures/AutoModerationRule');
exports.Base = require('./structures/Base');
exports.AuthorizingIntegrationOwners = require('./structures/AuthorizingIntegrationOwners');
exports.BaseGuild = require('./structures/BaseGuild');
exports.BaseGuildEmoji = require('./structures/BaseGuildEmoji');
exports.BaseGuildTextChannel = require('./structures/BaseGuildTextChannel');
@@ -0,0 +1,66 @@
'use strict';
const { ApplicationIntegrationType } = require('discord-api-types/v10');
const Base = require('./Base');
/**
* 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 values by integration type, such as
// authorizingIntegrationOwners[ApplicationIntegrationType.GuildInstall],
// and preserve forward compatibility if new installation types are 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;
}
}
module.exports = AuthorizingIntegrationOwners;
@@ -4,6 +4,7 @@ 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 AuthorizingIntegrationOwners = require('./AuthorizingIntegrationOwners');
const Base = require('./Base');
const { SelectMenuTypes } = require('../util/Constants');
const PermissionsBitField = require('../util/PermissionsBitField');
@@ -110,11 +111,15 @@ class BaseInteraction extends Base {
/* eslint-disable max-len */
/**
* Mapping of installation contexts that the interaction was authorized for the related user or guild ids
* @type {APIAuthorizingIntegrationOwnersMap}
* Mapping of integration types that the application was authorized for the related user or guild ids
*
* @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,
);
/* eslint-enable max-len */
/**
@@ -394,8 +394,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
-5
View File
@@ -40,11 +40,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}
+5 -1
View File
@@ -3,6 +3,7 @@
const { isJSONEncodable } = require('@discordjs/util');
const snakeCase = require('lodash.snakecase');
const { resolvePartialEmoji } = require('./Util');
const AuthorizingIntegrationOwners = require('../structures/AuthorizingIntegrationOwners');
/**
* Transforms camel-cased keys into snake cased keys
@@ -53,7 +54,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
+18 -2
View File
@@ -2175,7 +2175,7 @@ export class BaseInteraction<Cached extends CacheType = CacheType> extends Base
private readonly _cacheType: Cached;
protected constructor(client: Client<true>, data: RawInteractionData);
public applicationId: Snowflake;
public authorizingIntegrationOwners: APIAuthorizingIntegrationOwnersMap;
public authorizingIntegrationOwners: AuthorizingIntegrationOwners;
public get channel(): CacheTypeReducer<
Cached,
GuildTextBasedChannel | null,
@@ -2543,6 +2543,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 AttachmentBuilder {
public constructor(attachment: BufferResolvable | Stream, data?: AttachmentData);
public attachment: BufferResolvable | Stream;
@@ -7353,7 +7369,7 @@ export interface MessageInteractionMetadata {
id: Snowflake;
type: InteractionType;
user: User;
authorizingIntegrationOwners: APIAuthorizingIntegrationOwnersMap;
authorizingIntegrationOwners: AuthorizingIntegrationOwners;
originalResponseMessageId: Snowflake | null;
interactedMessageId: Snowflake | null;
triggeringInteractionMetadata: MessageInteractionMetadata | null;
@@ -12,6 +12,7 @@ import {
ApplicationCommandOptionType,
ComponentType,
ApplicationCommandPermissionType,
ApplicationIntegrationType,
ChannelType,
InteractionType,
GatewayIntentBits,
@@ -203,6 +204,7 @@ import {
SKU,
UserSelectMenuBuilder,
VoiceServerUpdateData,
AuthorizingIntegrationOwners,
RoleSelectMenuBuilder,
ChannelSelectMenuBuilder,
MentionableSelectMenuBuilder,
@@ -3071,3 +3073,12 @@ await guildScheduledEventManager.edit(snowflake, { recurrenceRule: null });
byMonth: [GuildScheduledEventRecurrenceRuleMonth.May],
});
}
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]);
}