mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-02 21:16:09 +00:00
feat(helpers): Permissions util
This commit is contained in:
parent
490898a56f
commit
fa32071d4e
@ -1,7 +1,7 @@
|
||||
import { MakeRequired, Options } from '@biscuitland/common';
|
||||
import { type Session, Handler } from '@biscuitland/core';
|
||||
import { GatewayEvents } from '@biscuitland/ws';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { MakeRequired, Options } from "@biscuitland/common";
|
||||
import { Handler, type Session } from "@biscuitland/core";
|
||||
import { GatewayEvents } from "@biscuitland/ws";
|
||||
import { EventEmitter } from "node:events";
|
||||
|
||||
interface CollectorOptions<E extends keyof GatewayEvents> {
|
||||
event: `${E}`;
|
||||
@ -13,19 +13,19 @@ interface CollectorOptions<E extends keyof GatewayEvents> {
|
||||
|
||||
export const DEFAULT_OPTIONS = {
|
||||
filter: () => true,
|
||||
max: -1
|
||||
max: -1,
|
||||
};
|
||||
|
||||
export enum CollectorStatus {
|
||||
Idle = 0,
|
||||
Started = 1,
|
||||
Ended = 2
|
||||
Ended = 2,
|
||||
}
|
||||
|
||||
export class EventCollector<E extends keyof GatewayEvents> extends EventEmitter {
|
||||
collected = new Set<Parameters<Handler[E]>[0]>();
|
||||
status: CollectorStatus = CollectorStatus.Idle;
|
||||
options: MakeRequired<CollectorOptions<E>, 'filter' | 'max'>;
|
||||
options: MakeRequired<CollectorOptions<E>, "filter" | "max">;
|
||||
private timeout: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(readonly session: Session, rawOptions: CollectorOptions<E>) {
|
||||
@ -36,24 +36,24 @@ export class EventCollector<E extends keyof GatewayEvents> extends EventEmitter
|
||||
start() {
|
||||
this.session.setMaxListeners(this.session.getMaxListeners() + 1);
|
||||
this.session.on(this.options.event, (...args: unknown[]) => this.collect(...(args as Parameters<Handler[E]>)));
|
||||
this.timeout = setTimeout(() => this.stop('time'), this.options.idle ?? this.options.time);
|
||||
this.timeout = setTimeout(() => this.stop("time"), this.options.idle ?? this.options.time);
|
||||
}
|
||||
|
||||
private collect(...args: Parameters<Handler[E]>) {
|
||||
if (this.options.filter?.(...args)) {
|
||||
this.collected.add(args[0]);
|
||||
this.emit('collect', ...args);
|
||||
this.emit("collect", ...args);
|
||||
}
|
||||
|
||||
if (this.options.idle) {
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
this.timeout = setTimeout(() => this.stop('time'), this.options.idle);
|
||||
this.timeout = setTimeout(() => this.stop("time"), this.options.idle);
|
||||
}
|
||||
|
||||
if (this.collected.size >= this.options.max!) this.stop('max');
|
||||
if (this.collected.size >= this.options.max!) this.stop("max");
|
||||
}
|
||||
|
||||
stop(reason = 'User stopped') {
|
||||
stop(reason = "User stopped") {
|
||||
if (this.status === CollectorStatus.Ended) return;
|
||||
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
@ -62,17 +62,17 @@ export class EventCollector<E extends keyof GatewayEvents> extends EventEmitter
|
||||
this.session.setMaxListeners(this.session.getMaxListeners() - 1);
|
||||
|
||||
this.status = CollectorStatus.Ended;
|
||||
this.emit('end', reason, this.collected);
|
||||
this.emit("end", reason, this.collected);
|
||||
}
|
||||
|
||||
on(event: 'collect', listener: (...args: Parameters<Handler[E]>) => unknown): this;
|
||||
on(event: 'end', listener: (reason: string | null | undefined, collected: Set<Parameters<Handler[E]>[0]>) => void): this;
|
||||
on(event: "collect", listener: (...args: Parameters<Handler[E]>) => unknown): this;
|
||||
on(event: "end", listener: (reason: string | null | undefined, collected: Set<Parameters<Handler[E]>[0]>) => void): this;
|
||||
on(event: string, listener: unknown): this {
|
||||
return super.on(event, listener as () => unknown);
|
||||
}
|
||||
|
||||
once(event: 'collect', listener: (...args: Parameters<Handler[E]>) => unknown): this;
|
||||
once(event: 'end', listener: (reason: string | null | undefined, collected: Set<Parameters<Handler[E]>[0]>) => void): this;
|
||||
once(event: "collect", listener: (...args: Parameters<Handler[E]>) => unknown): this;
|
||||
once(event: "end", listener: (reason: string | null | undefined, collected: Set<Parameters<Handler[E]>[0]>) => void): this;
|
||||
once(event: string, listener: unknown): this {
|
||||
return super.once(event, listener as () => unknown);
|
||||
}
|
||||
|
132
packages/helpers/src/Permissions.ts
Normal file
132
packages/helpers/src/Permissions.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import { PermissionFlagsBits } from "@biscuitland/common";
|
||||
|
||||
export type PermissionsStrings = keyof typeof PermissionFlagsBits;
|
||||
export type PermissionResolvable = bigint | PermissionsStrings | PermissionsStrings[] | PermissionsStrings | PermissionsStrings[];
|
||||
|
||||
export class Permissions {
|
||||
/** Stores a reference to BitwisePermissionFlags */
|
||||
static Flags = PermissionFlagsBits;
|
||||
|
||||
/** Falsy; Stores the lack of permissions*/
|
||||
static None = 0n;
|
||||
|
||||
/** Stores all entity permissions */
|
||||
bitfield: bigint;
|
||||
|
||||
/**
|
||||
* Wheter to grant all other permissions to the administrator
|
||||
* **Not to get confused with Permissions#admin**
|
||||
*/
|
||||
__admin__ = true;
|
||||
|
||||
constructor(bitfield: PermissionResolvable) {
|
||||
this.bitfield = Permissions.resolve(bitfield);
|
||||
}
|
||||
|
||||
/** Wheter the bitfield has the administrator flag */
|
||||
get admin(): boolean {
|
||||
return this.has(Permissions.Flags.Administrator);
|
||||
}
|
||||
|
||||
get array(): PermissionsStrings[] {
|
||||
// unsafe cast, do not edit
|
||||
const permissions = Object.keys(Permissions.Flags) as PermissionsStrings[];
|
||||
return permissions.filter((bit) => this.has(bit));
|
||||
}
|
||||
|
||||
add(...bits: PermissionResolvable[]): this {
|
||||
let reduced = 0n;
|
||||
for (const bit of bits) {
|
||||
reduced |= Permissions.resolve(bit);
|
||||
}
|
||||
this.bitfield |= reduced;
|
||||
return this;
|
||||
}
|
||||
|
||||
remove(...bits: PermissionResolvable[]): this {
|
||||
let reduced = 0n;
|
||||
for (const bit of bits) {
|
||||
reduced |= Permissions.resolve(bit);
|
||||
}
|
||||
this.bitfield &= ~reduced;
|
||||
return this;
|
||||
}
|
||||
|
||||
has(bit: PermissionResolvable): boolean {
|
||||
const bbit = Permissions.resolve(bit);
|
||||
|
||||
if (this.__admin__ && this.bitfield & BigInt(Permissions.Flags.Administrator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (this.bitfield & bbit) === bbit;
|
||||
}
|
||||
|
||||
any(bit: PermissionResolvable): boolean {
|
||||
const bbit = Permissions.resolve(bit);
|
||||
|
||||
if (this.__admin__ && this.bitfield & BigInt(Permissions.Flags.Administrator)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (this.bitfield & bbit) !== Permissions.None;
|
||||
}
|
||||
|
||||
equals(bit: PermissionResolvable): boolean {
|
||||
return !!(this.bitfield & Permissions.resolve(bit));
|
||||
}
|
||||
|
||||
/** Gets all permissions */
|
||||
static get All(): bigint {
|
||||
let reduced = 0n;
|
||||
for (const key in PermissionFlagsBits) {
|
||||
const perm = PermissionFlagsBits[key];
|
||||
|
||||
reduced = reduced | perm;
|
||||
}
|
||||
return reduced;
|
||||
}
|
||||
|
||||
static resolve(bit: PermissionResolvable): bigint {
|
||||
switch (typeof bit) {
|
||||
case "bigint":
|
||||
return bit;
|
||||
case "number":
|
||||
return BigInt(bit);
|
||||
case "string":
|
||||
return BigInt(Permissions.Flags[bit]);
|
||||
case "object":
|
||||
return Permissions.resolve(
|
||||
bit
|
||||
.map((p) => (typeof p === "string" ? BigInt(Permissions.Flags[p]) : BigInt(p)))
|
||||
.reduce((acc, cur) => acc | cur, Permissions.None),
|
||||
);
|
||||
default:
|
||||
throw new TypeError(`Cannot resolve permission: ${bit}`);
|
||||
}
|
||||
}
|
||||
|
||||
static sum(permissions: (bigint | number)[]) {
|
||||
return permissions.reduce((y, x) => BigInt(y) | BigInt(x), Permissions.None);
|
||||
}
|
||||
|
||||
static reduce(permissions: PermissionResolvable[]): Permissions {
|
||||
const solved = permissions.map(Permissions.resolve);
|
||||
|
||||
return new Permissions(solved.reduce((y, x) => y | x, Permissions.None));
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
yield* this.array;
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this.bitfield;
|
||||
}
|
||||
|
||||
toJSON(): { fields: string[] } {
|
||||
const fields = Object.keys(Permissions.Flags).filter((bit) => typeof bit === "number" && this.has(bit));
|
||||
|
||||
return { fields };
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { APIMessageActionRowComponent, APIModalActionRowComponent, ComponentType, PermissionFlagsBits } from '@biscuitland/common';
|
||||
import { APIMessageActionRowComponent, APIModalActionRowComponent, ComponentType } from "@biscuitland/common";
|
||||
import {
|
||||
ChannelSelectMenu,
|
||||
MentionableSelectMenu,
|
||||
@ -6,9 +6,9 @@ import {
|
||||
ModalTextInput,
|
||||
RoleSelectMenu,
|
||||
StringSelectMenu,
|
||||
UserSelectMenu
|
||||
} from './components';
|
||||
import { BaseComponent } from './components/BaseComponent';
|
||||
UserSelectMenu,
|
||||
} from "./components";
|
||||
import { BaseComponent } from "./components/BaseComponent";
|
||||
|
||||
export function createComponent(data: APIMessageActionRowComponent): HelperComponents;
|
||||
export function createComponent(data: APIModalActionRowComponent): HelperComponents;
|
||||
@ -36,7 +36,6 @@ export function createComponent(data: HelperComponents | APIMessageActionRowComp
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionsStrings = `${keyof typeof PermissionFlagsBits}`;
|
||||
export type OptionValuesLength = { max: number; min: number };
|
||||
export type MessageSelectMenus = RoleSelectMenu | UserSelectMenu | StringSelectMenu | ChannelSelectMenu | MentionableSelectMenu;
|
||||
export type MessageComponents = MessageButton | MessageSelectMenus;
|
||||
|
@ -1,11 +1,5 @@
|
||||
import {
|
||||
LocalizationMap,
|
||||
ApplicationCommandType,
|
||||
Permissions,
|
||||
PermissionFlagsBits,
|
||||
RESTPostAPIContextMenuApplicationCommandsJSONBody
|
||||
} from '@biscuitland/common';
|
||||
import { PermissionsStrings } from '../../Utils';
|
||||
import { ApplicationCommandType, LocalizationMap, RESTPostAPIContextMenuApplicationCommandsJSONBody } from "@biscuitland/common";
|
||||
import { PermissionResolvable, Permissions } from "../../Permissions";
|
||||
|
||||
export type ContextCommandType = ApplicationCommandType.Message | ApplicationCommandType.User;
|
||||
|
||||
@ -14,7 +8,7 @@ export class ContextCommand {
|
||||
name_localizations?: LocalizationMap;
|
||||
type: ContextCommandType = undefined!;
|
||||
default_permission: boolean | undefined = undefined;
|
||||
default_member_permissions: Permissions | null | undefined = undefined;
|
||||
default_member_permissions: string | undefined = undefined;
|
||||
dm_permission: boolean | undefined = undefined;
|
||||
|
||||
setName(name: string): this {
|
||||
@ -38,8 +32,8 @@ export class ContextCommand {
|
||||
return this;
|
||||
}
|
||||
|
||||
setDefautlMemberPermissions(permissions: PermissionsStrings[]): this {
|
||||
this.default_member_permissions = `$${permissions.reduce((y, x) => y | PermissionFlagsBits[x], 0n)}`;
|
||||
setDefautlMemberPermissions(permissions: PermissionResolvable[]): this {
|
||||
this.default_member_permissions = Permissions.reduce(permissions).bitfield.toString();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
export * from './slash/SlashCommand';
|
||||
export * from './slash/SlashCommandOption';
|
||||
export * from './contextMenu/ContextCommand';
|
||||
export * from "./contextMenu/ContextCommand";
|
||||
export * from "./slash/SlashCommand";
|
||||
export * from "./slash/SlashCommandOption";
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ApplicationCommandType, PermissionFlagsBits, RESTPostAPIChatInputApplicationCommandsJSONBody } from '@biscuitland/common';
|
||||
import { AllSlashOptions, SlashSubcommandGroupOption, SlashSubcommandOption } from './SlashCommandOption';
|
||||
import { PermissionsStrings } from '../../Utils';
|
||||
import { Mixin } from 'ts-mixer';
|
||||
import { ApplicationCommandType, RESTPostAPIChatInputApplicationCommandsJSONBody } from "@biscuitland/common";
|
||||
import { Mixin } from "ts-mixer";
|
||||
import { PermissionResolvable, Permissions } from "../../Permissions";
|
||||
import { AllSlashOptions, SlashSubcommandGroupOption, SlashSubcommandOption } from "./SlashCommandOption";
|
||||
|
||||
class SlashCommandB {
|
||||
constructor(public data: Partial<RESTPostAPIChatInputApplicationCommandsJSONBody> = {}) {}
|
||||
@ -16,8 +16,8 @@ class SlashCommandB {
|
||||
return this;
|
||||
}
|
||||
|
||||
setDefautlMemberPermissions(permissions: PermissionsStrings[]): this {
|
||||
this.data.default_member_permissions = `$${permissions.reduce((y, x) => y | PermissionFlagsBits[x], 0n)}`;
|
||||
setDefautlMemberPermissions(permissions: PermissionResolvable[]): this {
|
||||
this.data.default_member_permissions = Permissions.reduce(permissions).bitfield.toString();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ class SlashCommandB {
|
||||
return this;
|
||||
}
|
||||
|
||||
addRawOption(option: ReturnType<AllSlashOptions['toJSON']>) {
|
||||
addRawOption(option: ReturnType<AllSlashOptions["toJSON"]>) {
|
||||
this.data.options ??= [];
|
||||
// @ts-expect-error discord-api-types bad typing, again
|
||||
this.data.options.push(option);
|
||||
@ -42,7 +42,7 @@ class SlashCommandB {
|
||||
toJSON(): RESTPostAPIChatInputApplicationCommandsJSONBody {
|
||||
return {
|
||||
...this.data,
|
||||
type: ApplicationCommandType.ChatInput
|
||||
type: ApplicationCommandType.ChatInput,
|
||||
} as RESTPostAPIChatInputApplicationCommandsJSONBody & {
|
||||
type: ApplicationCommandType.ChatInput;
|
||||
};
|
||||
|
@ -1,26 +1,26 @@
|
||||
import {
|
||||
APIApplicationCommandOption,
|
||||
APIApplicationCommandOptionChoice,
|
||||
ApplicationCommandOptionType,
|
||||
APIApplicationCommandIntegerOption as AACIO,
|
||||
APIApplicationCommandNumberOption as AACNO,
|
||||
APIApplicationCommandSubcommandOption as AACSCO,
|
||||
APIApplicationCommandSubcommandGroupOption as AACSGO,
|
||||
APIApplicationCommandStringOption as AACSO,
|
||||
APIApplicationCommandAttachmentOption,
|
||||
APIApplicationCommandBooleanOption,
|
||||
APIApplicationCommandChannelOption,
|
||||
APIApplicationCommandMentionableOption,
|
||||
APIApplicationCommandOption,
|
||||
APIApplicationCommandOptionBase,
|
||||
APIApplicationCommandOptionChoice,
|
||||
APIApplicationCommandRoleOption,
|
||||
APIApplicationCommandUserOption,
|
||||
ApplicationCommandOptionType,
|
||||
ChannelType,
|
||||
LocalizationMap,
|
||||
RestToKeys,
|
||||
TypeArray,
|
||||
When,
|
||||
APIApplicationCommandNumberOption as AACNO,
|
||||
APIApplicationCommandIntegerOption as AACIO,
|
||||
APIApplicationCommandSubcommandGroupOption as AACSGO,
|
||||
APIApplicationCommandSubcommandOption as AACSCO,
|
||||
APIApplicationCommandUserOption,
|
||||
APIApplicationCommandChannelOption,
|
||||
ChannelType,
|
||||
APIApplicationCommandRoleOption,
|
||||
APIApplicationCommandMentionableOption,
|
||||
APIApplicationCommandAttachmentOption,
|
||||
APIApplicationCommandBooleanOption
|
||||
} from '@biscuitland/common';
|
||||
import { OptionValuesLength } from '../../';
|
||||
} from "@biscuitland/common";
|
||||
import { OptionValuesLength } from "../../";
|
||||
|
||||
export type SlashBaseOptionTypes =
|
||||
| Exclude<APIApplicationCommandOption, AACSO | AACNO | AACIO | AACSCO>
|
||||
@ -53,7 +53,7 @@ export abstract class SlashBaseOption<DataType extends SlashBaseOptionTypes> {
|
||||
return this;
|
||||
}
|
||||
|
||||
addLocalizations(locals: RestToKeys<[LocalizationMap, 'name', 'description']>): this {
|
||||
addLocalizations(locals: RestToKeys<[LocalizationMap, "name", "description"]>): this {
|
||||
this.data.name_localizations = locals.name;
|
||||
this.data.description_localizations = locals.description;
|
||||
return this;
|
||||
@ -333,7 +333,7 @@ export class SlashSubcommandGroupOption extends SlashBaseOption<APIApplicationCo
|
||||
return this;
|
||||
}
|
||||
|
||||
addRawOption(option: ReturnType<SlashSubcommandOption['toJSON']>) {
|
||||
addRawOption(option: ReturnType<SlashSubcommandOption["toJSON"]>) {
|
||||
this.data.options ??= [];
|
||||
this.data.options.push(option);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { APIActionRowComponent, APIMessageActionRowComponent, ComponentType, TypeArray } from '@biscuitland/common';
|
||||
import { MessageComponents, createComponent } from '../Utils';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
import { APIActionRowComponent, APIMessageActionRowComponent, ComponentType, TypeArray } from "@biscuitland/common";
|
||||
import { MessageComponents, createComponent } from "../Utils";
|
||||
import { BaseComponent } from "./BaseComponent";
|
||||
|
||||
export class MessageActionRow<T extends MessageComponents> extends BaseComponent<APIActionRowComponent<APIMessageActionRowComponent>> {
|
||||
constructor({ components, ...data }: Partial<APIActionRowComponent<APIMessageActionRowComponent>>) {
|
||||
@ -22,7 +22,7 @@ export class MessageActionRow<T extends MessageComponents> extends BaseComponent
|
||||
toJSON(): APIActionRowComponent<APIMessageActionRowComponent> {
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map((c) => c.toJSON())
|
||||
} as APIActionRowComponent<ReturnType<T['toJSON']>>;
|
||||
components: this.components.map((c) => c.toJSON()),
|
||||
} as APIActionRowComponent<ReturnType<T["toJSON"]>>;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { APIBaseComponent, ComponentType } from '@biscuitland/common';
|
||||
import { APIBaseComponent, ComponentType } from "@biscuitland/common";
|
||||
|
||||
export abstract class BaseComponent<TYPE extends Partial<APIBaseComponent<ComponentType>> = APIBaseComponent<ComponentType>,> {
|
||||
constructor(public data: Partial<TYPE>) {}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { APIButtonComponentBase, APIMessageComponentEmoji, ButtonStyle, ComponentType, When } from '@biscuitland/common';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
import { APIButtonComponentBase, APIMessageComponentEmoji, ButtonStyle, ComponentType, When } from "@biscuitland/common";
|
||||
import { BaseComponent } from "./BaseComponent";
|
||||
|
||||
export type ButtonStylesForID = Exclude<ButtonStyle, ButtonStyle.Link>;
|
||||
|
||||
|
@ -9,10 +9,10 @@ import {
|
||||
APIUserSelectComponent,
|
||||
ChannelType,
|
||||
ComponentType,
|
||||
TypeArray
|
||||
} from '@biscuitland/common';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
import { OptionValuesLength } from '..';
|
||||
TypeArray,
|
||||
} from "@biscuitland/common";
|
||||
import { OptionValuesLength } from "..";
|
||||
import { BaseComponent } from "./BaseComponent";
|
||||
|
||||
class SelectMenu<Select extends APISelectMenuComponent = APISelectMenuComponent,> extends BaseComponent<Select> {
|
||||
setCustomId(id: string): this {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { APITextInputComponent, ComponentType, TextInputStyle } from '@biscuitland/common';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
import { OptionValuesLength } from '..';
|
||||
import { APITextInputComponent, ComponentType, TextInputStyle } from "@biscuitland/common";
|
||||
import { OptionValuesLength } from "..";
|
||||
import { BaseComponent } from "./BaseComponent";
|
||||
|
||||
export class ModalTextInput extends BaseComponent<APITextInputComponent> {
|
||||
constructor(data: Partial<APITextInputComponent> = {}) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
export * from './ActionRow';
|
||||
export * from './MessageButton';
|
||||
export * from './SelectMenu';
|
||||
export * from './TextInput';
|
||||
export * from './BaseComponent';
|
||||
export * from "./ActionRow";
|
||||
export * from "./BaseComponent";
|
||||
export * from "./MessageButton";
|
||||
export * from "./SelectMenu";
|
||||
export * from "./TextInput";
|
||||
|
@ -1,4 +1,5 @@
|
||||
export * from './MessageEmbed';
|
||||
export * from './components/index';
|
||||
export * from './Utils';
|
||||
export * from './commands';
|
||||
export * from "./MessageEmbed";
|
||||
export * from "./Permissions";
|
||||
export * from "./Utils";
|
||||
export * from "./commands";
|
||||
export * from "./components";
|
||||
|
Loading…
x
Reference in New Issue
Block a user