feat: better support for applications and their resources (#328)

* feat: stuff

* fix: :b

* chore: apply formatting

* fix: lol

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Marcos Susaña 2025-02-14 18:15:39 -04:00 committed by GitHub
parent 7849ad1235
commit a677ec795c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 392 additions and 118 deletions

View File

@ -14,12 +14,15 @@ import type {
RESTGetAPIEntitlementsResult, RESTGetAPIEntitlementsResult,
RESTGetAPIGuildApplicationCommandsPermissionsResult, RESTGetAPIGuildApplicationCommandsPermissionsResult,
RESTGetAPISKUsResult, RESTGetAPISKUsResult,
RESTGetCurrentApplicationResult,
RESTPatchAPIApplicationCommandJSONBody, RESTPatchAPIApplicationCommandJSONBody,
RESTPatchAPIApplicationCommandResult, RESTPatchAPIApplicationCommandResult,
RESTPatchAPIApplicationEmojiJSONBody, RESTPatchAPIApplicationEmojiJSONBody,
RESTPatchAPIApplicationEmojiResult, RESTPatchAPIApplicationEmojiResult,
RESTPatchAPIApplicationGuildCommandJSONBody, RESTPatchAPIApplicationGuildCommandJSONBody,
RESTPatchAPIApplicationGuildCommandResult, RESTPatchAPIApplicationGuildCommandResult,
RESTPatchCurrentApplicationJSONBody,
RESTPatchCurrentApplicationResult,
RESTPostAPIApplicationCommandsJSONBody, RESTPostAPIApplicationCommandsJSONBody,
RESTPostAPIApplicationCommandsResult, RESTPostAPIApplicationCommandsResult,
RESTPostAPIApplicationEmojiJSONBody, RESTPostAPIApplicationEmojiJSONBody,
@ -36,11 +39,17 @@ import type {
RESTPutAPIApplicationRoleConnectionMetadataJSONBody, RESTPutAPIApplicationRoleConnectionMetadataJSONBody,
RESTPutAPIApplicationRoleConnectionMetadataResult, RESTPutAPIApplicationRoleConnectionMetadataResult,
RESTPutAPIGuildApplicationCommandsPermissionsResult, RESTPutAPIGuildApplicationCommandsPermissionsResult,
RestGetAPIApplicationActivityInstanceResult,
} from '../../types'; } from '../../types';
import type { RestArguments, RestArgumentsNoBody } from '../api'; import type { RestArguments, RestArgumentsNoBody } from '../api';
export interface ApplicationRoutes { export interface ApplicationRoutes {
applications: (id: string) => { applications: (id: string) => {
get(args?: RestArgumentsNoBody): Promise<RESTGetCurrentApplicationResult>;
patch(args: RestArguments<RESTPatchCurrentApplicationJSONBody>): Promise<RESTPatchCurrentApplicationResult>;
'activity-instances': (id: string) => {
get(args?: RestArgumentsNoBody): Promise<RestGetAPIApplicationActivityInstanceResult>;
};
guilds: (id: string) => { guilds: (id: string) => {
commands: { commands: {
get( get(

View File

@ -1,10 +1,10 @@
import type { CacheFrom, ReturnCache } from '../..'; import type { ApplicationEmojiStructure, CacheFrom, ReturnCache } from '../..';
import { type GuildEmojiStructure, Transformers } from '../../client/transformers'; import { type GuildEmojiStructure, Transformers } from '../../client/transformers';
import { fakePromise } from '../../common'; import { fakePromise } from '../../common';
import type { APIEmoji } from '../../types'; import type { APIApplicationEmoji, APIEmoji } from '../../types';
import { GuildRelatedResource } from './default/guild-related'; import { GuildRelatedResource } from './default/guild-related';
export class Emojis extends GuildRelatedResource<any, APIEmoji> { export class Emojis extends GuildRelatedResource<any, APIEmoji | APIApplicationEmoji> {
namespace = 'emoji'; namespace = 'emoji';
//@ts-expect-error //@ts-expect-error
@ -12,19 +12,25 @@ export class Emojis extends GuildRelatedResource<any, APIEmoji> {
return true; return true;
} }
override get(id: string): ReturnCache<GuildEmojiStructure | undefined> { override get(id: string): ReturnCache<GuildEmojiStructure | ApplicationEmojiStructure | undefined> {
return fakePromise(super.get(id)).then(rawEmoji => return fakePromise(super.get(id)).then(rawEmoji => {
rawEmoji ? Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id) : undefined, if (!rawEmoji) return undefined;
); if (rawEmoji.guild_id === this.client.applicationId) return Transformers.ApplicationEmoji(this.client, rawEmoji);
return Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id);
});
} }
raw(id: string): ReturnCache<APIEmoji | undefined> { raw(id: string): ReturnCache<APIEmoji | undefined> {
return super.get(id); return super.get(id);
} }
override bulk(ids: string[]): ReturnCache<GuildEmojiStructure[]> { override bulk(ids: string[]): ReturnCache<(GuildEmojiStructure | ApplicationEmojiStructure)[]> {
return fakePromise(super.bulk(ids) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis => return fakePromise(super.bulk(ids) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis =>
emojis.map(rawEmoji => Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id)), emojis.map(rawEmoji => {
if (rawEmoji.guild_id === this.client.applicationId)
return Transformers.ApplicationEmoji(this.client, rawEmoji as APIApplicationEmoji);
return Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id);
}),
); );
} }
@ -32,13 +38,19 @@ export class Emojis extends GuildRelatedResource<any, APIEmoji> {
return super.bulk(ids); return super.bulk(ids);
} }
override values(guild: string): ReturnCache<GuildEmojiStructure[]> { override values(guild: string): ReturnCache<(GuildEmojiStructure | ApplicationEmojiStructure)[]> {
return fakePromise(super.values(guild) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis => return fakePromise(super.values(guild) as (APIEmoji & { id: string; guild_id: string })[]).then(emojis =>
emojis.map(rawEmoji => Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id)), emojis.map(rawEmoji => {
if (rawEmoji.guild_id === this.client.applicationId)
return Transformers.ApplicationEmoji(this.client, rawEmoji as APIApplicationEmoji);
return Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id);
}),
); );
} }
valuesRaw(guild: string): ReturnCache<(APIEmoji & { id: string; guild_id: string })[]> { valuesRaw(
guild: string,
): ReturnCache<(((APIEmoji & { id: string }) | APIApplicationEmoji) & { guild_id: string })[]> {
return super.values(guild); return super.values(guild);
} }
} }

View File

@ -36,6 +36,14 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
super(options); super(options);
} }
get applicationId() {
return this.me?.application.id ?? super.applicationId;
}
set applicationId(id: string) {
super.applicationId = id;
}
setServices({ setServices({
gateway, gateway,
...rest ...rest

View File

@ -2,6 +2,7 @@ import { type CustomStructures, OptionResolver } from '../commands';
import type { StructStates } from '../common/'; import type { StructStates } from '../common/';
import { import {
AnonymousGuild, AnonymousGuild,
ApplicationEmoji,
AutoModerationRule, AutoModerationRule,
BaseChannel, BaseChannel,
BaseGuildChannel, BaseGuildChannel,
@ -9,6 +10,7 @@ import {
ClientUser, ClientUser,
DMChannel, DMChannel,
DirectoryChannel, DirectoryChannel,
Emoji,
Entitlement, Entitlement,
ForumChannel, ForumChannel,
Guild, Guild,
@ -32,10 +34,13 @@ import {
Webhook, Webhook,
WebhookMessage, WebhookMessage,
} from '../structures'; } from '../structures';
import { Application } from '../structures/Application';
import type { ChannelType } from '../types'; import type { ChannelType } from '../types';
export type PollStructure = InferCustomStructure<Poll, 'Poll'>; export type PollStructure = InferCustomStructure<Poll, 'Poll'>;
export type ClientUserStructure = InferCustomStructure<ClientUser, 'ClientUser'>; export type ClientUserStructure = InferCustomStructure<ClientUser, 'ClientUser'>;
export type ApplicationStructure = InferCustomStructure<Application, 'Application'>;
export type ApplicationEmojiStructure = InferCustomStructure<ApplicationEmoji, 'ApplicationEmoji'>;
export type AnonymousGuildStructure = InferCustomStructure<AnonymousGuild, 'AnonymousGuild'>; export type AnonymousGuildStructure = InferCustomStructure<AnonymousGuild, 'AnonymousGuild'>;
export type AutoModerationRuleStructure = InferCustomStructure<AutoModerationRule, 'AutoModerationRule'>; export type AutoModerationRuleStructure = InferCustomStructure<AutoModerationRule, 'AutoModerationRule'>;
export type BaseChannelStructure = InferCustomStructure<BaseChannel<ChannelType>, 'BaseChannel'>; export type BaseChannelStructure = InferCustomStructure<BaseChannel<ChannelType>, 'BaseChannel'>;
@ -52,6 +57,7 @@ export type NewsChannelStructure = InferCustomStructure<NewsChannel, 'NewsChanne
export type DirectoryChannelStructure = InferCustomStructure<DirectoryChannel, 'DirectoryChannel'>; export type DirectoryChannelStructure = InferCustomStructure<DirectoryChannel, 'DirectoryChannel'>;
export type GuildStructure<State extends StructStates = 'api'> = InferCustomStructure<Guild<State>, 'Guild'>; export type GuildStructure<State extends StructStates = 'api'> = InferCustomStructure<Guild<State>, 'Guild'>;
export type GuildBanStructure = InferCustomStructure<GuildBan, 'GuildBan'>; export type GuildBanStructure = InferCustomStructure<GuildBan, 'GuildBan'>;
export type EmojiStructure = InferCustomStructure<Emoji, 'Emoji'>;
export type GuildEmojiStructure = InferCustomStructure<GuildEmoji, 'GuildEmoji'>; export type GuildEmojiStructure = InferCustomStructure<GuildEmoji, 'GuildEmoji'>;
export type GuildMemberStructure = InferCustomStructure<GuildMember, 'GuildMember'>; export type GuildMemberStructure = InferCustomStructure<GuildMember, 'GuildMember'>;
export type InteractionGuildMemberStructure = InferCustomStructure<InteractionGuildMember, 'InteractionGuildMember'>; export type InteractionGuildMemberStructure = InferCustomStructure<InteractionGuildMember, 'InteractionGuildMember'>;
@ -67,6 +73,12 @@ export type OptionResolverStructure = InferCustomStructure<OptionResolver, 'Opti
export type EntitlementStructure = InferCustomStructure<Entitlement, 'Entitlement'>; export type EntitlementStructure = InferCustomStructure<Entitlement, 'Entitlement'>;
export const Transformers = { export const Transformers = {
Application(...args: ConstructorParameters<typeof Application>): ApplicationStructure {
return new Application(...args);
},
ApplicationEmoji(...args: ConstructorParameters<typeof ApplicationEmoji>): ApplicationEmojiStructure {
return new ApplicationEmoji(...args);
},
AnonymousGuild(...args: ConstructorParameters<typeof AnonymousGuild>): AnonymousGuildStructure { AnonymousGuild(...args: ConstructorParameters<typeof AnonymousGuild>): AnonymousGuildStructure {
return new AnonymousGuild(...args); return new AnonymousGuild(...args);
}, },
@ -118,6 +130,9 @@ export const Transformers = {
GuildBan(...args: ConstructorParameters<typeof GuildBan>): GuildBanStructure { GuildBan(...args: ConstructorParameters<typeof GuildBan>): GuildBanStructure {
return new GuildBan(...args); return new GuildBan(...args);
}, },
Emoji(...args: ConstructorParameters<typeof Emoji>): EmojiStructure {
return new Emoji(...args);
},
GuildEmoji(...args: ConstructorParameters<typeof GuildEmoji>): GuildEmojiStructure { GuildEmoji(...args: ConstructorParameters<typeof GuildEmoji>): GuildEmojiStructure {
return new GuildEmoji(...args); return new GuildEmoji(...args);
}, },

View File

@ -96,6 +96,14 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
return acc / this.shards.size; return acc / this.shards.size;
} }
get applicationId() {
return this.me?.application.id ?? super.applicationId;
}
set applicationId(id: string) {
super.applicationId = id;
}
setServices(rest: ServicesOptions) { setServices(rest: ServicesOptions) {
super.setServices(rest); super.setServices(rest);
if (this.options.postMessage && rest.cache?.adapter instanceof WorkerAdapter) { if (this.options.postMessage && rest.cache?.adapter instanceof WorkerAdapter) {

View File

@ -1,90 +1,141 @@
import { type EntitlementStructure, Transformers } from '../../client'; import { CacheFrom, resolveImage } from '../..';
import { type ApplicationEmojiStructure, type EntitlementStructure, Transformers } from '../../client';
import type { import type {
APIEntitlement, APIEntitlement,
RESTGetAPIEntitlementsQuery, RESTGetAPIEntitlementsQuery,
RESTPostAPIApplicationEmojiJSONBody, RESTPatchAPIApplicationEmojiJSONBody,
RESTPatchCurrentApplicationJSONBody,
RESTPostAPIEntitlementBody, RESTPostAPIEntitlementBody,
} from '../../types'; } from '../../types';
import type { ApplicationEmojiResolvable } from '../types/resolvables';
import { BaseShorter } from './base'; import { BaseShorter } from './base';
export class ApplicationShorter extends BaseShorter { export class ApplicationShorter extends BaseShorter {
/** /**
* Lists the emojis for the application. * Lists the emojis for the application.
* @param applicationId The ID of the application.
* @returns The emojis. * @returns The emojis.
*/ */
listEmojis(applicationId: string) { async listEmojis(force = false): Promise<ApplicationEmojiStructure[]> {
return this.client.proxy.applications(applicationId).emojis.get(); if (!force) {
const cached = (await this.client.cache.emojis?.values(this.client.applicationId)) as
| ApplicationEmojiStructure[]
| undefined;
if (cached?.length) return cached;
}
const data = await this.client.proxy.applications(this.client.applicationId).emojis.get();
this.client.cache.emojis?.set(
CacheFrom.Rest,
data.items.map(e => [e.id, e]),
this.client.applicationId,
);
return data.items.map(e => Transformers.ApplicationEmoji(this.client, e));
} }
/** /**
* Gets an emoji for the application. * Gets an emoji for the application.
* @param applicationId The ID of the application.
* @param emojiId The ID of the emoji. * @param emojiId The ID of the emoji.
* @returns The emoji. * @returns The emoji.
*/ */
getEmoji(applicationId: string, emojiId: string) { async getEmoji(emojiId: string, force = false): Promise<ApplicationEmojiStructure> {
return this.client.proxy.applications(applicationId).emojis(emojiId).get(); if (!force) {
const cached = (await this.client.cache.emojis?.get(emojiId)) as ApplicationEmojiStructure;
if (cached) return cached;
}
const data = await this.client.proxy.applications(this.client.applicationId).emojis(emojiId).get();
this.client.cache.emojis?.set(CacheFrom.Rest, data.id, this.client.applicationId, data);
return Transformers.ApplicationEmoji(this.client, data);
} }
/** /**
* Creates a new emoji for the application. * Creates a new emoji for the application.
* @param applicationId The ID of the application.
* @param body.name The name of the emoji. * @param body.name The name of the emoji.
* @param body.image The [image data string](https://discord.com/developers/docs/reference#image-data) of the emoji. * @param body.image The [image data string](https://discord.com/developers/docs/reference#image-data) of the emoji.
* @returns The created emoji. * @returns The created emoji.
*/ */
createEmoji(applicationId: string, body: RESTPostAPIApplicationEmojiJSONBody) { async createEmoji(raw: ApplicationEmojiResolvable) {
return this.client.proxy.applications(applicationId).emojis.post({ body }); const data = await this.client.proxy
.applications(this.client.applicationId)
.emojis.post({ body: { ...raw, image: await resolveImage(raw.image) } });
this.client.cache.emojis?.set(CacheFrom.Rest, data.id, this.client.applicationId, data);
return Transformers.ApplicationEmoji(this.client, data);
}
/**
* Edits an emoji for the application.
* @param emojiId The ID of the emoji.
* @param body.name The new name of the emoji.
* @returns The edited emoji.
*/
async editEmoji(emojiId: string, body: RESTPatchAPIApplicationEmojiJSONBody) {
const data = await this.client.proxy.applications(this.client.applicationId).emojis(emojiId).patch({ body });
this.client.cache.emojis?.patch(CacheFrom.Rest, emojiId, this.client.applicationId, data);
return Transformers.ApplicationEmoji(this.client, data);
}
/**
* Deletes an emoji for the application.
* @param emojiId The ID of the emoji.
*/
deleteEmoji(emojiId: string) {
return this.client.proxy.applications(this.client.applicationId).emojis(emojiId).delete();
} }
/** /**
* Lists the entitlements for the application. * Lists the entitlements for the application.
* @param applicationId The ID of the application.
* @param [query] The query parameters. * @param [query] The query parameters.
*/ */
listEntitlements(applicationId: string, query?: RESTGetAPIEntitlementsQuery): Promise<EntitlementStructure[]> { listEntitlements(query?: RESTGetAPIEntitlementsQuery): Promise<EntitlementStructure[]> {
return this.client.proxy return this.client.proxy
.applications(applicationId) .applications(this.client.applicationId)
.entitlements.get({ query }) .entitlements.get({ query })
.then(et => et.map(e => Transformers.Entitlement(this.client, e))); .then(et => et.map(e => Transformers.Entitlement(this.client, e)));
} }
/** /**
* Consumes an entitlement for the application. * Consumes an entitlement for the application.
* @param applicationId The ID of the application.
* @param entitlementId The ID of the entitlement. * @param entitlementId The ID of the entitlement.
*/ */
consumeEntitlement(applicationId: string, entitlementId: string) { consumeEntitlement(entitlementId: string) {
return this.client.proxy.applications(applicationId).entitlements(entitlementId).consume.post(); return this.client.proxy.applications(this.client.applicationId).entitlements(entitlementId).consume.post();
} }
/** /**
* Creates a test entitlement for the application. * Creates a test entitlement for the application.
* @param applicationId The ID of the application.
* @param body The body of the request. * @param body The body of the request.
*/ */
createTestEntitlement(applicationId: string, body: RESTPostAPIEntitlementBody): Promise<EntitlementStructure> { createTestEntitlement(body: RESTPostAPIEntitlementBody): Promise<EntitlementStructure> {
return this.client.proxy return this.client.proxy
.applications(applicationId) .applications(this.client.applicationId)
.entitlements.post({ body }) .entitlements.post({ body })
.then(et => Transformers.Entitlement(this.client, et as APIEntitlement)); .then(et => Transformers.Entitlement(this.client, et as APIEntitlement));
} }
/** /**
* Deletes a test entitlement for the application. * Deletes a test entitlement for the application.
* @param applicationId The ID of the application.
* @param entitlementId The ID of the entitlement. * @param entitlementId The ID of the entitlement.
*/ */
deleteTestEntitlement(applicationId: string, entitlementId: string) { deleteTestEntitlement(entitlementId: string) {
return this.client.proxy.applications(applicationId).entitlements(entitlementId).delete(); return this.client.proxy.applications(this.client.applicationId).entitlements(entitlementId).delete();
} }
/** /**
* Lists the SKUs for the application. * Lists the SKUs for the application.
* @param applicationId The ID of the application.
* @returns The SKUs. * @returns The SKUs.
*/ */
listSKUs(applicationId: string) { listSKUs() {
return this.client.proxy.applications(applicationId).skus.get(); return this.client.proxy.applications(this.client.applicationId).skus.get();
}
async fetch() {
const data = await this.client.proxy.applications('@me').get();
return Transformers.Application(this.client, data);
}
async edit(body: RESTPatchCurrentApplicationJSONBody) {
const data = await this.client.proxy.applications('@me').patch({ body });
return Transformers.Application(this.client, data);
}
getActivityInstance(instanceId: string) {
return this.client.proxy.applications(this.client.applicationId)['activity-instances'](instanceId).get();
} }
} }

View File

@ -17,7 +17,9 @@ export class BanShorter extends BaseShorter {
*/ */
async bulkCreate(guildId: string, body: RESTPostAPIGuildBulkBanJSONBody, reason?: string) { async bulkCreate(guildId: string, body: RESTPostAPIGuildBulkBanJSONBody, reason?: string) {
const bans = await this.client.proxy.guilds(guildId)['bulk-bans'].post({ reason, body }); const bans = await this.client.proxy.guilds(guildId)['bulk-bans'].post({ reason, body });
for (const id of bans.banned_users) this.client.cache.members?.removeIfNI('GuildModeration', id, guildId); await Promise.all(
bans.banned_users.map(id => this.client.cache.members?.removeIfNI('GuildModeration', id, guildId)),
);
return bans; return bans;
} }
@ -70,12 +72,11 @@ export class BanShorter extends BaseShorter {
* @returns A Promise that resolves to an array of listed bans. * @returns A Promise that resolves to an array of listed bans.
*/ */
async list(guildId: string, query?: RESTGetAPIGuildBansQuery, force = false): Promise<GuildBanStructure[]> { async list(guildId: string, query?: RESTGetAPIGuildBansQuery, force = false): Promise<GuildBanStructure[]> {
let bans: APIBan[] | GuildBanStructure[];
if (!force) { if (!force) {
bans = (await this.client.cache.bans?.values(guildId)) ?? []; const bans = await this.client.cache.bans?.values(guildId);
if (bans.length) return bans; if (bans?.length) return bans;
} }
bans = await this.client.proxy.guilds(guildId).bans.get({ const bans = await this.client.proxy.guilds(guildId).bans.get({
query, query,
}); });
await this.client.cache.bans?.set( await this.client.cache.bans?.set(

View File

@ -1,5 +1,4 @@
import { CacheFrom } from '../../cache'; import { CacheFrom } from '../../cache';
import type { Channels } from '../../cache/resources/channels';
import type { Overwrites } from '../../cache/resources/overwrites'; import type { Overwrites } from '../../cache/resources/overwrites';
import { type MessageStructure, type ThreadChannelStructure, Transformers } from '../../client/transformers'; import { type MessageStructure, type ThreadChannelStructure, Transformers } from '../../client/transformers';
import { type AllChannels, BaseChannel, type GuildMember, type GuildRole, channelFrom } from '../../structures'; import { type AllChannels, BaseChannel, type GuildMember, type GuildRole, channelFrom } from '../../structures';
@ -29,9 +28,8 @@ export class ChannelShorter extends BaseShorter {
} }
async raw(id: string, force?: boolean): Promise<APIChannel> { async raw(id: string, force?: boolean): Promise<APIChannel> {
let channel: APIChannel | ReturnType<Channels['raw']>;
if (!force) { if (!force) {
channel = await this.client.cache.channels?.raw(id); const channel = await this.client.cache.channels?.raw(id);
const overwrites = await this.client.cache.overwrites?.raw(id); const overwrites = await this.client.cache.overwrites?.raw(id);
if (channel) { if (channel) {
if (overwrites) (channel as APIGuildChannel<ChannelType>).permission_overwrites = overwrites; if (overwrites) (channel as APIGuildChannel<ChannelType>).permission_overwrites = overwrites;
@ -39,7 +37,7 @@ export class ChannelShorter extends BaseShorter {
} }
} }
channel = await this.client.proxy.channels(id).get(); const channel = await this.client.proxy.channels(id).get();
await this.client.cache.channels?.patch( await this.client.cache.channels?.patch(
CacheFrom.Rest, CacheFrom.Rest,
id, id,
@ -198,11 +196,11 @@ export class ChannelShorter extends BaseShorter {
if (checkAdmin && role.permissions.has([PermissionFlagsBits.Administrator])) { if (checkAdmin && role.permissions.has([PermissionFlagsBits.Administrator])) {
return new PermissionsBitField(PermissionsBitField.All); return new PermissionsBitField(PermissionsBitField.All);
} }
const channelOverwrites = (await this.client.cache.overwrites?.get(channelId)) ?? []; const permissions = new PermissionsBitField(role.permissions.bits);
const channelOverwrites = await this.client.cache.overwrites?.get(channelId);
if (!channelOverwrites) return permissions;
const everyoneOverwrites = channelOverwrites.find(x => x.id === role.guildId); const everyoneOverwrites = channelOverwrites.find(x => x.id === role.guildId);
const roleOverwrites = channelOverwrites.find(x => x.id === role.id); const roleOverwrites = channelOverwrites.find(x => x.id === role.id);
const permissions = new PermissionsBitField(role.permissions.bits);
permissions.remove([everyoneOverwrites?.deny.bits ?? 0n]); permissions.remove([everyoneOverwrites?.deny.bits ?? 0n]);
permissions.add([everyoneOverwrites?.allow.bits ?? 0n]); permissions.add([everyoneOverwrites?.allow.bits ?? 0n]);

View File

@ -14,14 +14,11 @@ export class EmojiShorter extends BaseShorter {
* @returns A Promise that resolves to an array of emojis. * @returns A Promise that resolves to an array of emojis.
*/ */
async list(guildId: string, force = false): Promise<GuildEmojiStructure[]> { async list(guildId: string, force = false): Promise<GuildEmojiStructure[]> {
let emojis: APIEmoji[] | GuildEmojiStructure[];
if (!force) { if (!force) {
emojis = (await this.client.cache.emojis?.values(guildId)) ?? []; const cached = (await this.client.cache.emojis?.values(guildId)) as GuildEmojiStructure[] | undefined;
if (emojis.length) { if (cached?.length) return cached;
return emojis;
}
} }
emojis = await this.client.proxy.guilds(guildId).emojis.get(); const emojis = await this.client.proxy.guilds(guildId).emojis.get();
await this.client.cache.emojis?.set( await this.client.cache.emojis?.set(
CacheFrom.Rest, CacheFrom.Rest,
emojis.map<[string, APIEmoji]>(x => [x.id!, x]), emojis.map<[string, APIEmoji]>(x => [x.id!, x]),
@ -58,12 +55,11 @@ export class EmojiShorter extends BaseShorter {
* @returns A Promise that resolves to the fetched emoji. * @returns A Promise that resolves to the fetched emoji.
*/ */
async fetch(guildId: string, emojiId: string, force = false): Promise<GuildEmojiStructure> { async fetch(guildId: string, emojiId: string, force = false): Promise<GuildEmojiStructure> {
let emoji: APIEmoji | GuildEmojiStructure | undefined;
if (!force) { if (!force) {
emoji = await this.client.cache.emojis?.get(emojiId); const emoji = (await this.client.cache.emojis?.get(emojiId)) as GuildEmojiStructure | undefined;
if (emoji) return emoji; if (emoji) return emoji;
} }
emoji = await this.client.proxy.guilds(guildId).emojis(emojiId).get(); const emoji = await this.client.proxy.guilds(guildId).emojis(emojiId).get();
return Transformers.GuildEmoji(this.client, emoji, guildId); return Transformers.GuildEmoji(this.client, emoji, guildId);
} }

View File

@ -1,9 +1,14 @@
import type { EmbedColors } from '..'; import type { EmbedColors, OmitInsert } from '..';
import type { Attachment, AttachmentDataType, AttachmentResolvable } from '../../builders'; import type { Attachment, AttachmentDataType, AttachmentResolvable } from '../../builders';
import type { GuildMember } from '../../structures'; import type { GuildMember } from '../../structures';
import type { APIGuildMember, APIPartialEmoji } from '../../types'; import type { APIGuildMember, APIPartialEmoji, RESTPostAPIApplicationEmojiJSONBody } from '../../types';
export type EmojiResolvable = string | Partial<APIPartialEmoji> | `<${string | undefined}:${string}:${string}>`; export type EmojiResolvable = string | Partial<APIPartialEmoji> | `<${string | undefined}:${string}:${string}>`;
export type GuildMemberResolvable = string | Partial<GuildMember> | APIGuildMember; export type GuildMemberResolvable = string | Partial<GuildMember> | APIGuildMember;
export type ColorResolvable = `#${string}` | number | keyof typeof EmbedColors | 'Random' | [number, number, number]; export type ColorResolvable = `#${string}` | number | keyof typeof EmbedColors | 'Random' | [number, number, number];
export type ImageResolvable = { data: AttachmentResolvable; type: AttachmentDataType } | Attachment; export type ImageResolvable = { data: AttachmentResolvable; type: AttachmentDataType } | Attachment;
export type ApplicationEmojiResolvable = OmitInsert<
RESTPostAPIApplicationEmojiJSONBody,
'image',
{ image: ImageResolvable }
>;

View File

@ -0,0 +1,61 @@
import type { UsingClient } from '..';
import type { ApplicationEmojiResolvable, ObjectToLower } from '../common';
import type {
APIApplication,
RESTPatchAPIApplicationEmojiJSONBody,
RESTPatchCurrentApplicationJSONBody,
} from '../types';
import { DiscordBase } from './extra/DiscordBase';
export interface Application extends ObjectToLower<APIApplication> {}
/**
* Due to current limitations, this is exclusively for the current application.
*/
export class Application extends DiscordBase<APIApplication> {
constructor(client: UsingClient, data: APIApplication) {
// override any id for safety
data.id = client.applicationId;
super(client, data);
}
/**
* Fetch the current application.
*/
fetch() {
return this.client.applications.fetch();
}
/**
* Edit the current application.
*/
edit(data: RESTPatchCurrentApplicationJSONBody) {
return this.client.applications.edit(data);
}
/**
* Get an activity instance.
*/
getActivityInstance(instanceId: string) {
return this.client.applications.getActivityInstance(instanceId);
}
emojis = {
/**
* Get an application emoji.
*/
fetch: (id: string) => this.client.applications.getEmoji(id),
/**
* Get the application emojis.
*/
list: () => this.client.applications.listEmojis(),
/**
* Create an application emoji.
*/
create: (data: ApplicationEmojiResolvable) => this.client.applications.createEmoji(data),
/**
* Edit an application emoji.
*/
edit: (emojiId: string, body: RESTPatchAPIApplicationEmojiJSONBody) =>
this.client.applications.editEmoji(emojiId, body),
};
}

View File

@ -1,25 +1,54 @@
import { type ClientUserStructure, Transformers } from '../client'; import type { ClientUserStructure } from '../client';
import type { UsingClient } from '../commands'; import type { UsingClient } from '../commands';
import type { GatewayReadyDispatchData, RESTPatchAPICurrentUserJSONBody } from '../types'; import type { GatewayReadyDispatchData, RESTPatchAPICurrentUserJSONBody } from '../types';
import { User } from './User'; import { User } from './User';
/**
* Represents a client user that extends the base User class.
* This class is used to interact with the authenticated user.
*
* @extends User
*/
export class ClientUser extends User { export class ClientUser extends User {
/**
* Indicates if the user is a bot.
* @type {true}
*/
bot = true; bot = true;
/**
* Creates an instance of ClientUser.
*
* @param client - The client instance used for making API requests.
* @param data - The user data received from the gateway.
* @param application - The application data received from the gateway.
*/
constructor( constructor(
client: UsingClient, client: UsingClient,
data: GatewayReadyDispatchData['user'], data: GatewayReadyDispatchData['user'],
public application: GatewayReadyDispatchData['application'], readonly application: GatewayReadyDispatchData['application'],
) { ) {
super(client, data); super(client, data);
} }
/**
* Fetches the current user data from the API.
*
* @returns A promise that resolves to the ClientUserStructure.
*/
async fetch(): Promise<ClientUserStructure> { async fetch(): Promise<ClientUserStructure> {
const data = await this.api.users('@me').get(); const data = await this.api.users('@me').get();
return Transformers.ClientUser(this.client, data, this.application); return this.__patchThis(data);
} }
/**
* Edits the current user data.
*
* @param body - The data to update the user with.
* @returns A promise that resolves to the updated ClientUserStructure.
*/
async edit(body: RESTPatchAPICurrentUserJSONBody): Promise<ClientUserStructure> { async edit(body: RESTPatchAPICurrentUserJSONBody): Promise<ClientUserStructure> {
const data = await this.api.users('@me').patch({ body }); const data = await this.api.users('@me').patch({ body });
return Transformers.ClientUser(this.client, data, this.application); return this.__patchThis(data);
} }
} }

View File

@ -2,45 +2,23 @@ import type { BaseCDNUrlOptions } from '../api';
import type { ReturnCache } from '../cache'; import type { ReturnCache } from '../cache';
import type { GuildEmojiStructure, GuildStructure } from '../client'; import type { GuildEmojiStructure, GuildStructure } from '../client';
import type { UsingClient } from '../commands'; import type { UsingClient } from '../commands';
import { type EmojiShorter, Formatter, type MethodContext, type ObjectToLower } from '../common'; import { type EmojiShorter, Formatter, type MethodContext, type ObjectToLower, type When } from '../common';
import type { APIEmoji, RESTPatchAPIChannelJSONBody, RESTPatchAPIGuildEmojiJSONBody } from '../types'; import type {
APIApplicationEmoji,
APIEmoji,
RESTPatchAPIApplicationEmojiJSONBody,
RESTPatchAPIGuildEmojiJSONBody,
} from '../types';
import { User } from './User';
import { DiscordBase } from './extra/DiscordBase'; import { DiscordBase } from './extra/DiscordBase';
export interface GuildEmoji extends DiscordBase, ObjectToLower<Omit<APIEmoji, 'id'>> {} export interface Emoji extends DiscordBase, ObjectToLower<Omit<APIEmoji, 'id' | 'user'>> {}
export class GuildEmoji extends DiscordBase { export class Emoji<T extends boolean = false> extends DiscordBase {
constructor( user: When<T, User>;
client: UsingClient, constructor(client: UsingClient, data: APIEmoji) {
data: APIEmoji,
readonly guildId: string,
) {
super(client, { ...data, id: data.id! }); super(client, { ...data, id: data.id! });
} this.user = (data.user && new User(client, data.user)) as never;
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'>>;
guild(mode: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'flow'): unknown {
switch (mode) {
case 'cache':
return (
this.client.cache.guilds?.get(this.guildId) ||
(this.client.cache.adapter.isAsync ? (Promise.resolve() as any) : undefined)
);
default:
return this.client.guilds.fetch(this.guildId, mode === 'rest');
}
}
edit(body: RESTPatchAPIChannelJSONBody, reason?: string): Promise<GuildEmojiStructure> {
return this.client.emojis.edit(this.guildId, this.id, body, reason);
}
delete(reason?: string) {
return this.client.emojis.delete(this.guildId, this.id, reason);
}
fetch(force = false): Promise<GuildEmojiStructure> {
return this.client.emojis.fetch(this.guildId, this.id, force);
} }
url(options?: BaseCDNUrlOptions) { url(options?: BaseCDNUrlOptions) {
@ -58,6 +36,44 @@ export class GuildEmoji extends DiscordBase {
animated: !!this.animated, animated: !!this.animated,
}; };
} }
}
export interface GuildEmoji extends Emoji {}
export class GuildEmoji extends Emoji {
constructor(
client: UsingClient,
data: APIEmoji,
readonly guildId: string,
) {
super(client, data);
}
guild(mode?: 'rest' | 'flow'): Promise<GuildStructure<'cached' | 'api'>>;
guild(mode: 'cache'): ReturnCache<GuildStructure<'cached'> | undefined>;
guild(mode: 'cache' | 'rest' | 'flow' = 'flow'): unknown {
switch (mode) {
case 'cache':
return (
this.client.cache.guilds?.get(this.guildId) ||
(this.client.cache.adapter.isAsync ? (Promise.resolve() as any) : undefined)
);
default:
return this.client.guilds.fetch(this.guildId, mode === 'rest');
}
}
edit(body: RESTPatchAPIGuildEmojiJSONBody, reason?: string): Promise<GuildEmojiStructure> {
return this.client.emojis.edit(this.guildId, this.id, body, reason);
}
delete(reason?: string) {
return this.client.emojis.delete(this.guildId, this.id, reason);
}
fetch(force = false): Promise<GuildEmojiStructure> {
return this.client.emojis.fetch(this.guildId, this.id, force);
}
static methods({ client, guildId }: MethodContext<{ guildId: string }>) { static methods({ client, guildId }: MethodContext<{ guildId: string }>) {
return { return {
@ -71,3 +87,21 @@ export class GuildEmoji extends DiscordBase {
}; };
} }
} }
export class ApplicationEmoji extends Emoji<true> {
constructor(client: UsingClient, data: APIApplicationEmoji) {
super(client, data);
}
fetch() {
return this.client.applications.getEmoji(this.id);
}
edit(body: RESTPatchAPIApplicationEmojiJSONBody) {
return this.client.applications.editEmoji(this.id, body);
}
delete() {
return this.client.applications.deleteEmoji(this.id);
}
}

View File

@ -14,6 +14,6 @@ export class Entitlement extends DiscordBase<APIEntitlement> {
} }
consume() { consume() {
return this.client.applications.consumeEntitlement(this.applicationId, this.id); return this.client.applications.consumeEntitlement(this.id);
} }
} }

View File

@ -3,8 +3,8 @@ import type { UsingClient } from '../commands';
import type { ObjectToLower, StructPropState, StructStates, ToClass } from '../common/types/util'; import type { ObjectToLower, StructPropState, StructStates, ToClass } from '../common/types/util';
import type { APIGuild, APIPartialGuild, GatewayGuildCreateDispatchData, RESTPatchAPIGuildJSONBody } from '../types'; import type { APIGuild, APIPartialGuild, GatewayGuildCreateDispatchData, RESTPatchAPIGuildJSONBody } from '../types';
import { AutoModerationRule } from './AutoModerationRule'; import { AutoModerationRule } from './AutoModerationRule';
import { GuildEmoji } from './Emoji';
import { GuildBan } from './GuildBan'; import { GuildBan } from './GuildBan';
import { GuildEmoji } from './GuildEmoji';
import { GuildMember } from './GuildMember'; import { GuildMember } from './GuildMember';
import { GuildRole } from './GuildRole'; import { GuildRole } from './GuildRole';
import { GuildTemplate } from './GuildTemplate'; import { GuildTemplate } from './GuildTemplate';

View File

@ -46,14 +46,20 @@ export class BaseGuild extends DiscordBase<APIPartialGuild> {
} }
} }
/**
* Leave the guild.
*/
leave() {
return this.client.guilds.leave(this.id);
}
/** /**
* iconURL gets the current guild icon. * iconURL gets the current guild icon.
* @link https://discord.com/developers/docs/reference#image-formatting * @link https://discord.com/developers/docs/reference#image-formatting
*/ */
iconURL(options?: ImageOptions): string | undefined { iconURL(options?: ImageOptions): string | undefined {
if (!this.icon) { if (!this.icon) return;
return;
}
return this.rest.cdn.icons(this.id).get(this.icon, options); return this.rest.cdn.icons(this.id).get(this.icon, options);
} }
@ -64,9 +70,8 @@ export class BaseGuild extends DiscordBase<APIPartialGuild> {
* @returns Splash url or void. * @returns Splash url or void.
*/ */
splashURL(options?: ImageOptions): string | undefined { splashURL(options?: ImageOptions): string | undefined {
if (!this.splash) { if (!this.splash) return;
return;
}
return this.rest.cdn['discovery-splashes'](this.id).get(this.splash, options); return this.rest.cdn['discovery-splashes'](this.id).get(this.splash, options);
} }
@ -77,9 +82,8 @@ export class BaseGuild extends DiscordBase<APIPartialGuild> {
* @returns Banner url or void * @returns Banner url or void
*/ */
bannerURL(options?: ImageOptions): string | undefined { bannerURL(options?: ImageOptions): string | undefined {
if (!this.banner) { if (!this.banner) return;
return;
}
return this.rest.cdn.banners(this.id).get(this.banner, options); return this.rest.cdn.banners(this.id).get(this.banner, options);
} }

View File

@ -2,7 +2,7 @@ export * from './AnonymousGuild';
export * from './AutoModerationRule'; export * from './AutoModerationRule';
export * from './ClientUser'; export * from './ClientUser';
export * from './Guild'; export * from './Guild';
export * from './GuildEmoji'; export * from './Emoji';
export * from './GuildMember'; export * from './GuildMember';
export * from './GuildPreview'; export * from './GuildPreview';
export * from './GuildRole'; export * from './GuildRole';

View File

@ -2,8 +2,9 @@
* Types extracted from https://discord.com/developers/docs/resources/application * Types extracted from https://discord.com/developers/docs/resources/application
*/ */
import type { LocalizationMap } from '.'; import type { APIEmoji, LocalizationMap } from '.';
import type { Permissions, Snowflake } from '..'; import type { Permissions, Snowflake } from '..';
import type { MakeRequired } from '../../common';
import type { APIPartialGuild } from './guild'; import type { APIPartialGuild } from './guild';
import type { ApplicationIntegrationType } from './interactions'; import type { ApplicationIntegrationType } from './interactions';
import type { OAuth2Scopes } from './oauth2'; import type { OAuth2Scopes } from './oauth2';
@ -292,3 +293,35 @@ export enum ApplicationRoleConnectionMetadataType {
*/ */
BooleanNotEqual, BooleanNotEqual,
} }
/**
* https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-instance-object
*/
export interface APIActivityInstance {
application_id: string;
instance_id: string;
launch_id: string;
/** Location the instance is runnning in */
location: APIActivityLocation;
users: string[];
}
/**
* https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-location-object
*/
export interface APIActivityLocation {
id: string;
/** Enum describing kind of location */
kind: ActivityLocation;
channel_id: string;
guild_id?: string | null;
}
/**
* https://discord.com/developers/docs/resources/application#get-application-activity-instance-activity-location-kind-enum
*/
export enum ActivityLocation {
GuildChannel = 'gc',
PrivateChannel = 'pc',
}
export type APIApplicationEmoji = MakeRequired<APIEmoji, 'id' | 'user'>;

View File

@ -1,4 +1,9 @@
import type { APIApplication, APIApplicationRoleConnectionMetadata, APIEmoji } from '../payloads'; import type {
APIActivityInstance,
APIApplication,
APIApplicationEmoji,
APIApplicationRoleConnectionMetadata,
} from '../payloads';
import type { Nullable, StrictPartial } from '../utils'; import type { Nullable, StrictPartial } from '../utils';
import type { RESTPatchAPIGuildEmojiJSONBody, RESTPostAPIGuildEmojiJSONBody } from './emoji'; import type { RESTPatchAPIGuildEmojiJSONBody, RESTPostAPIGuildEmojiJSONBody } from './emoji';
@ -49,13 +54,13 @@ export type RESTPatchCurrentApplicationResult = APIApplication;
* https://discord.com/developers/docs/resources/emoji#list-application-emojis * https://discord.com/developers/docs/resources/emoji#list-application-emojis
*/ */
export interface RESTGetAPIApplicationEmojisResult { export interface RESTGetAPIApplicationEmojisResult {
emojis: APIEmoji[]; items: APIApplicationEmoji[];
} }
/** /**
* https://discord.com/developers/docs/resources/emoji#get-application-emoji * https://discord.com/developers/docs/resources/emoji#get-application-emoji
*/ */
export type RESTGetAPIApplicationEmojiResult = APIEmoji; export type RESTGetAPIApplicationEmojiResult = APIApplicationEmoji;
/** /**
* https://discord.com/developers/docs/resources/emoji#create-application-emoji-json-params * https://discord.com/developers/docs/resources/emoji#create-application-emoji-json-params
@ -65,7 +70,7 @@ export type RESTPostAPIApplicationEmojiJSONBody = Pick<RESTPostAPIGuildEmojiJSON
/** /**
* https://discord.com/developers/docs/resources/emoji#create-application-emoji * https://discord.com/developers/docs/resources/emoji#create-application-emoji
*/ */
export type RESTPostAPIApplicationEmojiResult = APIEmoji; export type RESTPostAPIApplicationEmojiResult = APIApplicationEmoji;
/** /**
* https://discord.com/developers/docs/resources/emoji#modify-application-emoji * https://discord.com/developers/docs/resources/emoji#modify-application-emoji
@ -75,9 +80,14 @@ export type RESTPatchAPIApplicationEmojiJSONBody = Pick<RESTPatchAPIGuildEmojiJS
/** /**
* https://discord.com/developers/docs/resources/emoji#modify-application-emoji * https://discord.com/developers/docs/resources/emoji#modify-application-emoji
*/ */
export type RESTPatchAPIApplicationEmojiResult = APIEmoji; export type RESTPatchAPIApplicationEmojiResult = APIApplicationEmoji;
/** /**
* https://discord.com/developers/docs/resources/emoji#delete-application-emoji * https://discord.com/developers/docs/resources/emoji#delete-application-emoji
*/ */
export type RESTDeleteAPIApplicationEmojiResult = never; export type RESTDeleteAPIApplicationEmojiResult = never;
/**
* https://discord.com/developers/docs/resources/application#get-application-activity-instance
*/
export type RestGetAPIApplicationActivityInstanceResult = APIActivityInstance | undefined;