From 67c43e8fcf20f416d14f0cdbd415f96c2462c595 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Thu, 14 Jul 2022 17:45:27 +0000 Subject: [PATCH] compat: remove mixer (#53) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yuzu Co-authored-by: Nicolás Serna * compat: replace the mixer Co-authored-by: Marcos Susaña <66887817+socram03@users.noreply.github.com> Co-authored-by: Nicolás Serna --- examples/node.js | 42 +++ examples/nodemodules.mjs | 42 +++ packages/biscuit/mod.ts | 12 +- .../InputTextComponentBuilder.ts | 2 +- .../{ => components}/MessageActionRow.ts | 4 +- .../{ => components}/MessageButton.ts | 4 +- .../{ => components}/MessageSelectMenu.ts | 2 +- .../SelectMenuOptionBuilder.ts | 4 +- .../builders/slash/ApplicationCommand.ts | 108 ++++++ .../slash/ApplicationCommandOption.ts | 343 ++++++++++++++++++ .../interactions/CommandInteraction.ts | 2 +- 11 files changed, 551 insertions(+), 14 deletions(-) create mode 100644 examples/node.js create mode 100644 examples/nodemodules.mjs rename packages/biscuit/structures/builders/{ => components}/InputTextComponentBuilder.ts (95%) rename packages/biscuit/structures/builders/{ => components}/MessageActionRow.ts (89%) rename packages/biscuit/structures/builders/{ => components}/MessageButton.ts (89%) rename packages/biscuit/structures/builders/{ => components}/MessageSelectMenu.ts (97%) rename packages/biscuit/structures/builders/{ => components}/SelectMenuOptionBuilder.ts (84%) create mode 100644 packages/biscuit/structures/builders/slash/ApplicationCommand.ts create mode 100644 packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts diff --git a/examples/node.js b/examples/node.js new file mode 100644 index 0000000..aedac40 --- /dev/null +++ b/examples/node.js @@ -0,0 +1,42 @@ +/** + * Biscuit node example +*/ + +/** @type {NodeJS.Process} process */ +const process = require("node:process"); +const { Session, GatewayIntents } = require("@oasisjs/biscuit"); + +/** @type {string} token */ +const token = process.env.TOKEN || "YOUR_TOKEN_HERE"; + +if (token === "") { + return new Error("Please set the TOKEN environment variable"); +} + +const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages; +const session = new Session({ token, intents }); +const PREFIX = ">"; + +session.on("ready", (data) => { + console.log("Ready! Let's start chatting!"); + console.log("Connected as: " + data.user.username); +}) + +session.on("messageCreate", (message) => { + if (message.author?.bot || !message.content.startsWith(PREFIX)) { + return; + } + + const args = message.content.substring(PREFIX.length).trim().split(/\s+/gm); + const name = args.shift()?.toLowerCase(); + + if (name === "ping") { + message.reply({ content: "pong!" }); + } +}); + +try { + session.start(); +} catch(err){ + throw err; +} \ No newline at end of file diff --git a/examples/nodemodules.mjs b/examples/nodemodules.mjs new file mode 100644 index 0000000..f566931 --- /dev/null +++ b/examples/nodemodules.mjs @@ -0,0 +1,42 @@ +/** + * Biscuit node example +*/ + +/** @type {NodeJS.Process} process */ +import process from 'node:process'; +import { Session, GatewayIntents } from '@oasisjs/biscuit'; + +/** @type {string} token */ +const token = process.env.TOKEN || "YOUR_TOKEN_HERE"; + +if (token === "") { + console.log(new Error("Please set the TOKEN environment variable")); +} + +const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages; +const session = new Session({ token, intents }); +const PREFIX = ">"; + +session.on("ready", (data) => { + console.log("Ready! Let's start chatting!"); + console.log("Connected as: " + data.user.username); +}) + +session.on("messageCreate", (message) => { + if (message.author?.bot || !message.content.startsWith(PREFIX)) { + return; + } + + const args = message.content.substring(PREFIX.length).trim().split(/\s+/gm); + const name = args.shift()?.toLowerCase(); + + if (name === "ping") { + message.reply({ content: "pong!" }); + } +}); + +try { + session.start(); +} catch(err){ + throw err; +} \ No newline at end of file diff --git a/packages/biscuit/mod.ts b/packages/biscuit/mod.ts index 3e91371..190f223 100644 --- a/packages/biscuit/mod.ts +++ b/packages/biscuit/mod.ts @@ -43,11 +43,13 @@ export * from "./structures/guilds.ts"; // builders export * from "./structures/builders/EmbedBuilder.ts"; -export * from "./structures/builders/InputTextComponentBuilder.ts"; -export * from "./structures/builders/MessageActionRow.ts"; -export * from "./structures/builders/MessageButton.ts"; -export * from "./structures/builders/MessageSelectMenu.ts"; -export * from "./structures/builders/SelectMenuOptionBuilder.ts"; +export * from "./structures/builders/components/InputTextComponentBuilder.ts"; +export * from "./structures/builders/components/MessageActionRow.ts"; +export * from "./structures/builders/components/MessageButton.ts"; +export * from "./structures/builders/components/MessageSelectMenu.ts"; +export * from "./structures/builders/components/SelectMenuOptionBuilder.ts"; +export * from "./structures/builders/slash/ApplicationCommand.ts"; +export * from "./structures/builders/slash/ApplicationCommandOption.ts"; // interactions export * from "./structures/interactions/AutoCompleteInteraction.ts"; diff --git a/packages/biscuit/structures/builders/InputTextComponentBuilder.ts b/packages/biscuit/structures/builders/components/InputTextComponentBuilder.ts similarity index 95% rename from packages/biscuit/structures/builders/InputTextComponentBuilder.ts rename to packages/biscuit/structures/builders/components/InputTextComponentBuilder.ts index 71e7521..d8f9689 100644 --- a/packages/biscuit/structures/builders/InputTextComponentBuilder.ts +++ b/packages/biscuit/structures/builders/components/InputTextComponentBuilder.ts @@ -1,4 +1,4 @@ -import type { DiscordInputTextComponent, MessageComponentTypes, TextStyles } from "../../../discordeno/mod.ts"; +import type { DiscordInputTextComponent, MessageComponentTypes, TextStyles } from "../../../../discordeno/mod.ts"; export class InputTextBuilder { constructor() { diff --git a/packages/biscuit/structures/builders/MessageActionRow.ts b/packages/biscuit/structures/builders/components/MessageActionRow.ts similarity index 89% rename from packages/biscuit/structures/builders/MessageActionRow.ts rename to packages/biscuit/structures/builders/components/MessageActionRow.ts index 237f6fa..70bd4be 100644 --- a/packages/biscuit/structures/builders/MessageActionRow.ts +++ b/packages/biscuit/structures/builders/components/MessageActionRow.ts @@ -1,5 +1,5 @@ -import type { DiscordActionRow, MessageComponentTypes } from "../../../discordeno/mod.ts"; -import type { ComponentBuilder } from "../../Util.ts"; +import type { DiscordActionRow, MessageComponentTypes } from "../../../../discordeno/mod.ts"; +import type { ComponentBuilder } from "../../../Util.ts"; export class ActionRowBuilder { constructor() { diff --git a/packages/biscuit/structures/builders/MessageButton.ts b/packages/biscuit/structures/builders/components/MessageButton.ts similarity index 89% rename from packages/biscuit/structures/builders/MessageButton.ts rename to packages/biscuit/structures/builders/components/MessageButton.ts index 3b96bb9..dfc85ac 100644 --- a/packages/biscuit/structures/builders/MessageButton.ts +++ b/packages/biscuit/structures/builders/components/MessageButton.ts @@ -1,5 +1,5 @@ -import type { ButtonStyles, DiscordButtonComponent, MessageComponentTypes } from "../../../discordeno/mod.ts"; -import type { ComponentEmoji } from "../../Util.ts"; +import type { ButtonStyles, DiscordButtonComponent, MessageComponentTypes } from "../../../../discordeno/mod.ts"; +import type { ComponentEmoji } from "../../../Util.ts"; export class ButtonBuilder { constructor() { diff --git a/packages/biscuit/structures/builders/MessageSelectMenu.ts b/packages/biscuit/structures/builders/components/MessageSelectMenu.ts similarity index 97% rename from packages/biscuit/structures/builders/MessageSelectMenu.ts rename to packages/biscuit/structures/builders/components/MessageSelectMenu.ts index 0b3c1db..bf67b01 100644 --- a/packages/biscuit/structures/builders/MessageSelectMenu.ts +++ b/packages/biscuit/structures/builders/components/MessageSelectMenu.ts @@ -1,4 +1,4 @@ -import type { DiscordSelectMenuComponent, MessageComponentTypes } from "../../../discordeno/mod.ts"; +import type { DiscordSelectMenuComponent, MessageComponentTypes } from "../../../../discordeno/mod.ts"; import type { SelectMenuOptionBuilder } from "./SelectMenuOptionBuilder.ts"; export class SelectMenuBuilder { diff --git a/packages/biscuit/structures/builders/SelectMenuOptionBuilder.ts b/packages/biscuit/structures/builders/components/SelectMenuOptionBuilder.ts similarity index 84% rename from packages/biscuit/structures/builders/SelectMenuOptionBuilder.ts rename to packages/biscuit/structures/builders/components/SelectMenuOptionBuilder.ts index 56a1b1f..ddfc30e 100644 --- a/packages/biscuit/structures/builders/SelectMenuOptionBuilder.ts +++ b/packages/biscuit/structures/builders/components/SelectMenuOptionBuilder.ts @@ -1,5 +1,5 @@ -import type { DiscordSelectOption } from "../../../discordeno/mod.ts"; -import type { ComponentEmoji } from "../../Util.ts"; +import type { DiscordSelectOption } from "../../../../discordeno/mod.ts"; +import type { ComponentEmoji } from "../../../Util.ts"; export class SelectMenuOptionBuilder { constructor() { diff --git a/packages/biscuit/structures/builders/slash/ApplicationCommand.ts b/packages/biscuit/structures/builders/slash/ApplicationCommand.ts new file mode 100644 index 0000000..031c092 --- /dev/null +++ b/packages/biscuit/structures/builders/slash/ApplicationCommand.ts @@ -0,0 +1,108 @@ +import type { Localization, PermissionStrings } from '../../../../discordeno/mod.ts'; +import { ApplicationCommandTypes } from '../../../../discordeno/mod.ts'; +import { OptionBased } from './ApplicationCommandOption.ts'; +import { CreateApplicationCommand } from "../../../Session.ts" + +export abstract class ApplicationCommandBuilder implements CreateApplicationCommand { + protected constructor( + // required + public type: ApplicationCommandTypes = ApplicationCommandTypes.ChatInput, + public name = '', + public description = '', + // non-required + public defaultMemberPermissions?: PermissionStrings[], + // etc + public nameLocalizations?: Localization, + public descriptionLocalizations?: Localization, + public dmPermission = true, + ) { + this.type = type; + this.name = name; + this.description = description; + this.defaultMemberPermissions = defaultMemberPermissions; + this.nameLocalizations = nameLocalizations; + this.descriptionLocalizations = descriptionLocalizations; + this.dmPermission = dmPermission; + } + + public setType(type: ApplicationCommandTypes) { + return (this.type = type), this; + } + + public setName(name: string) { + return (this.name = name), this; + } + + public setDescription(description: string) { + return (this.description = description), this; + } + + public setDefaultMemberPermission(perm: PermissionStrings[]) { + return (this.defaultMemberPermissions = perm), this; + } + + public setNameLocalizations(l: Localization) { + return (this.nameLocalizations = l), this; + } + + public setDescriptionLocalizations(l: Localization) { + return (this.descriptionLocalizations = l), this; + } + + public setDmPermission(perm: boolean) { + return (this.dmPermission = perm), this; + } +} + +export class MessageApplicationCommandBuilder { + public constructor( + // required + public type?: ApplicationCommandTypes, + public name?: string, + ) { + this.type = ApplicationCommandTypes.Message; + this.name = name; + } + + public setName(name: string) { + return (this.name = name), this; + } + + public toJSON(): { name: string; type: ApplicationCommandTypes.Message } { + if (!this.name) throw new TypeError('Propety \'name\' is required'); + + return { + type: ApplicationCommandTypes.Message, + name: this.name, + }; + } +} + +export class ChatInputApplicationCommandBuilder extends ApplicationCommandBuilder { + public type: ApplicationCommandTypes.ChatInput = ApplicationCommandTypes.ChatInput; + + public toJSON(): CreateApplicationCommand { + if (!this.type) throw new TypeError('Propety \'type\' is required'); + if (!this.name) throw new TypeError('Propety \'name\' is required'); + if (!this.description) { + throw new TypeError('Propety \'description\' is required'); + } + + return { + type: ApplicationCommandTypes.ChatInput, + name: this.name, + description: this.description, + options: this.options?.map((o) => o.toJSON()) ?? [], + defaultMemberPermissions: this.defaultMemberPermissions, + nameLocalizations: this.nameLocalizations, + descriptionLocalizations: this.descriptionLocalizations, + dmPermission: this.dmPermission, + }; + } +} + +OptionBased.applyTo(ChatInputApplicationCommandBuilder); + +export interface ChatInputApplicationCommandBuilder extends ApplicationCommandBuilder, OptionBased { + // pass +} diff --git a/packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts b/packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts new file mode 100644 index 0000000..d475cd8 --- /dev/null +++ b/packages/biscuit/structures/builders/slash/ApplicationCommandOption.ts @@ -0,0 +1,343 @@ +import { ApplicationCommandOptionTypes, type ChannelTypes, type Localization } from "../../../../discordeno/mod.ts"; +import { ApplicationCommandOptionChoice } from "../../interactions/CommandInteraction.ts" + +export class ChoiceBuilder { + public name?: string; + public value?: string; + + public setName(name: string) { + this.name = name; + return this; + } + + public setValue(value: string) { + this.value = value; + return this; + } + + public toJSON(): ApplicationCommandOptionChoice { + if (!this.name) throw new TypeError("Property 'name' is required"); + if (!this.value) throw new TypeError("Property 'value' is required"); + + return { + name: this.name, + value: this.value, + }; + } +} + +export class OptionBuilder { + public required?: boolean; + public autocomplete?: boolean; + + public constructor(public type?: ApplicationCommandOptionTypes, public name?: string, public description?: string) { + this.type = type; + this.name = name; + this.description = description; + } + + public setType(type: ApplicationCommandOptionTypes) { + return (this.type = type), this; + } + + public setName(name: string) { + return (this.name = name), this; + } + + public setDescription(description: string) { + return (this.description = description), this; + } + + public setRequired(required: boolean) { + return (this.required = required), this; + } + + public toJSON(): ApplicationCommandOption { + if (!this.type) throw new TypeError("Property 'type' is required"); + if (!this.name) throw new TypeError("Property 'name' is required"); + if (!this.description) { + throw new TypeError("Property 'description' is required"); + } + + const applicationCommandOption: ApplicationCommandOption = { + type: this.type, + name: this.name, + description: this.description, + required: this.required ? true : false, + }; + + return applicationCommandOption; + } +} + +export class OptionBuilderLimitedValues extends OptionBuilder { + public choices?: ChoiceBuilder[]; + public minValue?: number; + public maxValue?: number; + + public constructor( + public type?: ApplicationCommandOptionTypes.Integer | ApplicationCommandOptionTypes.Number, + public name?: string, + public description?: string, + ) { + super(); + this.type = type; + this.name = name; + this.description = description; + } + + public setMinValue(n: number) { + return (this.minValue = n), this; + } + + public setMaxValue(n: number) { + return (this.maxValue = n), this; + } + + public addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder) { + const choice = fn(new ChoiceBuilder()); + this.choices ??= []; + this.choices.push(choice); + return this; + } + + public override toJSON(): ApplicationCommandOption { + return { + ...super.toJSON(), + choices: this.choices?.map((c) => c.toJSON()) ?? [], + minValue: this.minValue, + maxValue: this.maxValue, + }; + } +} + +export class OptionBuilderString extends OptionBuilder { + public choices?: ChoiceBuilder[]; + public constructor( + public type?: ApplicationCommandOptionTypes.String, + public name?: string, + public description?: string, + ) { + super(); + this.type = type; + this.name = name; + this.description = description; + this; + } + + public addChoice(fn: (choice: ChoiceBuilder) => ChoiceBuilder) { + const choice = fn(new ChoiceBuilder()); + this.choices ??= []; + this.choices.push(choice); + return this; + } + + public override toJSON(): ApplicationCommandOption { + return { + ...super.toJSON(), + choices: this.choices?.map((c) => c.toJSON()) ?? [], + }; + } +} + +export class OptionBuilderChannel extends OptionBuilder { + public channelTypes?: ChannelTypes[]; + public constructor( + public type?: ApplicationCommandOptionTypes.Channel, + public name?: string, + public description?: string, + ) { + super(); + this.type = type; + this.name = name; + this.description = description; + this; + } + + public addChannelTypes(...channels: ChannelTypes[]) { + this.channelTypes ??= []; + this.channelTypes.push(...channels); + return this; + } + + public override toJSON(): ApplicationCommandOption { + return { + ...super.toJSON(), + channelTypes: this.channelTypes ?? [], + }; + } +} + +export interface OptionBuilderLike { + toJSON(): ApplicationCommandOption; +} + +export class OptionBased { + public options?: + & ( + | OptionBuilder[] + | OptionBuilderString[] + | OptionBuilderLimitedValues[] + | OptionBuilderNested[] + | OptionBuilderChannel[] + ) + & OptionBuilderLike[]; + + public addOption(fn: (option: OptionBuilder) => OptionBuilder, type?: ApplicationCommandOptionTypes) { + const option = fn(new OptionBuilder(type)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addNestedOption(fn: (option: OptionBuilder) => OptionBuilder) { + const option = fn(new OptionBuilder(ApplicationCommandOptionTypes.SubCommand)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addStringOption(fn: (option: OptionBuilderString) => OptionBuilderString) { + const option = fn(new OptionBuilderString(ApplicationCommandOptionTypes.String)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addIntegerOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues) { + const option = fn(new OptionBuilderLimitedValues(ApplicationCommandOptionTypes.Integer)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addNumberOption(fn: (option: OptionBuilderLimitedValues) => OptionBuilderLimitedValues) { + const option = fn(new OptionBuilderLimitedValues(ApplicationCommandOptionTypes.Number)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addBooleanOption(fn: (option: OptionBuilder) => OptionBuilder) { + return this.addOption(fn, ApplicationCommandOptionTypes.Boolean); + } + + public addSubCommand(fn: (option: OptionBuilderNested) => OptionBuilderNested) { + const option = fn(new OptionBuilderNested(ApplicationCommandOptionTypes.SubCommand)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addSubCommandGroup(fn: (option: OptionBuilderNested) => OptionBuilderNested) { + const option = fn(new OptionBuilderNested(ApplicationCommandOptionTypes.SubCommandGroup)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addUserOption(fn: (option: OptionBuilder) => OptionBuilder) { + return this.addOption(fn, ApplicationCommandOptionTypes.User); + } + + public addChannelOption(fn: (option: OptionBuilderChannel) => OptionBuilderChannel) { + const option = fn(new OptionBuilderChannel(ApplicationCommandOptionTypes.Channel)); + this.options ??= []; + this.options.push(option); + return this; + } + + public addRoleOption(fn: (option: OptionBuilder) => OptionBuilder) { + return this.addOption(fn, ApplicationCommandOptionTypes.Role); + } + + public addMentionableOption(fn: (option: OptionBuilder) => OptionBuilder) { + return this.addOption(fn, ApplicationCommandOptionTypes.Mentionable); + } + + // deno-lint-ignore ban-types + public static applyTo(klass: Function, ignore: Array = []) { + const methods: Array = [ + "addOption", + "addNestedOption", + "addStringOption", + "addIntegerOption", + "addNumberOption", + "addBooleanOption", + "addSubCommand", + "addSubCommandGroup", + "addUserOption", + "addChannelOption", + "addRoleOption", + "addMentionableOption", + ]; + + for (const method of methods) { + if (ignore.includes(method)) continue; + + klass.prototype[method] = OptionBased.prototype[method]; + } + } +} + +export class OptionBuilderNested extends OptionBuilder { + public constructor( + public type?: ApplicationCommandOptionTypes.SubCommand | ApplicationCommandOptionTypes.SubCommandGroup, + public name?: string, + public description?: string, + ) { + super(); + this.type = type; + this.name = name; + this.description = description; + } + + public override toJSON(): ApplicationCommandOption { + if (!this.type) throw new TypeError("Property 'type' is required"); + if (!this.name) throw new TypeError("Property 'name' is required"); + if (!this.description) { + throw new TypeError("Property 'description' is required"); + } + + return { + type: this.type, + name: this.name, + description: this.description, + options: this.options?.map((o) => o.toJSON()) ?? [], + required: this.required ? true : false, + }; + } +} + +OptionBased.applyTo(OptionBuilderNested); + +export interface OptionBuilderNested extends OptionBuilder, OptionBased { + // pass +} + +export interface ApplicationCommandOption { + /** Value of Application Command Option Type */ + type: ApplicationCommandOptionTypes; + /** 1-32 character name matching lowercase `^[\w-]{1,32}$` */ + name: string; + /** Localization object for the `name` field. Values follow the same restrictions as `name` */ + nameLocalizations?: Localization; + /** 1-100 character description */ + description: string; + /** Localization object for the `description` field. Values follow the same restrictions as `description` */ + descriptionLocalizations?: Localization; + /** If the parameter is required or optional--default `false` */ + required?: boolean; + /** Choices for `string` and `int` types for the user to pick from */ + choices?: ApplicationCommandOptionChoice[]; + /** If the option is a subcommand or subcommand group type, this nested options will be the parameters */ + options?: ApplicationCommandOption[]; + /** if autocomplete interactions are enabled for this `String`, `Integer`, or `Number` type option */ + autocomplete?: boolean; + /** If the option is a channel type, the channels shown will be restricted to these types */ + channelTypes?: ChannelTypes[]; + /** Minimum number desired. */ + minValue?: number; + /** Maximum number desired. */ + maxValue?: number; +} diff --git a/packages/biscuit/structures/interactions/CommandInteraction.ts b/packages/biscuit/structures/interactions/CommandInteraction.ts index 74c4d5f..f112b0b 100644 --- a/packages/biscuit/structures/interactions/CommandInteraction.ts +++ b/packages/biscuit/structures/interactions/CommandInteraction.ts @@ -38,7 +38,7 @@ export interface InteractionApplicationCommandCallbackData // components?: MessageComponents; flags?: MessageFlags; choices?: ApplicationCommandOptionChoice[]; -} +} /** * @link https://discord.com/developers/docs/interactions/slash-commands#applicationcommandoptionchoice