new select menus (#122)

This commit is contained in:
MARCROCK22 2022-11-05 11:40:28 -04:00 committed by GitHub
parent 6a5fce0f58
commit f6b745c726
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 139 additions and 109 deletions

View File

@ -79,6 +79,14 @@ export enum MessageComponentTypes {
SelectMenu = 3, SelectMenu = 3,
/** A text input object */ /** A text input object */
InputText = 4, InputText = 4,
/** A select menu for picking from users */
UserSelect = 5,
/** A select menu for picking from roles */
RoleSelect = 6,
/** A select menu for picking from users and roles */
MentionableSelect = 7,
/** A select menu for picking from channels */
ChannelSelect = 8
} }
export enum TextStyles { export enum TextStyles {
@ -1260,20 +1268,20 @@ export type MakeRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
// THANK YOU YUI FOR SHARING THIS! // THANK YOU YUI FOR SHARING THIS!
export type CamelCase<S extends string> = export type CamelCase<S extends string> =
S extends `${infer P1}_${infer P2}${infer P3}` S extends `${infer P1}_${infer P2}${infer P3}`
? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}` ? `${Lowercase<P1>}${Uppercase<P2>}${CamelCase<P3>}`
: Lowercase<S>; : Lowercase<S>;
export type Camelize<T> = { export type Camelize<T> = {
// eslint-disable-next-line @typescript-eslint/array-type // eslint-disable-next-line @typescript-eslint/array-type
[K in keyof T as CamelCase<string & K>]: T[K] extends Array<infer U> [K in keyof T as CamelCase<string & K>]: T[K] extends Array<infer U>
? // eslint-disable-next-line @typescript-eslint/ban-types ? // eslint-disable-next-line @typescript-eslint/ban-types
U extends {} U extends {}
? // eslint-disable-next-line @typescript-eslint/array-type ? // eslint-disable-next-line @typescript-eslint/array-type
Array<Camelize<U>> Array<Camelize<U>>
: T[K] : T[K]
: // eslint-disable-next-line @typescript-eslint/ban-types : // eslint-disable-next-line @typescript-eslint/ban-types
T[K] extends {} T[K] extends {}
? Camelize<T[K]> ? Camelize<T[K]>
: never; : never;
}; };
export type PickPartial<T, K extends keyof T> = { export type PickPartial<T, K extends keyof T> = {

View File

@ -1187,7 +1187,11 @@ export interface DiscordActionRow {
} }
export interface DiscordSelectMenuComponent { export interface DiscordSelectMenuComponent {
type: MessageComponentTypes.SelectMenu; type: MessageComponentTypes.SelectMenu |
MessageComponentTypes.RoleSelect |
MessageComponentTypes.UserSelect |
MessageComponentTypes.MentionableSelect |
MessageComponentTypes.ChannelSelect;
/** A custom identifier for this component. Maximum 100 characters. */ /** A custom identifier for this component. Maximum 100 characters. */
custom_id: string; custom_id: string;
/** A custom placeholder text if nothing is selected. Maximum 150 characters. */ /** A custom placeholder text if nothing is selected. Maximum 150 characters. */
@ -2396,8 +2400,8 @@ export interface DiscordGuildMemberUpdate {
/** https://discord.com/developers/docs/topics/gateway#message-reaction-remove-all */ /** https://discord.com/developers/docs/topics/gateway#message-reaction-remove-all */
export interface DiscordMessageReactionRemoveAll export interface DiscordMessageReactionRemoveAll
extends Pick< extends Pick<
DiscordMessageReactionAdd, DiscordMessageReactionAdd,
'channel_id' | 'message_id' | 'guild_id' 'channel_id' | 'message_id' | 'guild_id'
> { } > { }
// TODO: add docs link // TODO: add docs link

View File

@ -223,6 +223,10 @@ export class ActionRow extends BaseComponent implements ActionRowComponent {
} }
return new Button(session, component); return new Button(session, component);
case MessageComponentTypes.SelectMenu: case MessageComponentTypes.SelectMenu:
case MessageComponentTypes.RoleSelect:
case MessageComponentTypes.UserSelect:
case MessageComponentTypes.MentionableSelect:
case MessageComponentTypes.ChannelSelect:
return new SelectMenu(session, component); return new SelectMenu(session, component);
case MessageComponentTypes.InputText: case MessageComponentTypes.InputText:
return new TextInput( return new TextInput(
@ -257,6 +261,10 @@ export class ComponentFactory {
} }
return new Button(session, component); return new Button(session, component);
case MessageComponentTypes.SelectMenu: case MessageComponentTypes.SelectMenu:
case MessageComponentTypes.RoleSelect:
case MessageComponentTypes.UserSelect:
case MessageComponentTypes.MentionableSelect:
case MessageComponentTypes.ChannelSelect:
return new SelectMenu(session, component); return new SelectMenu(session, component);
case MessageComponentTypes.InputText: case MessageComponentTypes.InputText:
return new TextInput( return new TextInput(

View File

@ -3,91 +3,101 @@ import type { ComponentEmoji } from '@biscuitland/core';
import { MessageComponentTypes } from '@biscuitland/api-types'; import { MessageComponentTypes } from '@biscuitland/api-types';
export class SelectMenuOptionBuilder { export class SelectMenuOptionBuilder {
constructor() { constructor() {
this.#data = {} as DiscordSelectOption; this.#data = {} as DiscordSelectOption;
} }
#data: DiscordSelectOption; #data: DiscordSelectOption;
setLabel(label: string): SelectMenuOptionBuilder { setLabel(label: string): SelectMenuOptionBuilder {
this.#data.label = label; this.#data.label = label;
return this; return this;
} }
setValue(value: string): SelectMenuOptionBuilder { setValue(value: string): SelectMenuOptionBuilder {
this.#data.value = value; this.#data.value = value;
return this; return this;
} }
setDescription(description: string): SelectMenuOptionBuilder { setDescription(description: string): SelectMenuOptionBuilder {
this.#data.description = description; this.#data.description = description;
return this; return this;
} }
setDefault(Default = true): SelectMenuOptionBuilder { setDefault(Default = true): SelectMenuOptionBuilder {
this.#data.default = Default; this.#data.default = Default;
return this; return this;
} }
setEmoji(emoji: ComponentEmoji): SelectMenuOptionBuilder { setEmoji(emoji: ComponentEmoji): SelectMenuOptionBuilder {
this.#data.emoji = emoji; this.#data.emoji = emoji;
return this; return this;
} }
toJSON(): DiscordSelectOption { toJSON(): DiscordSelectOption {
return { ...this.#data }; return { ...this.#data };
} }
} }
export class SelectMenuBuilder { export class SelectMenuBuilder {
constructor() { constructor() {
this.#data = {} as DiscordSelectMenuComponent; this.#data = {} as DiscordSelectMenuComponent;
this.type = MessageComponentTypes.SelectMenu; this.type = MessageComponentTypes.SelectMenu;
this.options = []; this.options = [];
} }
#data: DiscordSelectMenuComponent; #data: DiscordSelectMenuComponent;
type: MessageComponentTypes.SelectMenu; type: MessageComponentTypes.SelectMenu |
options: SelectMenuOptionBuilder[]; MessageComponentTypes.RoleSelect |
MessageComponentTypes.UserSelect |
MessageComponentTypes.MentionableSelect |
MessageComponentTypes.ChannelSelect;
setPlaceholder(placeholder: string): this { options: SelectMenuOptionBuilder[];
this.#data.placeholder = placeholder;
return this;
}
setValues(max?: number, min?: number): this { setType(type: this['type']) {
this.#data.max_values = max; this.type = type;
this.#data.min_values = min; return this;
return this; }
}
setDisabled(disabled = true): this { setPlaceholder(placeholder: string): this {
this.#data.disabled = disabled; this.#data.placeholder = placeholder;
return this; return this;
} }
setCustomId(id: string): this { setValues(max?: number, min?: number): this {
this.#data.custom_id = id; this.#data.max_values = max;
return this; this.#data.min_values = min;
} return this;
}
setOptions(...options: SelectMenuOptionBuilder[]): this { setDisabled(disabled = true): this {
this.options.splice( this.#data.disabled = disabled;
0, return this;
this.options.length, }
...options,
);
return this;
}
addOptions(...options: SelectMenuOptionBuilder[]): this { setCustomId(id: string): this {
this.options.push( this.#data.custom_id = id;
...options, return this;
); }
return this;
}
toJSON(): DiscordSelectMenuComponent { setOptions(...options: SelectMenuOptionBuilder[]): this {
return { ...this.#data, type: this.type, options: this.options.map(option => option.toJSON()) }; this.options.splice(
} 0,
this.options.length,
...options,
);
return this;
}
addOptions(...options: SelectMenuOptionBuilder[]): this {
this.options.push(
...options,
);
return this;
}
toJSON(): DiscordSelectMenuComponent {
return { ...this.#data, type: this.type, options: this.options.map(option => option.toJSON()) };
}
} }

View File

@ -73,7 +73,7 @@ export class DefaultRestAdapter implements RestAdapter {
private url: string; private url: string;
constructor(options: DefaultRestOptions) { constructor(options: DefaultRestOptions) {
this.options = Object.assign(Object.create(DefaultRestAdapter.DEFAULTS), options); this.options = Object.assign({}, DefaultRestAdapter.DEFAULTS, options);
if (this.options.url) { if (this.options.url) {
this.url = `${options.url}/v${this.options.version}`; this.url = `${options.url}/v${this.options.version}`;

View File

@ -30,10 +30,10 @@ export class ShardManager {
} }
>(); >();
readonly shards = new Map<number, Shard>(); readonly shards = new Map<number, Shard>();
constructor(options: ShardManagerOptions) { constructor(options: ShardManagerOptions) {
this.options = Options({}, ShardManager.DEFAULTS, options); this.options = Options(ShardManager.DEFAULTS, options);
} }
/** Invokes internal processing and respawns shards */ /** Invokes internal processing and respawns shards */
@ -59,11 +59,11 @@ export class ShardManager {
/** Create the start sequence of the shards inside the buckets. */ /** Create the start sequence of the shards inside the buckets. */
for (let i = 0; i < gateway.shards; i++) { for (let i = 0; i < gateway.shards; i++) {
const bucketID = i % gateway.session_start_limit.max_concurrency; const bucketID = i % gateway.session_start_limit.max_concurrency;
const bucket = this.buckets.get(bucketID); const bucket = this.buckets.get(bucketID);
if (bucket) { if (bucket) {
const workerID = Math.floor(i / workers.shards); const workerID = Math.floor(i / workers.shards);
const worker = bucket.workers.find(w => w.id === workerID); const worker = bucket.workers.find(w => w.id === workerID);
if (worker) { if (worker) {
@ -75,22 +75,22 @@ export class ShardManager {
} }
/** Route all shards to workers */ /** Route all shards to workers */
this.buckets.forEach(async bucket => { this.buckets.forEach(async bucket => {
for (const worker of bucket.workers) { for (const worker of bucket.workers) {
for (const id of worker.queue) { for (const id of worker.queue) {
await this.connect(id); await this.connect(id);
} }
} }
}); });
} }
/** Invokes the bucket to prepare the connection to the shard */ /** Invokes the bucket to prepare the connection to the shard */
private async connect(id: number): Promise<Shard> { private async connect(id: number): Promise<Shard> {
const { gateway } = this.options; const { gateway } = this.options;
let shard = this.shards.get(id); let shard = this.shards.get(id);
if (!shard) { if (!shard) {
shard = new Shard({ shard = new Shard({
@ -107,8 +107,8 @@ export class ShardManager {
}, },
handleIdentify: async (id: number) => { handleIdentify: async (id: number) => {
await this.buckets.get(id % gateway.session_start_limit.max_concurrency)!.leak.acquire(1); // remove await? await this.buckets.get(id % gateway.session_start_limit.max_concurrency)!.leak.acquire(1); // remove await?
} }
}); });
this.shards.set(id, shard); this.shards.set(id, shard);

View File

@ -27,7 +27,7 @@ export class Shard {
resumeURL: string | null = null; resumeURL: string | null = null;
sessionID: string | null = null; sessionID: string | null = null;
sequence = 0 ; sequence = 0;
resolves: Map<string, (payload?: unknown) => void> = new Map(); resolves: Map<string, (payload?: unknown) => void> = new Map();
@ -40,7 +40,7 @@ export class Shard {
ws: WebSocket | null = null; ws: WebSocket | null = null;
constructor(options: ShardOptions) { constructor(options: ShardOptions) {
this.options = Options({}, Shard.DEFAULTS, options); this.options = Options(Shard.DEFAULTS, options);
this.bucket = createLeakyBucket({ this.bucket = createLeakyBucket({
max: 120, max: 120,
@ -78,7 +78,7 @@ export class Shard {
this.heartbeatInterval = null; this.heartbeatInterval = null;
} }
connect() { connect() {
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) { if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
return; return;
} }
@ -101,7 +101,7 @@ export class Shard {
setTimeout(() => resolve(true), this.options.shards.timeout); setTimeout(() => resolve(true), this.options.shards.timeout);
}); });
}); });
} }
identify() { identify() {
this.status = 'Identifying'; this.status = 'Identifying';
@ -110,14 +110,14 @@ export class Shard {
op: GatewayOpcodes.Identify, op: GatewayOpcodes.Identify,
d: { d: {
token: `Bot ${this.options.config.token}`, token: `Bot ${this.options.config.token}`,
compress: false, compress: false,
properties: { properties: {
os: 'linux', os: 'linux',
device: 'Biscuit', device: 'Biscuit',
browser: 'Biscuit' browser: 'Biscuit'
}, },
intents: this.options.config.intents, intents: this.options.config.intents,
shard: [this.options.id, this.options.gateway.shards], shard: [this.options.id, this.options.gateway.shards],
} }
}); });
} }
@ -203,7 +203,7 @@ export class Shard {
} }
private async onMessage(data: any, isBinary: boolean) { private async onMessage(data: any, isBinary: boolean) {
const payload = this.pack(data as Buffer | ArrayBuffer, isBinary); const payload = this.pack(data as Buffer | ArrayBuffer, isBinary);
if (payload.s != null) { if (payload.s != null) {
this.sequence = payload.s; this.sequence = payload.s;