Skip to content

Commit

Permalink
feat: better support for applications and their resources (#328)
Browse files Browse the repository at this point in the history
* feat: stuff

* fix: :b

* chore: apply formatting

* fix: lol

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
socram03 and github-actions[bot] authored Feb 14, 2025
1 parent 7849ad1 commit a677ec7
Show file tree
Hide file tree
Showing 19 changed files with 379 additions and 105 deletions.
9 changes: 9 additions & 0 deletions src/api/Routes/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import type {
RESTGetAPIEntitlementsResult,
RESTGetAPIGuildApplicationCommandsPermissionsResult,
RESTGetAPISKUsResult,
RESTGetCurrentApplicationResult,
RESTPatchAPIApplicationCommandJSONBody,
RESTPatchAPIApplicationCommandResult,
RESTPatchAPIApplicationEmojiJSONBody,
RESTPatchAPIApplicationEmojiResult,
RESTPatchAPIApplicationGuildCommandJSONBody,
RESTPatchAPIApplicationGuildCommandResult,
RESTPatchCurrentApplicationJSONBody,
RESTPatchCurrentApplicationResult,
RESTPostAPIApplicationCommandsJSONBody,
RESTPostAPIApplicationCommandsResult,
RESTPostAPIApplicationEmojiJSONBody,
Expand All @@ -36,11 +39,17 @@ import type {
RESTPutAPIApplicationRoleConnectionMetadataJSONBody,
RESTPutAPIApplicationRoleConnectionMetadataResult,
RESTPutAPIGuildApplicationCommandsPermissionsResult,
RestGetAPIApplicationActivityInstanceResult,
} from '../../types';
import type { RestArguments, RestArgumentsNoBody } from '../api';

export interface ApplicationRoutes {
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) => {
commands: {
get(
Expand Down
36 changes: 24 additions & 12 deletions src/cache/resources/emojis.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,56 @@
import type { CacheFrom, ReturnCache } from '../..';
import type { ApplicationEmojiStructure, CacheFrom, ReturnCache } from '../..';
import { type GuildEmojiStructure, Transformers } from '../../client/transformers';
import { fakePromise } from '../../common';
import type { APIEmoji } from '../../types';
import type { APIApplicationEmoji, APIEmoji } from '../../types';
import { GuildRelatedResource } from './default/guild-related';

export class Emojis extends GuildRelatedResource<any, APIEmoji> {
export class Emojis extends GuildRelatedResource<any, APIEmoji | APIApplicationEmoji> {
namespace = 'emoji';

//@ts-expect-error
filter(data: APIEmoji, id: string, guild_id: string, from: CacheFrom) {
return true;
}

override get(id: string): ReturnCache<GuildEmojiStructure | undefined> {
return fakePromise(super.get(id)).then(rawEmoji =>
rawEmoji ? Transformers.GuildEmoji(this.client, rawEmoji, rawEmoji.guild_id) : undefined,
);
override get(id: string): ReturnCache<GuildEmojiStructure | ApplicationEmojiStructure | undefined> {
return fakePromise(super.get(id)).then(rawEmoji => {
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> {
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 =>
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);
}),
);
}

bulkRaw(ids: string[]): ReturnCache<(APIEmoji & { id: string; guild_id: string })[]> {
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 =>
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);
}
}
8 changes: 8 additions & 0 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export class Client<Ready extends boolean = boolean> extends BaseClient {
super(options);
}

get applicationId() {
return this.me?.application.id ?? super.applicationId;
}

set applicationId(id: string) {
super.applicationId = id;
}

setServices({
gateway,
...rest
Expand Down
15 changes: 15 additions & 0 deletions src/client/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { type CustomStructures, OptionResolver } from '../commands';
import type { StructStates } from '../common/';
import {
AnonymousGuild,
ApplicationEmoji,
AutoModerationRule,
BaseChannel,
BaseGuildChannel,
CategoryChannel,
ClientUser,
DMChannel,
DirectoryChannel,
Emoji,
Entitlement,
ForumChannel,
Guild,
Expand All @@ -32,10 +34,13 @@ import {
Webhook,
WebhookMessage,
} from '../structures';
import { Application } from '../structures/Application';
import type { ChannelType } from '../types';

export type PollStructure = InferCustomStructure<Poll, 'Poll'>;
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 AutoModerationRuleStructure = InferCustomStructure<AutoModerationRule, 'AutoModerationRule'>;
export type BaseChannelStructure = InferCustomStructure<BaseChannel<ChannelType>, 'BaseChannel'>;
Expand All @@ -52,6 +57,7 @@ export type NewsChannelStructure = InferCustomStructure<NewsChannel, 'NewsChanne
export type DirectoryChannelStructure = InferCustomStructure<DirectoryChannel, 'DirectoryChannel'>;
export type GuildStructure<State extends StructStates = 'api'> = InferCustomStructure<Guild<State>, 'Guild'>;
export type GuildBanStructure = InferCustomStructure<GuildBan, 'GuildBan'>;
export type EmojiStructure = InferCustomStructure<Emoji, 'Emoji'>;
export type GuildEmojiStructure = InferCustomStructure<GuildEmoji, 'GuildEmoji'>;
export type GuildMemberStructure = InferCustomStructure<GuildMember, 'GuildMember'>;
export type InteractionGuildMemberStructure = InferCustomStructure<InteractionGuildMember, 'InteractionGuildMember'>;
Expand All @@ -67,6 +73,12 @@ export type OptionResolverStructure = InferCustomStructure<OptionResolver, 'Opti
export type EntitlementStructure = InferCustomStructure<Entitlement, 'Entitlement'>;

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 {
return new AnonymousGuild(...args);
},
Expand Down Expand Up @@ -118,6 +130,9 @@ export const Transformers = {
GuildBan(...args: ConstructorParameters<typeof GuildBan>): GuildBanStructure {
return new GuildBan(...args);
},
Emoji(...args: ConstructorParameters<typeof Emoji>): EmojiStructure {
return new Emoji(...args);
},
GuildEmoji(...args: ConstructorParameters<typeof GuildEmoji>): GuildEmojiStructure {
return new GuildEmoji(...args);
},
Expand Down
8 changes: 8 additions & 0 deletions src/client/workerclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ export class WorkerClient<Ready extends boolean = boolean> extends BaseClient {
return acc / this.shards.size;
}

get applicationId() {
return this.me?.application.id ?? super.applicationId;
}

set applicationId(id: string) {
super.applicationId = id;
}

setServices(rest: ServicesOptions) {
super.setServices(rest);
if (this.options.postMessage && rest.cache?.adapter instanceof WorkerAdapter) {
Expand Down
103 changes: 77 additions & 26 deletions src/common/shorters/application.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,141 @@
import { type EntitlementStructure, Transformers } from '../../client';
import { CacheFrom, resolveImage } from '../..';
import { type ApplicationEmojiStructure, type EntitlementStructure, Transformers } from '../../client';
import type {
APIEntitlement,
RESTGetAPIEntitlementsQuery,
RESTPostAPIApplicationEmojiJSONBody,
RESTPatchAPIApplicationEmojiJSONBody,
RESTPatchCurrentApplicationJSONBody,
RESTPostAPIEntitlementBody,
} from '../../types';
import type { ApplicationEmojiResolvable } from '../types/resolvables';
import { BaseShorter } from './base';

export class ApplicationShorter extends BaseShorter {
/**
* Lists the emojis for the application.
* @param applicationId The ID of the application.
* @returns The emojis.
*/
listEmojis(applicationId: string) {
return this.client.proxy.applications(applicationId).emojis.get();
async listEmojis(force = false): Promise<ApplicationEmojiStructure[]> {
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.
* @param applicationId The ID of the application.
* @param emojiId The ID of the emoji.
* @returns The emoji.
*/
getEmoji(applicationId: string, emojiId: string) {
return this.client.proxy.applications(applicationId).emojis(emojiId).get();
async getEmoji(emojiId: string, force = false): Promise<ApplicationEmojiStructure> {
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.
* @param applicationId The ID of the application.
* @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.
* @returns The created emoji.
*/
createEmoji(applicationId: string, body: RESTPostAPIApplicationEmojiJSONBody) {
return this.client.proxy.applications(applicationId).emojis.post({ body });
async createEmoji(raw: ApplicationEmojiResolvable) {
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.
* @param applicationId The ID of the application.
* @param [query] The query parameters.
*/
listEntitlements(applicationId: string, query?: RESTGetAPIEntitlementsQuery): Promise<EntitlementStructure[]> {
listEntitlements(query?: RESTGetAPIEntitlementsQuery): Promise<EntitlementStructure[]> {
return this.client.proxy
.applications(applicationId)
.applications(this.client.applicationId)
.entitlements.get({ query })
.then(et => et.map(e => Transformers.Entitlement(this.client, e)));
}

/**
* Consumes an entitlement for the application.
* @param applicationId The ID of the application.
* @param entitlementId The ID of the entitlement.
*/
consumeEntitlement(applicationId: string, entitlementId: string) {
return this.client.proxy.applications(applicationId).entitlements(entitlementId).consume.post();
consumeEntitlement(entitlementId: string) {
return this.client.proxy.applications(this.client.applicationId).entitlements(entitlementId).consume.post();
}

/**
* Creates a test entitlement for the application.
* @param applicationId The ID of the application.
* @param body The body of the request.
*/
createTestEntitlement(applicationId: string, body: RESTPostAPIEntitlementBody): Promise<EntitlementStructure> {
createTestEntitlement(body: RESTPostAPIEntitlementBody): Promise<EntitlementStructure> {
return this.client.proxy
.applications(applicationId)
.applications(this.client.applicationId)
.entitlements.post({ body })
.then(et => Transformers.Entitlement(this.client, et as APIEntitlement));
}

/**
* Deletes a test entitlement for the application.
* @param applicationId The ID of the application.
* @param entitlementId The ID of the entitlement.
*/
deleteTestEntitlement(applicationId: string, entitlementId: string) {
return this.client.proxy.applications(applicationId).entitlements(entitlementId).delete();
deleteTestEntitlement(entitlementId: string) {
return this.client.proxy.applications(this.client.applicationId).entitlements(entitlementId).delete();
}

/**
* Lists the SKUs for the application.
* @param applicationId The ID of the application.
* @returns The SKUs.
*/
listSKUs(applicationId: string) {
return this.client.proxy.applications(applicationId).skus.get();
listSKUs() {
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();
}
}
11 changes: 6 additions & 5 deletions src/common/shorters/bans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ export class BanShorter extends BaseShorter {
*/
async bulkCreate(guildId: string, body: RESTPostAPIGuildBulkBanJSONBody, reason?: string) {
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;
}

Expand Down Expand Up @@ -70,12 +72,11 @@ export class BanShorter extends BaseShorter {
* @returns A Promise that resolves to an array of listed bans.
*/
async list(guildId: string, query?: RESTGetAPIGuildBansQuery, force = false): Promise<GuildBanStructure[]> {
let bans: APIBan[] | GuildBanStructure[];
if (!force) {
bans = (await this.client.cache.bans?.values(guildId)) ?? [];
if (bans.length) return bans;
const bans = await this.client.cache.bans?.values(guildId);
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,
});
await this.client.cache.bans?.set(
Expand Down
Loading

0 comments on commit a677ec7

Please sign in to comment.