From d187354c5f826e5229718ccec61b8848d61458bb Mon Sep 17 00:00:00 2001 From: DidaS Date: Mon, 18 Nov 2024 21:04:15 +0000 Subject: [PATCH 01/25] The compiler is now its own thing `createClient` has been removed `Client` class has several changes --- packages/core/package.json | 2 +- packages/core/src/client.ts | 555 +----------------- packages/core/src/compiler.ts | 469 +++++++++++++++ packages/core/src/index.ts | 1 + packages/core/src/typings/client.ts | 176 +++--- packages/core/src/ws/manager.ts | 20 +- packages/create/eslint.config.js | 2 +- packages/create/globals.d.ts | 5 + packages/create/package.json | 2 +- packages/create/src/templates.cts | 4 +- .../create/src/templates/basic-template.ts | 15 +- .../create/src/templates/handlers-template.ts | 20 +- .../create/src/templates/listener-template.ts | 2 +- .../src/templates/transformers-template.ts | 32 +- packages/create/tsconfig.json | 9 +- packages/handlers/package.json | 17 +- .../application-command-store.ts | 0 .../handlers/src/{advanced => }/handler.ts | 15 +- packages/handlers/src/{advanced => }/index.ts | 0 .../{advanced => }/message-component-store.ts | 5 +- .../handlers/src/{advanced => }/shared.ts | 0 .../src/simple/application-command.ts | 21 - packages/handlers/src/simple/events.ts | 11 - packages/handlers/src/simple/handler.ts | 223 ------- packages/handlers/src/simple/index.ts | 10 - .../handlers/src/simple/message-commands.ts | 10 - packages/test/globals.d.ts | 10 +- .../test/src/commands-adv/bananas-poll.ts | 39 -- packages/test/src/commands-adv/ping.ts | 14 - packages/test/src/commands/bananas-poll.ts | 14 +- packages/test/src/commands/ping.ts | 26 +- .../src/{commands-adv => commands}/search.ts | 0 packages/test/src/commands/search.tsx | 111 ---- .../test/src/{events-adv => events}/ready.ts | 2 +- packages/test/src/events/ready.tsx | 8 - packages/test/src/handlers.ts | 11 +- packages/test/src/index.ts | 24 +- packages/transformers/package.json | 4 +- packages/transformers/src/index.ts | 8 +- packages/transformers/src/transformers.ts | 153 ++--- 40 files changed, 788 insertions(+), 1262 deletions(-) create mode 100644 packages/core/src/compiler.ts create mode 100644 packages/create/globals.d.ts rename packages/handlers/src/{advanced => }/application-command-store.ts (100%) rename packages/handlers/src/{advanced => }/handler.ts (96%) rename packages/handlers/src/{advanced => }/index.ts (100%) rename packages/handlers/src/{advanced => }/message-component-store.ts (97%) rename packages/handlers/src/{advanced => }/shared.ts (100%) delete mode 100644 packages/handlers/src/simple/application-command.ts delete mode 100644 packages/handlers/src/simple/events.ts delete mode 100644 packages/handlers/src/simple/handler.ts delete mode 100644 packages/handlers/src/simple/index.ts delete mode 100644 packages/handlers/src/simple/message-commands.ts delete mode 100644 packages/test/src/commands-adv/bananas-poll.ts delete mode 100644 packages/test/src/commands-adv/ping.ts rename packages/test/src/{commands-adv => commands}/search.ts (100%) delete mode 100644 packages/test/src/commands/search.tsx rename packages/test/src/{events-adv => events}/ready.ts (68%) delete mode 100644 packages/test/src/events/ready.tsx diff --git a/packages/core/package.json b/packages/core/package.json index a310ab7..8b51882 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "lilybird", - "version": "0.8.0", + "version": "0.9.0-beta.0", "description": "A bun-first discord api wrapper written in TS", "main": "./dist/index.js", "author": "DidaS", diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index df0d830..5337614 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -2,90 +2,59 @@ import { CachingManager } from "./cache/manager.js"; import { DebugREST, REST } from "./http/rest.js"; import { WebSocketManager } from "#ws"; -import { - CachingDelegationType, - TransformerReturnType, - CacheExecutionPolicy, - CacheElementType, - DebugIdentifier, - GatewayEvent -} from "#enums"; - import type { DispatchFunction } from "#ws"; + import type { - UpdatePresenceStructure, CacheManagerStructure, - ParseCachingManager, - BaseClientOptions, - SelectiveCache, ClientOptions, DebugFunction, Transformers, - Application, - Transformer + Application } from "./typings/index.js"; -type GetUserType = (T["userUpdate"] & {}) extends { handler: ((...args: infer U) => infer R) } +type GetUserType> = (T["userUpdate"] & {}) extends { handler: ((...args: infer U) => infer R) } ? unknown extends R ? U[1] : R : never ; -export interface Client { - readonly user: GetUserType; - readonly sessionId: string; - readonly application: Application.Structure; -} - -/* - I will probably end up making them props - but for now we use declaration merging - this is safe because as the library is meant to be used - the client will always have this properties defined - once the user can interact with it - - This however might not be true if the user - extends the class or tries to create its own instance -*/ -// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging -export class Client { +export class Client { public readonly rest: REST; - public readonly cache: C; + public readonly cache: CacheManagerStructure; + public readonly ws: WebSocketManager; - readonly #ws: WebSocketManager; - readonly #debug: DebugFunction; + public readonly declare user: GetUserType>; + public readonly declare sessionId: string; + public readonly declare application: Application.Structure; + protected readonly declare ready: boolean; - protected readonly ready: boolean = false; + readonly #dispatch?: DispatchFunction; - public constructor(options: BaseClientOptions, debug?: DebugFunction) { + public constructor(options: ClientOptions, debug?: DebugFunction) { this.rest = options.useDebugRest === true ? new DebugREST(debug) : new REST(); - this.cache = typeof options.caching?.manager !== "undefined" ? options.caching.manager : new CachingManager(); - // eslint-disable-next-line @typescript-eslint/no-empty-function - this.#debug = debug ?? (() => {}); - this.#ws = new WebSocketManager( + this.cache = typeof options.cachingManager !== "undefined" ? options.cachingManager : new CachingManager(); + this.ws = new WebSocketManager( { intents: options.intents, presence: options.presence }, - this.#generateListeners(options), debug ); + + this.#dispatch = options.dispatch; } - public async login(token: string): Promise { - this.#ws.options = { token }; + public async login(token: string, dispatch: DispatchFunction | undefined = this.#dispatch): Promise { + if (typeof dispatch === "undefined") throw new Error("the client doesn't have any 'dispatch' function defined."); + this.ws.init(token, dispatch); this.rest.setToken(token); - await this.#ws.connect(); + await this.ws.connect(); return token; } public close(): void { this.rest.setToken(undefined); - this.#ws.close(); - } - - public setPresence(presence: UpdatePresenceStructure): void { - this.#ws.updatePresence(presence); + this.ws.close(); } /** Both numbers are represented in `ms` */ @@ -95,488 +64,8 @@ export class Client): DispatchFunction { - const builder = new Map(); - const functions = new Map any>(); - - const { listeners, caching } = options; - const transformers = (options.transformers ?? {}) as Record>; - - //#region Raw Listener - if (typeof listeners.raw !== "undefined") { - functions.set("raw", listeners.raw); - builder.set("RAW", "await raw(data);"); - } - //#endregion - //#region Ready Listener - const readyArr = []; - readyArr.push( - "if(data.t === \"READY\"){", - "Object.assign(client,{user:" - ); - - if (typeof transformers.userUpdate !== "undefined") { - if (transformers.userUpdate.return === TransformerReturnType.MULTIPLE) throw new Error("The transformer for 'userUpdate' should only return 1 value"); - functions.set("t_userUpdate", transformers.userUpdate.handler); - readyArr.push("await t_userUpdate(client, data.d.user)"); - } else readyArr.push("data.d.user"); - - readyArr.push( - ",sessionId:data.d.session_id,application:data.d.application});", - "client.__updateResumeInfo(data.d.resume_gateway_url, data.d.session_id);", - "if(!client.ready){client.ready=true;" - ); - - if (typeof options.setup !== "undefined") { - functions.set("setup", options.setup); - readyArr.push("await setup(client);"); - } - - readyArr.push("}"); - - if (typeof listeners.ready !== "undefined") { - functions.set("ready", listeners.ready); - - const transformer = transformers.ready; - - if (typeof transformer !== "undefined") { - functions.set("t_ready", transformer.handler); - switch (transformer.return) { - case TransformerReturnType.SINGLE: { - readyArr.push("await ready(t_ready(client, data.d));"); - break; - } - case TransformerReturnType.MULTIPLE: { - readyArr.push("await ready(...t_ready(client, data.d));"); - break; - } - } - } else readyArr.push("await ready(client, data.d);"); - } - - readyArr.push("}"); - builder.set("READY", readyArr.join("")); - //#endregion - //#region User defined listeners - for (let i = 0, listenerEntries = Object.entries(listeners), { length } = listenerEntries; i < length; i++) { - const [handlerName, handler] = listenerEntries[i] as [string, () => unknown]; - if (handlerName === "raw" || handlerName === "ready") continue; - if (typeof handler === "undefined") continue; - - const name = `${handlerName[0].toUpperCase()}${handlerName.slice(1)}`; - const event: GatewayEvent = GatewayEvent[name]; - const transformer = transformers[handlerName]; - - this.#createListener( - builder, - functions, - event, - name, - handler, - transformer - ); - } - //#endregion - //#region Cache handlers - if (typeof caching !== "undefined" && caching.delegate !== CachingDelegationType.TRANSFORMERS) { - if (caching.delegate === CachingDelegationType.EXTERNAL && caching.applyTransformers === true && !caching.safeToTransform) { - process.emitWarning("Transformers will be applied to external solution!", { - code: "EXTERNAL_TRANSFORMERS", - detail: "Some external caching managers might not support transformers, please read their documentation before proceeding." - }); - } - - if (Object.keys(caching.enabled).length === 0) throw new Error("Got unexpected empty object"); - - const defaults: SelectiveCache = { - create: CacheExecutionPolicy.FIRST, - update: CacheExecutionPolicy.LAST, - delete: CacheExecutionPolicy.LAST - }; - - // const enabledUser = caching.enabled.user === true ? defaults : typeof caching.enabled.user === "object" ? caching.enabled.user : undefined; - const enabledGuild = caching.enabled.guild === true ? defaults : typeof caching.enabled.guild === "object" ? caching.enabled.guild : undefined; - const enabledChannel = caching.enabled.channel === true ? defaults : typeof caching.enabled.channel === "object" ? caching.enabled.channel : undefined; - const enabledThreads = caching.enabled.channel === true - ? defaults - : typeof caching.enabled.channel === "object" - ? caching.enabled.channel.threads === true - ? defaults - : typeof caching.enabled.channel.threads === "object" - ? caching.enabled.channel.threads - : undefined - : undefined; - - if (typeof caching.enabled.voiceState !== "undefined" && typeof enabledGuild?.create === "undefined") throw new Error("The 'voiceState' cache needs the `guildCreate' cache to be enabled"); - - if (typeof enabledGuild?.create !== "undefined") { - const temp: Array = []; - if (typeof enabledChannel?.create !== "undefined") { - if (caching.applyTransformers) { - if (!functions.has("t_channelCreate")) { - if (typeof transformers.channelCreate === "undefined") throw Error("Missing 'channelCreate' transformer"); - functions.set("t_channelCreate", transformers.channelCreate.handler); - } - } - temp.push( - "for (let i = 0, {channels} = td, {length} = channels; i < length; i++){", - `const channel = ${caching.applyTransformers ? "t_channelCreate(client, channels[i])" : "channels[i]"};`, - `await client.cache.set(${CacheElementType.CHANNEL}, channel.id, channel);`, - "}" - ); - } - - if (typeof enabledThreads?.create !== "undefined") { - if (caching.applyTransformers) { - if (!functions.has("t_channelCreate")) { - if (typeof transformers.channelCreate === "undefined") throw Error("Missing 'channelCreate' transformer"); - functions.set("t_channelCreate", transformers.channelCreate.handler); - } - } - - temp.push( - "for (let i = 0, {threads} = td, {length} = threads; i < length; i++){", - `const channel = ${caching.applyTransformers ? "t_channelCreate(client, threads[i])" : "threads[i]"};`, - `await client.cache.set(${CacheElementType.CHANNEL}, channel.id, channel);`, - "}" - ); - } - - if (typeof caching.enabled.voiceState !== "undefined") { - if (caching.applyTransformers) { - if (!functions.has("t_voiceStateUpdate")) { - if (typeof transformers.voiceStateUpdate === "undefined") throw Error("Missing 'voiceStateUpdate' transformer"); - functions.set("t_voiceStateUpdate", transformers.voiceStateUpdate.handler); - } - } - const key = caching.customKeys?.guild_voice_states ?? "voice_states"; - const vCid = caching.customKeys?.voice_state_channel_id ?? "channel_id"; - const vUid = caching.customKeys?.voice_state_user_id ?? "user_id"; - - this.#createListener( - builder, - functions, - GatewayEvent.VoiceStateUpdate, - "voiceStateUpdate", - listeners.voiceStateUpdate, - transformers.voiceStateUpdate, - { - when: caching.enabled.voiceState, - content: `await client.cache.set(${CacheElementType.VOICE_STATE}, ${caching.applyTransformers - ? `\`\${td.${vUid}}:\${td.${vCid}}\`` - : `\`\${data.d.${vUid}}:\${data.d.${vCid}}\``}, data.d);` - } - ); - - temp.push( - `for (let i = 0, {${key}} = td, {length} = ${key}; i < length; i++){`, - `const voice = ${caching.applyTransformers ? `t_voiceStateUpdate(client, ${key}[i])` : `${key}[i]`};`, - `await client.cache.set(${CacheElementType.VOICE_STATE}, \`\${voice.${vUid}}:\${voice.${vCid}}\`, voice);`, - "}" - ); - } - - temp.push( - `await client.cache.set(${CacheElementType.GUILD},`, - caching.applyTransformers ? "td.id, td" : "data.d.id, {...data.d,channels: undefined,threads:undefined,voice_states:undefined}", - ");" - ); - - this.#createListener( - builder, - functions, - GatewayEvent.GuildCreate, - "guildCreate", - listeners.guildCreate, - transformers.guildCreate, - { when: enabledGuild.create, content: temp.join("") } - ); - } - if (typeof enabledGuild?.update !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.GuildUpdate, - "guildUpdate", - listeners.guildUpdate, - transformers.guildUpdate, - { - when: enabledGuild.update, - content: `await client.cache.set(${CacheElementType.GUILD}, ${caching.applyTransformers ? "td.id, td" : "data.d.id, data.d"});` - } - ); - } - if (typeof enabledGuild?.delete !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.GuildDelete, - "guildDelete", - listeners.guildDelete, - transformers.guildDelete, - { - when: enabledGuild.delete, - content: `await client.cache.delete(${CacheElementType.GUILD}, ${caching.applyTransformers ? "td.id" : "data.d.id"});` - } - ); - } - if (typeof enabledChannel?.create !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.ChannelCreate, - "channelCreate", - listeners.channelCreate, - transformers.channelCreate, - { - when: enabledChannel.create, - content: `await client.cache.set(${CacheElementType.CHANNEL}, ${caching.applyTransformers ? "td.id, td" : "data.d.id, data.d"});` - } - ); - } - if (typeof enabledChannel?.update !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.ChannelUpdate, - "channelUpdate", - listeners.channelUpdate, - transformers.channelUpdate, - { - when: enabledChannel.update, - content: `await client.cache.set(${CacheElementType.CHANNEL}, ${caching.applyTransformers ? "td.id, td" : "data.d.id, data.d"});` - } - ); - } - if (typeof enabledChannel?.delete !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.ChannelDelete, - "channelDelete", - listeners.channelDelete, - transformers.channelDelete, - { - when: enabledChannel.delete, - content: `await client.cache.delete(${CacheElementType.CHANNEL}, ${caching.applyTransformers ? "td.id" : "data.d.id"});` - } - ); - } - if (typeof enabledThreads?.create !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.ThreadCreate, - "threadCreate", - listeners.threadCreate, - transformers.threadCreate, - { - when: enabledThreads.create, - content: `await client.cache.set(${CacheElementType.CHANNEL}, ${caching.applyTransformers ? "td.id, td" : "data.d.id, data.d"});` - } - ); - } - if (typeof enabledThreads?.update !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.ThreadUpdate, - "threadUpdate", - listeners.threadUpdate, - transformers.threadUpdate, - { - when: enabledThreads.update, - content: `await client.cache.set(${CacheElementType.CHANNEL}, ${caching.applyTransformers ? "td.id, td" : "data.d.id, data.d"});` - } - ); - } - if (typeof enabledThreads?.delete !== "undefined") { - this.#createListener( - builder, - functions, - GatewayEvent.ThreadDelete, - "threadDelete", - listeners.threadDelete, - transformers.threadDelete, - { - when: enabledThreads.delete, - content: `await client.cache.delete(${CacheElementType.CHANNEL}, ${caching.applyTransformers ? "td.id" : "data.d.id"});` - } - ); - } - if (typeof caching.enabled.self !== "undefined") { - if (caching.applyTransformers) { - if (!functions.has("t_userUpdate")) { - if (typeof transformers.userUpdate === "undefined") throw new Error("Missing 'userUpdate' transformer"); - if (transformers.userUpdate.return === TransformerReturnType.MULTIPLE) throw new Error("The transformer for 'userUpdate' should only return 1 value"); - functions.set("t_userUpdate", transformers.userUpdate.handler); - } - } - - this.#createListener( - builder, - functions, - GatewayEvent.UserUpdate, - "userUpdate", - listeners.userUpdate, - transformers.userUpdate, - { - when: caching.enabled.self, - content: `client.user = ${caching.applyTransformers ? "td" : "data.d"}` - } - ); - } - } - //#endregion - - const names = functions.keys(); - const handlers = functions.values(); - const compiledListeners = [...builder.values()].join(""); - this.#debug(DebugIdentifier.CompiledListeners, compiledListeners); - // eslint-disable-next-line @typescript-eslint/no-implied-eval - return new Function("client", ...names, `return async (data) => { ${compiledListeners} }`)(this, ...handlers) as never; - } - - #createListener( - builder: Map, - functions: Map any>, - event: GatewayEvent, - name: string, - handler: ((...args: any) => any) | undefined, - transformer: Transformer | undefined, - extra: { when: CacheExecutionPolicy, content: string } | undefined = undefined - ): void { - const temp = [`else if(data.t === "${event}"){`]; - if (typeof handler !== "undefined") functions.set(name, handler); - - const transf = `t_${name}`; - if (typeof transformer !== "undefined") { - functions.set(transf, transformer.handler); - - switch (transformer.return) { - case TransformerReturnType.SINGLE: { - if (typeof extra !== "undefined") { - if (extra.when === CacheExecutionPolicy.FIRST) { - temp.push( - `const td = await ${transf}(client, data.d);`, - extra.content, - typeof handler !== "undefined" ? `await ${name}(td)` : "" - ); - } else { - temp.push( - `const td = await ${transf}(client, data.d);`, - typeof handler !== "undefined" ? `await ${name}(td)` : "", - extra.content - ); - } - } else if (typeof handler !== "undefined") temp.push(`await ${name}(await ${transf}(client, data.d));`); - break; - } - case TransformerReturnType.MULTIPLE: { - if (typeof extra !== "undefined") { - if (extra.when === CacheExecutionPolicy.FIRST) { - temp.push( - `const td = await ${transf}(client, data.d);`, - extra.content, - typeof handler !== "undefined" ? `await ${name}(...td);` : "" - ); - } else { - temp.push( - `const td = await ${transf}(client, data.d);`, - typeof handler !== "undefined" ? `await ${name}(...td);` : "", - extra.content - ); - } - } else if (typeof handler !== "undefined") temp.push(`await ${name}(...(await ${transf}(client, data.d)));`); - break; - } - } - } else if (typeof extra !== "undefined") { - if (extra.when === CacheExecutionPolicy.FIRST) - temp.push("const td = data.d;", extra.content, typeof handler !== "undefined" ? `await ${name}(client, td);` : ""); - else - temp.push("const td = data.d;", typeof handler !== "undefined" ? `await ${name}(client, td);` : "", extra.content); - } else if (typeof handler !== "undefined") temp.push(`await ${name}(client, data.d);`); - - if (temp.length === 1) return; - - // Dead code elimination - // transformed data sometimes might not be needed and its easier to do it this way - // than adding more complexity to all the branches that add listeners - if (temp.length === 4) { - if (temp[2] === "") { - // eslint-disable-next-line @typescript-eslint/prefer-destructuring - temp[2] = temp[3]; - temp.pop(); - } else if (temp[3] === "") - temp.pop(); - - if (!temp[1].startsWith("const td =")) - throw new Error("There was something wrong internally with the compiler"); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (temp.length === 4 && !temp[3].includes("td")) { - if (!temp[2].includes("td")) { - // eslint-disable-next-line @typescript-eslint/prefer-destructuring - temp[1] = temp[2]; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - temp[2] = temp.pop()!; - functions.delete(transf); - } - //@ts-expect-error We are rechecking because we modify the length inside this case - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - } else if (temp.length === 3 && !temp[2].includes("td")) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - temp[1] = temp.pop()!; - functions.delete(transf); - } - } - - temp.push("}"); - - builder.set(event, temp.join("")); - } -} - -export async function createClient = ClientOptions>(options: O): Promise>> { - if (typeof options.caching?.customKeys !== "undefined") options.caching.customKeys = { ...options.customCacheKeys, ...options.caching.customKeys }; - else if (typeof options.caching !== "undefined") options.caching.customKeys = options.customCacheKeys; - - return new Promise((res) => { - // This is a promise executer, it doesn't need to be async - // eslint-disable-next-line @typescript-eslint/no-floating-promises - new Client( - { - intents: Array.isArray(options.intents) ? options.intents.reduce((prev, curr) => prev | curr, 0) : options.intents, - listeners: options.listeners, - transformers: options.transformers, - presence: options.presence, - caching: options.caching, - useDebugRest: options.useDebugRest ?? options.attachDebugListener, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - setup: typeof options.setup !== "undefined" ? async (client) => { await options.setup!(client); res(client); } : res - }, - options.attachDebugListener - ? options.debugListener ?? ((identifier, payload) => { - console.log(identifier, payload ?? ""); - }) - : undefined - ).login(options.token); - }); } diff --git a/packages/core/src/compiler.ts b/packages/core/src/compiler.ts new file mode 100644 index 0000000..ce5d8c4 --- /dev/null +++ b/packages/core/src/compiler.ts @@ -0,0 +1,469 @@ +import { + CachingDelegationType, + TransformerReturnType, + CacheExecutionPolicy, + CacheElementType, + GatewayEvent +} from "#enums"; + +import type { DispatchFunction } from "#ws"; +import type { + ReceiveDispatchEvent, + SelectiveCache, + CachingOptions, + Transformers, + Transformer, + MockClient, + Awaitable, + Listeners, + Ready +} from "./typings/index.js"; + +export class ListenerCompiler> { + readonly #stack: Map; + readonly #callbacks: Map any>; + + readonly #transformers: T; + readonly #shouldTransformClientUser: boolean; + + public constructor(options: { transformers?: T, transformClient?: boolean }) { + this.#stack = new Map(); + this.#callbacks = new Map(); + this.#transformers = options.transformers ?? {}; + this.#shouldTransformClientUser = options.transformClient ?? false; + + // We want to guarantee the raw listener will always be the first thing on the stack even if its not included + this.#stack.set("raw", ""); + } + + #createListener( + event: GatewayEvent, + name: string, + extra: { when: CacheExecutionPolicy, content: string } | undefined = undefined + ): void { + const handler = this.#callbacks.get(name); + const transformer: Transformer = this.#transformers[name]; + const temp = [`else if(payload.t === "${event}"){`]; + const transf = `t_${name}`; + + if (typeof transformer !== "undefined") { + this.#callbacks.set(transf, transformer.handler); + + switch (transformer.return) { + case TransformerReturnType.SINGLE: { + if (typeof extra !== "undefined") { + if (extra.when === CacheExecutionPolicy.FIRST) { + temp.push( + `const td = await ${transf}(client_ptr, payload.d);`, + extra.content, + typeof handler !== "undefined" ? `await ${name}(td)` : "" + ); + } else { + temp.push( + `const td = await ${transf}(client_ptr, payload.d);`, + typeof handler !== "undefined" ? `await ${name}(td)` : "", + extra.content + ); + } + } else if (typeof handler !== "undefined") temp.push(`await ${name}(await ${transf}(client_ptr, payload.d));`); + break; + } + case TransformerReturnType.MULTIPLE: { + if (typeof extra !== "undefined") { + if (extra.when === CacheExecutionPolicy.FIRST) { + temp.push( + `const td = await ${transf}(client_ptr, payload.d);`, + extra.content, + typeof handler !== "undefined" ? `await ${name}(...td);` : "" + ); + } else { + temp.push( + `const td = await ${transf}(client_ptr, payload.d);`, + typeof handler !== "undefined" ? `await ${name}(...td);` : "", + extra.content + ); + } + } else if (typeof handler !== "undefined") temp.push(`await ${name}(...(await ${transf}(client_ptr, payload.d)));`); + break; + } + } + } else if (typeof extra !== "undefined") { + if (extra.when === CacheExecutionPolicy.FIRST) + temp.push("const td = payload.d;", extra.content, typeof handler !== "undefined" ? `await ${name}(client_ptr, td);` : ""); + else + temp.push("const td = payload.d;", typeof handler !== "undefined" ? `await ${name}(client_ptr, td);` : "", extra.content); + } else if (typeof handler !== "undefined") temp.push(`await ${name}(client_ptr, payload.d);`); + + if (temp.length === 1) return; + + // Dead code elimination + // transformed data sometimes might not be needed and its easier to do it this way + // than adding more complexity to all the branches that add listeners + if (temp.length === 4) { + if (temp[2] === "") { + // eslint-disable-next-line @typescript-eslint/prefer-destructuring + temp[2] = temp[3]; + temp.pop(); + } else if (temp[3] === "") + temp.pop(); + + if (!temp[1].startsWith("const td =")) + throw new Error("There was something wrong internally with the compiler"); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (temp.length === 4 && !temp[3].includes("td")) { + if (!temp[2].includes("td")) { + // eslint-disable-next-line @typescript-eslint/prefer-destructuring + temp[1] = temp[2]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + temp[2] = temp.pop()!; + this.#callbacks.delete(transf); + } + //@ts-expect-error We are rechecking because we modify the length inside this case + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + } else if (temp.length === 3 && !temp[2].includes("td")) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + temp[1] = temp.pop()!; + this.#callbacks.delete(transf); + } + } + + temp.push("}"); + + this.#stack.set(event, temp.join("")); + } + + //! TODO: Support a once listener and always listener simultaneously + #addReadyListener(once: boolean, listener?: (client: C, payload: Ready["d"]) => Awaitable): void { + const readyArr = []; + readyArr.push( + "if(payload.t === \"READY\"){", + "Object.assign(client_ptr,{user:" + ); + + if (this.#shouldTransformClientUser && typeof this.#transformers.userUpdate !== "undefined") { + if (this.#transformers.userUpdate.return === TransformerReturnType.MULTIPLE) throw new Error("The transformer for 'userUpdate' should only return 1 value"); + this.#callbacks.set("t_userUpdate", this.#transformers.userUpdate.handler); + readyArr.push("await t_userUpdate(client, payload.d.user)"); + } else readyArr.push("payload.d.user"); + + readyArr.push( + `,sessionId:payload.d.session_id,application:payload.d.application,ready:${once ? "false" : "true"}});`, + "Object.assign(resume_info_ptr{url:payload.d.resume_gateway_url,id:payload.d.session_id});" + ); + + if (typeof listener !== "undefined") { + if (once) readyArr.push("if(!client_ptr.ready){client_ptr.ready=true;"); + this.#callbacks.set("ready", listener); + + if (typeof this.#transformers.ready !== "undefined") { + this.#callbacks.set("t_ready", this.#transformers.ready.handler); + switch (this.#transformers.ready.return) { + case TransformerReturnType.SINGLE: { + readyArr.push("await ready(t_ready(client_ptr, payload.d));"); + break; + } + case TransformerReturnType.MULTIPLE: { + readyArr.push("await ready(...t_ready(client_ptr, payload.d));"); + break; + } + } + } else readyArr.push("await ready(client_ptr, payload.d);"); + if (once) readyArr.push("}"); + } + + readyArr.push("}"); + this.#stack.set("ready", readyArr.join("")); + } + + public appendCachingHandlers(options: CachingOptions): this { + if (typeof options !== "undefined" && options.delegate !== CachingDelegationType.TRANSFORMERS) { + if (options.delegate === CachingDelegationType.EXTERNAL && options.applyTransformers === true && !options.safeToTransform) { + process.emitWarning("Transformers will be applied to external solution!", { + code: "EXTERNAL_TRANSFORMERS", + detail: "Some external caching managers might not support transformers, please read their documentation before proceeding." + }); + } + + if (Object.keys(options.enabled).length === 0) throw new Error("Got unexpected empty object"); + + const defaults: SelectiveCache = { + create: CacheExecutionPolicy.FIRST, + update: CacheExecutionPolicy.LAST, + delete: CacheExecutionPolicy.LAST + }; + + // const enabledUser = caching.enabled.user === true ? defaults : typeof caching.enabled.user === "object" ? caching.enabled.user : undefined; + const enabledGuild = options.enabled.guild === true ? defaults : typeof options.enabled.guild === "object" ? options.enabled.guild : undefined; + const enabledChannel = options.enabled.channel === true ? defaults : typeof options.enabled.channel === "object" ? options.enabled.channel : undefined; + const enabledThreads = options.enabled.channel === true + ? defaults + : typeof options.enabled.channel === "object" + ? options.enabled.channel.threads === true + ? defaults + : typeof options.enabled.channel.threads === "object" + ? options.enabled.channel.threads + : undefined + : undefined; + + if (typeof options.enabled.voiceState !== "undefined" && typeof enabledGuild?.create === "undefined") throw new Error("The 'voiceState' cache needs the `guildCreate' cache to be enabled"); + + if (typeof enabledGuild?.create !== "undefined") { + const temp: Array = []; + if (typeof enabledChannel?.create !== "undefined") { + if (options.applyTransformers) { + if (!this.#callbacks.has("t_channelCreate")) { + if (typeof this.#transformers.channelCreate === "undefined") throw Error("Missing 'channelCreate' transformer"); + this.#callbacks.set("t_channelCreate", this.#transformers.channelCreate.handler); + } + } + temp.push( + "for (let i = 0, {channels} = td, {length} = channels; i < length; i++){", + `const channel = ${options.applyTransformers ? "t_channelCreate(client_ptr, channels[i])" : "channels[i]"};`, + `await client_ptr.cache.set(${CacheElementType.CHANNEL}, channel.id, channel);`, + "}" + ); + } + + if (typeof enabledThreads?.create !== "undefined") { + if (options.applyTransformers) { + if (!this.#callbacks.has("t_channelCreate")) { + if (typeof this.#transformers.channelCreate === "undefined") throw Error("Missing 'channelCreate' transformer"); + this.#callbacks.set("t_channelCreate", this.#transformers.channelCreate.handler); + } + } + + temp.push( + "for (let i = 0, {threads} = td, {length} = threads; i < length; i++){", + `const channel = ${options.applyTransformers ? "t_channelCreate(client_ptr, threads[i])" : "threads[i]"};`, + `await client_ptr.cache.set(${CacheElementType.CHANNEL}, channel.id, channel);`, + "}" + ); + } + + if (typeof options.enabled.voiceState !== "undefined") { + if (options.applyTransformers) { + if (!this.#callbacks.has("t_voiceStateUpdate")) { + if (typeof this.#transformers.voiceStateUpdate === "undefined") throw Error("Missing 'voiceStateUpdate' transformer"); + this.#callbacks.set("t_voiceStateUpdate", this.#transformers.voiceStateUpdate.handler); + } + } + const key = options.customKeys?.guild_voice_states ?? "voice_states"; + const vCid = options.customKeys?.voice_state_channel_id ?? "channel_id"; + const vUid = options.customKeys?.voice_state_user_id ?? "user_id"; + + this.#createListener( + GatewayEvent.VoiceStateUpdate, + "voiceStateUpdate", + { + when: options.enabled.voiceState, + content: `await client_ptr.cache.set(${CacheElementType.VOICE_STATE}, ${options.applyTransformers + ? `\`\${td.${vUid}}:\${td.${vCid}}\`` + : `\`\${payload.d.${vUid}}:\${payload.d.${vCid}}\``}, payload.d);` + } + ); + + temp.push( + `for (let i = 0, {${key}} = td, {length} = ${key}; i < length; i++){`, + `const voice = ${options.applyTransformers ? `t_voiceStateUpdate(client_ptr, ${key}[i])` : `${key}[i]`};`, + `await client_ptr.cache.set(${CacheElementType.VOICE_STATE}, \`\${voice.${vUid}}:\${voice.${vCid}}\`, voice);`, + "}" + ); + } + + temp.push( + `await client_ptr.cache.set(${CacheElementType.GUILD},`, + options.applyTransformers ? "td.id, td" : "payload.d.id, {...payload.d,channels: undefined,threads:undefined,voice_states:undefined}", + ");" + ); + + this.#createListener( + GatewayEvent.GuildCreate, + "guildCreate", + { when: enabledGuild.create, content: temp.join("") } + ); + } + if (typeof enabledGuild?.update !== "undefined") { + this.#createListener( + GatewayEvent.GuildUpdate, + "guildUpdate", + { + when: enabledGuild.update, + content: `await client_ptr.cache.set(${CacheElementType.GUILD}, ${options.applyTransformers ? "td.id, td" : "payload.d.id, payload.d"});` + } + ); + } + if (typeof enabledGuild?.delete !== "undefined") { + this.#createListener( + GatewayEvent.GuildDelete, + "guildDelete", + { + when: enabledGuild.delete, + content: `await client_ptr.cache.delete(${CacheElementType.GUILD}, ${options.applyTransformers ? "td.id" : "payload.d.id"});` + } + ); + } + if (typeof enabledChannel?.create !== "undefined") { + this.#createListener( + GatewayEvent.ChannelCreate, + "channelCreate", + { + when: enabledChannel.create, + content: `await client_ptr.cache.set(${CacheElementType.CHANNEL}, ${options.applyTransformers ? "td.id, td" : "payload.d.id, payload.d"});` + } + ); + } + if (typeof enabledChannel?.update !== "undefined") { + this.#createListener( + GatewayEvent.ChannelUpdate, + "channelUpdate", + { + when: enabledChannel.update, + content: `await client_ptr.cache.set(${CacheElementType.CHANNEL}, ${options.applyTransformers ? "td.id, td" : "payload.d.id, payload.d"});` + } + ); + } + if (typeof enabledChannel?.delete !== "undefined") { + this.#createListener( + GatewayEvent.ChannelDelete, + "channelDelete", + { + when: enabledChannel.delete, + content: `await client_ptr.cache.delete(${CacheElementType.CHANNEL}, ${options.applyTransformers ? "td.id" : "payload.d.id"});` + } + ); + } + if (typeof enabledThreads?.create !== "undefined") { + this.#createListener( + GatewayEvent.ThreadCreate, + "threadCreate", + { + when: enabledThreads.create, + content: `await client_ptr.cache.set(${CacheElementType.CHANNEL}, ${options.applyTransformers ? "td.id, td" : "payload.d.id, payload.d"});` + } + ); + } + if (typeof enabledThreads?.update !== "undefined") { + this.#createListener( + GatewayEvent.ThreadUpdate, + "threadUpdate", + { + when: enabledThreads.update, + content: `await client_ptr.cache.set(${CacheElementType.CHANNEL}, ${options.applyTransformers ? "td.id, td" : "payload.d.id, payload.d"});` + } + ); + } + if (typeof enabledThreads?.delete !== "undefined") { + this.#createListener( + GatewayEvent.ThreadDelete, + "threadDelete", + { + when: enabledThreads.delete, + content: `await client_ptr.cache.delete(${CacheElementType.CHANNEL}, ${options.applyTransformers ? "td.id" : "payload.d.id"});` + } + ); + } + if (typeof options.enabled.self !== "undefined") { + if (options.applyTransformers) { + if (!this.#callbacks.has("t_userUpdate")) { + if (typeof this.#transformers.userUpdate === "undefined") throw new Error("Missing 'userUpdate' transformer"); + if (this.#transformers.userUpdate.return === TransformerReturnType.MULTIPLE) throw new Error("The transformer for 'userUpdate' should only return 1 value"); + this.#callbacks.set("t_userUpdate", this.#transformers.userUpdate.handler); + } + } + + this.#createListener( + GatewayEvent.UserUpdate, + "userUpdate", + { + when: options.enabled.self, + content: `client_ptr.user = ${options.applyTransformers ? "td" : "payload.d"}` + } + ); + } + } + + return this; + } + + public addRawListener(listener: (payload: ReceiveDispatchEvent) => Awaitable): this { + this.#stack.set("raw", "await raw(payload)"); + this.#callbacks.set("raw", listener); + return this; + } + + /** + * @param name - The name of the event you are listening to + * @param handler - The handler that will be executed when the event is received + * @param once - Wether the `ready` listener should only be called once + * @returns + */ + public addListener = Listeners, K extends (keyof L) & string = (keyof L) & string>( + name: K, + handler: L[K] & {}, + once: boolean = false + ): this { + if (name === "raw") { + this.addRawListener(handler); + return this; + } + + if (name === "ready") { + this.#addReadyListener(once, handler); + return this; + } + + const parsedName = `${name[0].toUpperCase()}${name.slice(1)}`; + const event: GatewayEvent = GatewayEvent[parsedName]; + + this.#callbacks.set(name, handler); + + this.#createListener( + event, + name + ); + + return this; + } + + public getCompilationOutput(): { + handlers: { + names: Array, + callbacks: Array<(...args: any) => any> + }, + stack: string + } { + const names = [...this.#callbacks.keys()]; + const handlers = [...this.#callbacks.values()]; + const compiledListeners = [...this.#stack.values()].join(""); + + return { + handlers: { + names, + callbacks: handlers + }, + stack: compiledListeners + }; + } + + public clearStack(): void { + this.#stack.clear(); + this.#callbacks.clear(); + this.#stack.set("raw", ""); + } + + public getDispatchFunction(clientPointer: C, resumeInfoPointer: { url: string, id: string }): DispatchFunction { + const { handlers, stack } = this.getCompilationOutput(); + // eslint-disable-next-line @typescript-eslint/no-implied-eval + return new Function("client_ptr", "resume_info_ptr", ...handlers.names, `return async (payload) => { ${stack} }`)(clientPointer, resumeInfoPointer, ...handlers.callbacks) as never; + } + + public addListenersFromObject(listeners: Listeners): this { + for (let i = 0, entries = Object.entries(listeners), { length } = entries; i < length; i++) { + const [event, handler] = <[string, (...args: any) => any]>entries[i]; + // There is no need for us to strongly type this + this.addListener(event, handler); + } + + return this; + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ecc67ec..88d5864 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,5 +4,6 @@ export { REST } from "./http/rest.js"; export * as CDN from "./http/cdn.js"; export * from "./enums/index.js"; +export * from "./compiler.js"; export * from "./client.js"; export * from "./utils.js"; diff --git a/packages/core/src/typings/client.ts b/packages/core/src/typings/client.ts index 62b52be..64c6754 100644 --- a/packages/core/src/typings/client.ts +++ b/packages/core/src/typings/client.ts @@ -1,14 +1,14 @@ -import type { CachingManager } from "../cache/manager.js"; import type { CacheManagerStructure } from "./cache-manager.js"; +import type { CachingManager } from "../cache/manager.js"; +import type { DispatchFunction } from "../ws/manager.js"; +import type { Application } from "./application.js"; import type { Awaitable } from "./utils.js"; -import type { Client } from "../client.js"; import type { CachingDelegationType, TransformerReturnType, CacheExecutionPolicy, - DebugIdentifier, - Intents + DebugIdentifier } from "#enums"; import type { @@ -77,7 +77,7 @@ import type { Ready } from "./gateway-events.js"; -export type ClientListeners = { +export type Listeners> = { [K in keyof T]?: T[K] extends { handler: unknown } ? (T[K] & {})["handler"] extends ((...args: any) => infer R) ? R extends [unknown, ...Array] ? ((...arg: R) => Awaitable) : ((arg: R) => Awaitable) @@ -87,79 +87,79 @@ export type ClientListeners = { : never }; -export type Transformer = { +export type Transformer = { return: TransformerReturnType, - handler: (...args: [client: Client, payload: T]) => unknown + handler: (...args: [client: C, payload: T]) => unknown }; -export interface Transformers { +export interface Transformers { raw?: { return: TransformerReturnType, - handler: (data: ReceiveDispatchEvent) => unknown + handler: (payload: ReceiveDispatchEvent) => unknown }; - ready?: Transformer; - resumed?: Transformer; - applicationCommandPermissionsUpdate?: Transformer; - autoModerationRuleCreate?: Transformer; - autoModerationRuleUpdate?: Transformer; - autoModerationRuleDelete?: Transformer; - autoModerationActionExecution?: Transformer; - channelCreate?: Transformer; - channelUpdate?: Transformer; - channelDelete?: Transformer; - channelPinsUpdate?: Transformer; - threadCreate?: Transformer; - threadUpdate?: Transformer; - threadDelete?: Transformer; - threadListSync?: Transformer; - threadMemberUpdate?: Transformer; - threadMembersUpdate?: Transformer; - guildCreate?: Transformer; - guildUpdate?: Transformer; - guildDelete?: Transformer; - guildAuditLogEntryCreate?: Transformer; - guildBanAdd?: Transformer; - guildBanRemove?: Transformer; - guildEmojisUpdate?: Transformer; - guildStickersUpdate?: Transformer; - guildIntegrationsUpdate?: Transformer; - guildMemberAdd?: Transformer; - guildMemberRemove?: Transformer; - guildMemberUpdate?: Transformer; - guildMembersChunk?: Transformer; - guildRoleCreate?: Transformer; - guildRoleUpdate?: Transformer; - guildRoleDelete?: Transformer; - guildScheduledEventCreate?: Transformer; - guildScheduledEventUpdate?: Transformer; - guildScheduledEventDelete?: Transformer; - guildScheduledEventUserAdd?: Transformer; - guildScheduledEventUserRemove?: Transformer; - integrationCreate?: Transformer; - integrationUpdate?: Transformer; - integrationDelete?: Transformer; - interactionCreate?: Transformer; - inviteCreate?: Transformer; - inviteDelete?: Transformer; - messageCreate?: Transformer; - messageUpdate?: Transformer; - messageDelete?: Transformer; - messageDeleteBulk?: Transformer; - messageReactionAdd?: Transformer; - messageReactionRemove?: Transformer; - messageReactionRemoveAll?: Transformer; - messageReactionRemoveEmoji?: Transformer; - presenceUpdate?: Transformer; - stageInstanceCreate?: Transformer; - stageInstanceUpdate?: Transformer; - stageInstanceDelete?: Transformer; - typingStart?: Transformer; - userUpdate?: Transformer; - voiceStateUpdate?: Transformer; - voiceServerUpdate?: Transformer; - webhookUpdate?: Transformer; - messagePollVoteAdd?: Transformer; - messagePollVoteRemove?: Transformer; + ready?: Transformer; + resumed?: Transformer; + applicationCommandPermissionsUpdate?: Transformer; + autoModerationRuleCreate?: Transformer; + autoModerationRuleUpdate?: Transformer; + autoModerationRuleDelete?: Transformer; + autoModerationActionExecution?: Transformer; + channelCreate?: Transformer; + channelUpdate?: Transformer; + channelDelete?: Transformer; + channelPinsUpdate?: Transformer; + threadCreate?: Transformer; + threadUpdate?: Transformer; + threadDelete?: Transformer; + threadListSync?: Transformer; + threadMemberUpdate?: Transformer; + threadMembersUpdate?: Transformer; + guildCreate?: Transformer; + guildUpdate?: Transformer; + guildDelete?: Transformer; + guildAuditLogEntryCreate?: Transformer; + guildBanAdd?: Transformer; + guildBanRemove?: Transformer; + guildEmojisUpdate?: Transformer; + guildStickersUpdate?: Transformer; + guildIntegrationsUpdate?: Transformer; + guildMemberAdd?: Transformer; + guildMemberRemove?: Transformer; + guildMemberUpdate?: Transformer; + guildMembersChunk?: Transformer; + guildRoleCreate?: Transformer; + guildRoleUpdate?: Transformer; + guildRoleDelete?: Transformer; + guildScheduledEventCreate?: Transformer; + guildScheduledEventUpdate?: Transformer; + guildScheduledEventDelete?: Transformer; + guildScheduledEventUserAdd?: Transformer; + guildScheduledEventUserRemove?: Transformer; + integrationCreate?: Transformer; + integrationUpdate?: Transformer; + integrationDelete?: Transformer; + interactionCreate?: Transformer; + inviteCreate?: Transformer; + inviteDelete?: Transformer; + messageCreate?: Transformer; + messageUpdate?: Transformer; + messageDelete?: Transformer; + messageDeleteBulk?: Transformer; + messageReactionAdd?: Transformer; + messageReactionRemove?: Transformer; + messageReactionRemoveAll?: Transformer; + messageReactionRemoveEmoji?: Transformer; + presenceUpdate?: Transformer; + stageInstanceCreate?: Transformer; + stageInstanceUpdate?: Transformer; + stageInstanceDelete?: Transformer; + typingStart?: Transformer; + userUpdate?: Transformer; + voiceStateUpdate?: Transformer; + voiceServerUpdate?: Transformer; + webhookUpdate?: Transformer; + messagePollVoteAdd?: Transformer; + messagePollVoteRemove?: Transformer; } export interface SelectiveCache { @@ -216,31 +216,31 @@ export interface DefaultCache extends BaseCachingStructure { } // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export type ParseCachingManager> = T["caching"] extends {} - ? T["caching"]["applyTransformers"] extends true - ? T["caching"] extends { transformerTypes: (infer U extends CacheWithTransformers["transformerTypes"]) } +export type ParseCachingManager = T extends {} + ? T["applyTransformers"] extends true + ? T extends { transformerTypes: (infer U extends CacheWithTransformers["transformerTypes"]) } ? CachingManager : never - : T["caching"]["delegate"] extends CachingDelegationType.EXTERNAL - ? T["caching"]["manager"] & {} + : T["delegate"] extends CachingDelegationType.EXTERNAL + ? T["manager"] & {} : CachingManager : never; export type DebugFunction = (identifier: DebugIdentifier, payload?: unknown) => any; -export interface BaseClientOptions { +export interface ClientOptions { intents: number; - listeners: ClientListeners; - transformers?: T; + dispatch?: DispatchFunction; presence?: UpdatePresenceStructure; - caching?: (DefaultCache | ExternalCache | TransformersCache) & ApplyTransformers; useDebugRest?: boolean; - setup?: (client: Client) => Awaitable; + cachingManager?: CacheManagerStructure; } -export interface ClientOptions extends Omit, "intents"> { - intents: Array | number; - token: string; - attachDebugListener?: boolean; - customCacheKeys?: BaseCachingStructure["customKeys"]; - debugListener?: (identifier: DebugIdentifier, payload: unknown) => void; +export type CachingOptions = (DefaultCache | ExternalCache | TransformersCache) & ApplyTransformers; + +export interface MockClient { + /** By default this is a UserStructure, but can change according to your transformers*/ + readonly user: unknown; + readonly sessionId: string; + readonly application: Application.Structure; + readonly cache: CacheManagerStructure; } diff --git a/packages/core/src/ws/manager.ts b/packages/core/src/ws/manager.ts index bc29a67..cbf6587 100644 --- a/packages/core/src/ws/manager.ts +++ b/packages/core/src/ws/manager.ts @@ -21,14 +21,14 @@ interface ManagerOptions { export type DispatchFunction = (data: ReceiveDispatchEvent) => any; export class WebSocketManager { - readonly #dispatch: DispatchFunction; readonly #debug: DebugFunction | undefined; + readonly #options: Required; + #dispatch: DispatchFunction | undefined; #sequenceNumber: number | null = null; #isResuming = false; #ws!: WebSocket; #gatewayInfo!: GetGatewayBotResponse; - #options: Required; #timer?: Timer; // eslint-disable-next-line @typescript-eslint/naming-convention #gotACK: boolean = true; @@ -39,19 +39,24 @@ export class WebSocketManager { id: string } = {}; - public constructor(options: ManagerOptions, dispatch: DispatchFunction, debug?: DebugFunction) { + public constructor(options: ManagerOptions, debug?: DebugFunction) { if (typeof options.intents !== "number" && Number.isNaN(options.intents)) throw new Error("Invalid intents"); - this.#dispatch = dispatch; this.#debug = debug; this.#options = options; } + public init(token: string, dispatch: DispatchFunction): void { + this.#options.token = token; + this.#dispatch = dispatch; + } + public close(): void { this.#ws.close(3000); } public async connect(url?: string): Promise { + if (typeof this.#dispatch === "undefined") throw new Error("You need to pass the dispatch function via 'init' before connecting."); if (typeof this.#gatewayInfo === "undefined") { const response = await fetch("https://discord.com/api/v10/gateway/bot", { headers: { @@ -95,7 +100,8 @@ export class WebSocketManager { switch (payload.op) { case GatewayOpCode.Dispatch: { - this.#dispatch(payload); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.#dispatch!(payload); break; } case GatewayOpCode.Hello: { @@ -251,10 +257,6 @@ export class WebSocketManager { this.#ws.send(JSON.stringify(options)); } - public set options(options: Partial) { - this.#options = { ...this.#options, ...options }; - } - public get options(): ManagerOptions { return this.#options; } diff --git a/packages/create/eslint.config.js b/packages/create/eslint.config.js index 1a5cf1c..09f3862 100644 --- a/packages/create/eslint.config.js +++ b/packages/create/eslint.config.js @@ -1,3 +1,3 @@ import config from "../../eslint.config.js"; -export default [{ ignores: ["src/templates/**/*", "dist/**/*"] }, ...config] \ No newline at end of file +export default [{ ignores: ["dist/**/*.ts"] }, ...config] \ No newline at end of file diff --git a/packages/create/globals.d.ts b/packages/create/globals.d.ts new file mode 100644 index 0000000..cc6f3d5 --- /dev/null +++ b/packages/create/globals.d.ts @@ -0,0 +1,5 @@ +declare module "bun" { + interface Env { + readonly TOKEN: string + } +} \ No newline at end of file diff --git a/packages/create/package.json b/packages/create/package.json index 3a2fa96..46ac70a 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -1,6 +1,6 @@ { "name": "@lilybird/create", - "version": "0.4.0", + "version": "0.5.0", "description": "Create a new template bot using lilybird", "main": "./dist/index.js", "author": "DidaS", diff --git a/packages/create/src/templates.cts b/packages/create/src/templates.cts index d458748..402ffd0 100644 --- a/packages/create/src/templates.cts +++ b/packages/create/src/templates.cts @@ -58,12 +58,12 @@ export function generateTSConfig(type: string, pm: string): string { export function generateGlobalTypes(pm: string): string { return pm === "bun" ? `declare module "bun" { interface Env { - TOKEN: string + readonly TOKEN: string } }` : `declare namespace NodeJS { interface ProcessEnv { - TOKEN: string + readonly TOKEN: string } }`; } diff --git a/packages/create/src/templates/basic-template.ts b/packages/create/src/templates/basic-template.ts index 5d9114d..46f3ee7 100644 --- a/packages/create/src/templates/basic-template.ts +++ b/packages/create/src/templates/basic-template.ts @@ -1,11 +1,10 @@ -import { createClient, Intents } from "lilybird"; +import { Client, GatewayEvent, Intents } from "lilybird"; -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], - listeners: { - ready(client) { - console.log(`Logged in as ${client.user.username}`); - } +const client = new Client({ + intents: Intents.GUILDS, + dispatch: (payload) => { + if (payload.t === GatewayEvent.Ready) console.log(`Logged in as ${client.user.username}`); } }); + +await client.login(process.env.TOKEN); diff --git a/packages/create/src/templates/handlers-template.ts b/packages/create/src/templates/handlers-template.ts index 503eee2..3a2b6c2 100644 --- a/packages/create/src/templates/handlers-template.ts +++ b/packages/create/src/templates/handlers-template.ts @@ -1,16 +1,20 @@ -import { Intents, createClient } from "lilybird"; -import { handler } from "@lilybird/handlers/advanced"; +import type { Transformers } from "lilybird"; +import { Intents, Client, ListenerCompiler } from "lilybird"; +import { handler } from "@lilybird/handlers"; handler.cachePath = `${import.meta.dir}/lily-cache/handler`; await handler.scanDir(`${import.meta.dir}/commands`); await handler.scanDir(`${import.meta.dir}/listeners`); -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], - setup: async (client) => { +const compiler = new ListenerCompiler>({}) + .addListener("ready", async (client) => { await handler.loadGlobalCommands(client); - }, - listeners: handler.getListenersObject() + }, true) + .addListenersFromObject(handler.getListenersObject()); + +const client = new Client({ + intents: Intents.GUILDS }); + +await client.login(process.env.TOKEN, compiler.getDispatchFunction(client, client.ws.resumeInfo)); diff --git a/packages/create/src/templates/listener-template.ts b/packages/create/src/templates/listener-template.ts index fb40fc0..c0a914a 100644 --- a/packages/create/src/templates/listener-template.ts +++ b/packages/create/src/templates/listener-template.ts @@ -1,4 +1,4 @@ -import { $listener } from "@lilybird/handlers/advanced"; +import { $listener } from "@lilybird/handlers"; $listener({ event: "ready", diff --git a/packages/create/src/templates/transformers-template.ts b/packages/create/src/templates/transformers-template.ts index 1ad3927..4fada45 100644 --- a/packages/create/src/templates/transformers-template.ts +++ b/packages/create/src/templates/transformers-template.ts @@ -1,13 +1,21 @@ -import { defaultTransformers } from "@lilybird/transformers"; -import { createClient, Intents } from "lilybird"; - -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], - transformers: defaultTransformers, - listeners: { - ready(client) { - console.log(`Logged in as ${client.user.username}`); - } - } +import { makeTransformersObject } from "@lilybird/transformers"; +import { Intents, Client, ListenerCompiler } from "lilybird"; + +import type { MergeTransformers } from "@lilybird/transformers"; + +const transformers = makeTransformersObject(); + +const compiler = new ListenerCompiler>({ + transformClient: true, + transformers }); + +compiler.addListener("ready", (client) => { + console.log(`Logged in as ${client.user.username}`); +}, true); + +const client = new Client({ + intents: Intents.GUILDS +}); + +await client.login(process.env.TOKEN, compiler.getDispatchFunction(client, client.ws.resumeInfo)); diff --git a/packages/create/tsconfig.json b/packages/create/tsconfig.json index 9b23db4..2578140 100644 --- a/packages/create/tsconfig.json +++ b/packages/create/tsconfig.json @@ -2,12 +2,13 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "dist", - "baseUrl": "." + "baseUrl": ".", + "types": [ + "bun-types", + "./globals" + ], }, "include": [ "src/**/*" - ], - "exclude": [ - "src/templates/**/*.ts" ] } \ No newline at end of file diff --git a/packages/handlers/package.json b/packages/handlers/package.json index ac6afc7..33cdf94 100644 --- a/packages/handlers/package.json +++ b/packages/handlers/package.json @@ -1,21 +1,16 @@ { "name": "@lilybird/handlers", - "version": "0.6.0", + "version": "0.7.0", "description": "Command handlers and more for lilybird", "main": "./dist/index.js", "author": "DidaS", "license": "Apache-2.0", "type": "module", "exports": { - "./simple": { - "types": "./dist/simple/index.d.ts", - "require": "./dist/simple/index.js", - "default": "./dist/simple/index.js" - }, - "./advanced": { - "types": "./dist/advanced/index.d.ts", - "require": "./dist/advanced/index.js", - "default": "./dist/advanced/index.js" + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" }, "./package.json": "./package.json" }, @@ -52,7 +47,7 @@ "bun-first" ], "peerDependencies": { - "lilybird": "^0.8.0", + "lilybird": "^0.9.0", "@lilybird/transformers": "^0.4.0" } } \ No newline at end of file diff --git a/packages/handlers/src/advanced/application-command-store.ts b/packages/handlers/src/application-command-store.ts similarity index 100% rename from packages/handlers/src/advanced/application-command-store.ts rename to packages/handlers/src/application-command-store.ts diff --git a/packages/handlers/src/advanced/handler.ts b/packages/handlers/src/handler.ts similarity index 96% rename from packages/handlers/src/advanced/handler.ts rename to packages/handlers/src/handler.ts index 366818b..186c9e2 100644 --- a/packages/handlers/src/advanced/handler.ts +++ b/packages/handlers/src/handler.ts @@ -8,18 +8,17 @@ import type { BaseCommandOption, CommandOption, CommandStructure, SubCommandStru import type { ComponentStructure, DynamicComponentStructure } from "./message-component-store.js"; import type { HandlerListener } from "./shared.js"; import type { - CacheManagerStructure, ApplicationCommand, - ClientListeners, Transformers, Interaction, Awaitable, + Listeners, Client } from "lilybird"; type ApplicationCommandJSONParams = ApplicationCommand.Create.ApplicationCommandJSONParams; -export class Handler { +export class Handler = Transformers, U extends boolean = false> { readonly #acs = new ApplicationCommandStore(); readonly #mcs = new MessageComponentStore(); readonly #listeners = new Map) => any>(); @@ -32,7 +31,7 @@ export class Handler>(data: SubCommandStructure): SubCommandStructure { return data; } public storeListener< - TR extends Transformers = T, + TR extends Transformers = T, K extends keyof TR = keyof TR - >(data: { event: K, handle: Required>[K] }): void { + >(data: { event: K, handle: Required>[K] }): void { this.#listeners.set(data.event, data.handle); } @@ -223,7 +222,7 @@ export class Handler(includeCommands: boolean = true): ClientListeners { + public getListenersObject(includeCommands: boolean = true): Listeners { const obj: Record = {}; for (let i = 0, entries = [...this.#listeners.entries()], { length } = entries; i < length; i++) { @@ -235,7 +234,7 @@ export class Handler, interaction: Interaction.Structure) => { + obj.interactionCreate = (client: Client, interaction: Interaction.Structure) => { // @ts-expect-error The obj constant is not typed obj.interactionCreate(client, interaction); listener(client, interaction); diff --git a/packages/handlers/src/advanced/index.ts b/packages/handlers/src/index.ts similarity index 100% rename from packages/handlers/src/advanced/index.ts rename to packages/handlers/src/index.ts diff --git a/packages/handlers/src/advanced/message-component-store.ts b/packages/handlers/src/message-component-store.ts similarity index 97% rename from packages/handlers/src/advanced/message-component-store.ts rename to packages/handlers/src/message-component-store.ts index 8551c40..5af7fbf 100644 --- a/packages/handlers/src/advanced/message-component-store.ts +++ b/packages/handlers/src/message-component-store.ts @@ -1,5 +1,5 @@ -import { defaultTransformers } from "@lilybird/transformers"; +import { makeTransformersObject } from "@lilybird/transformers"; import { ComponentType, InteractionType } from "lilybird"; import { HandlerIdentifier } from "./shared.js"; @@ -12,6 +12,9 @@ import type { MessageComponentData } from "@lilybird/transformers"; +//!TODO REWORK THIS ENTIRE THING +const defaultTransformers = makeTransformersObject(); + interface CompiledComponent { body: string; handler: [name: string, fn: MessageComponentHandler]; diff --git a/packages/handlers/src/advanced/shared.ts b/packages/handlers/src/shared.ts similarity index 100% rename from packages/handlers/src/advanced/shared.ts rename to packages/handlers/src/shared.ts diff --git a/packages/handlers/src/simple/application-command.ts b/packages/handlers/src/simple/application-command.ts deleted file mode 100644 index 48d596e..0000000 --- a/packages/handlers/src/simple/application-command.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ApplicationCommandData, AutocompleteData, GuildInteraction, Interaction } from "@lilybird/transformers"; -import type { ApplicationCommand, Awaitable } from "lilybird"; - -type Guild = `${number}` | Array<`${number}`>; - -export interface GlobalApplicationCommand { - data: ApplicationCommand.Create.ApplicationCommandJSONParams; - post: "GLOBAL"; - autocomplete?: (interaction: Interaction) => Awaitable; - run: (interaction: Interaction) => Awaitable; -} - -export interface GuildApplicationCommand { - data: ApplicationCommand.Create.ApplicationCommandJSONParams; - post: Guild; - autocomplete?: (interaction: GuildInteraction) => Awaitable; - run: (interaction: GuildInteraction) => Awaitable; -} - -export type ApplicationCommand = GuildApplicationCommand | GlobalApplicationCommand; - diff --git a/packages/handlers/src/simple/events.ts b/packages/handlers/src/simple/events.ts deleted file mode 100644 index 988e124..0000000 --- a/packages/handlers/src/simple/events.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { DefaultTransformers } from "@lilybird/transformers"; -import type { Awaitable, ClientListeners } from "lilybird"; - -export interface Event< - E extends keyof ClientListeners = keyof ClientListeners, - T extends Required> = Required> -> { - name?: string; - event: E; - run: (...args: Parameters) => Awaitable; -} diff --git a/packages/handlers/src/simple/handler.ts b/packages/handlers/src/simple/handler.ts deleted file mode 100644 index afbb97c..0000000 --- a/packages/handlers/src/simple/handler.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { defaultTransformers } from "@lilybird/transformers"; -import { join } from "node:path"; - -import type { GlobalApplicationCommand, GuildApplicationCommand, ApplicationCommand } from "./application-command.js"; -import type { DefaultTransformers, Interaction, Message } from "@lilybird/transformers"; -import type { MessageCommand } from "./message-commands.js"; -import type { Event } from "./events.js"; - -import type { - ClientListeners, - ClientOptions, - Client -} from "lilybird"; - -interface HandlerDirectories { - slashCommands?: string; - messageCommands?: string; - listeners?: string; -} - -export class Handler { - protected readonly guildApplicationCommands = new Map(); - protected readonly globalApplicationCommands = new Map(); - protected readonly messageCommands = new Map(); - protected readonly events = new Map(); - protected readonly messageCommandAliases = new Map(); - - protected readonly dirs: HandlerDirectories; - protected readonly prefix: string; - - readonly #globMatcher = new Bun.Glob("**/*.{ts,tsx,js,jsx}"); - - public constructor(dirs: HandlerDirectories, prefix?: string) { - this.dirs = dirs; - this.prefix = prefix ?? "!"; - } - - public async registerGlobalCommands(client: Client): Promise { - await client.rest.bulkOverwriteGlobalApplicationCommand(client.user.id, [...this.globalApplicationCommands.values()].map((e) => e.data)); - } - - public async registerGuildCommands(client: Client): Promise { - for await (const command of this.guildApplicationCommands.values()) { - if (Array.isArray(command.post)) { - const temp: Array> = []; - for (let i = 0; i < command.post.length; i++) temp.push(client.rest.createGuildApplicationCommand(client.user.id, command.post[i], command.data)); - await Promise.all(temp); - } else await client.rest.createGuildApplicationCommand(client.user.id, command.post, command.data); - } - } - - public async readSlashCommandDir(dir: string | undefined = this.dirs.slashCommands): Promise { - if (typeof dir === "undefined") return false; - - const files = this.#globMatcher.scan(dir); - - for await (const fileName of files) { - if (fileName.endsWith(".d.ts")) continue; - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const command: ApplicationCommand = (await import(join(dir, fileName))).default; - if (typeof command === "undefined") continue; - - if (fileName.startsWith("/guild") || command.post !== "GLOBAL") this.guildApplicationCommands.set(command.data.name, command); - else this.globalApplicationCommands.set(command.data.name, command); - } - - return true; - } - - public async readEventDir(dir: string | undefined = this.dirs.listeners): Promise { - if (typeof dir === "undefined") return false; - - const files = this.#globMatcher.scan(dir); - - for await (const fileName of files) { - if (fileName.endsWith(".d.ts")) continue; - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const event: Event = (await import(join(dir, fileName))).default; - if (typeof event === "undefined") continue; - - this.events.set(event.event, event); - } - - return true; - } - - public async readMessageCommandDir(dir: string | undefined = this.dirs.messageCommands): Promise { - if (typeof dir === "undefined") return false; - - const files = this.#globMatcher.scan(dir); - - for await (const fileName of files) { - if (fileName.endsWith(".d.ts")) continue; - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const command: MessageCommand = (await import(join(dir, fileName))).default; - if (typeof command === "undefined") continue; - - if (typeof command.alias !== "undefined" && command.alias.length > 0) { - if (command.alias.length === 1) this.messageCommandAliases.set(command.alias[0], command.name); - else for (let i = 0, { length } = command.alias; i < length; i++) this.messageCommandAliases.set(command.alias[i], command.name); - } - - this.messageCommands.set(command.name, command); - } - - return true; - } - - private async onInteraction(interaction: Interaction): Promise { - if (interaction.isApplicationCommandInteraction()) { - await this.globalApplicationCommands.get(interaction.data.name)?.run(interaction); - if (interaction.inGuild()) await this.guildApplicationCommands.get(interaction.data.name)?.run(interaction); - } else if (interaction.isAutocompleteInteraction()) { - await this.globalApplicationCommands.get(interaction.data.name)?.autocomplete?.(interaction); - if (interaction.inGuild()) await this.guildApplicationCommands.get(interaction.data.name)?.autocomplete?.(interaction); - } - } - - private async onMessage(message: Message): Promise { - if (message.author.bot || (await message.fetchChannel()).isDM()) return; - - if (message.content?.startsWith(this.prefix)) { - const args = message.content.slice(this.prefix.length).trim().split(/\s+/g); - if (args.length === 0) return; - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const alias = args.shift()!.toLowerCase(); - let command = this.messageCommands.get(alias); - let name: string | undefined = alias; - - if (typeof command === "undefined") { - name = this.messageCommandAliases.get(alias); - if (typeof name !== "string") return; - command = this.messageCommands.get(name); - if (typeof command === "undefined") return; - } - - if (command.enabled ?? true) await command.run(message, args, { name, alias }); - } - } - - public async buildListeners(): Promise> { - const slashCommandsExist = await this.readSlashCommandDir(); - const messageCommandsExist = await this.readMessageCommandDir(); - const eventsExist = await this.readEventDir(); - - let interactionCreateFn: Exclude["interactionCreate"], undefined> | undefined = undefined; - let messageCreateFn: Exclude["messageCreate"], undefined> | undefined = undefined; - - const listeners: ClientListeners & Record = {} as never; - - if (eventsExist) { - for (const [name, event] of this.events) { - if (name === "interactionCreate") { - interactionCreateFn = event.run; - continue; - } - - if (name === "messageCreate") { - messageCreateFn = event.run; - continue; - } - - listeners[name] = event.run; - } - } - - if (!slashCommandsExist) listeners.interactionCreate = interactionCreateFn; - else if (typeof interactionCreateFn !== "undefined") { - listeners.interactionCreate = async (interaction) => { - //@ts-expect-error It is being checked above... - await interactionCreateFn(interaction); - await this.onInteraction(interaction); - }; - } else { - listeners.interactionCreate = async (interaction) => { - await this.onInteraction(interaction); - }; - } - - if (!messageCommandsExist) listeners.messageCreate = messageCreateFn; - else if (typeof messageCreateFn !== "undefined") { - listeners.messageCreate = async (message) => { - //@ts-expect-error It is being checked above... - await messageCreateFn(message); - await this.onMessage(message); - }; - } else { - listeners.messageCreate = async (message) => { - await this.onMessage(message); - }; - } - - return listeners; - } -} - -type Expand = T extends (...args: Array) => any ? T : { [K in keyof T]: T[K] }; - -export async function createHandler({ - dirs, - prefix -}: { - dirs: HandlerDirectories, - prefix?: string | undefined -}): Promise>, "listeners" | "transformers" | "setup" | "customCacheKeys">>> { - const handler = new Handler(dirs, prefix); - - return { - transformers: defaultTransformers, - listeners: await handler.buildListeners(), - customCacheKeys: { - guild_voice_states: "voiceStates" - }, - setup: async (client) => { - await handler.registerGlobalCommands(client); - await handler.registerGuildCommands(client); - } - }; -} diff --git a/packages/handlers/src/simple/index.ts b/packages/handlers/src/simple/index.ts deleted file mode 100644 index becb671..0000000 --- a/packages/handlers/src/simple/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -process.emitWarning("Simple handlers are deprecated!", { - code: "HANDLERS_DEPRECATED", - detail: "Simple handlers have been deprecated, please use `@lilybird/handlers/advanced` instead." -}); - -export type * from "./application-command.js"; -export type * from "./message-commands.js"; -export type * from "./events.js"; - -export * from "./handler.js"; diff --git a/packages/handlers/src/simple/message-commands.ts b/packages/handlers/src/simple/message-commands.ts deleted file mode 100644 index e7c7870..0000000 --- a/packages/handlers/src/simple/message-commands.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Message } from "@lilybird/transformers"; -import type { Awaitable } from "lilybird"; - -export interface MessageCommand { - name: string; - alias?: Array; - description?: string; - enabled?: boolean; - run: (message: Message, args: Array, meta: { name: string, alias: string }) => Awaitable; -} diff --git a/packages/test/globals.d.ts b/packages/test/globals.d.ts index 05deb9f..aec12fe 100644 --- a/packages/test/globals.d.ts +++ b/packages/test/globals.d.ts @@ -1,9 +1,9 @@ declare module "bun" { interface Env { - TOKEN: string; - TEST_CHANNEL_ID: string; - TEST_GUILD_ID: string; - SEARCH_KEY: string; - CX: string; + readonly TOKEN: string; + readonly TEST_CHANNEL_ID: string; + readonly TEST_GUILD_ID: string; + readonly SEARCH_KEY: string; + readonly CX: string; } } diff --git a/packages/test/src/commands-adv/bananas-poll.ts b/packages/test/src/commands-adv/bananas-poll.ts deleted file mode 100644 index 1c4c03a..0000000 --- a/packages/test/src/commands-adv/bananas-poll.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { $applicationCommand } from "../handlers.js"; -import { Message } from "@lilybird/transformers"; - -import type { Interaction } from "@lilybird/transformers"; - -$applicationCommand({ - name: "bananas-poll", - description: "create bananas poll", - handle: async (interaction: Interaction): Promise => { - await interaction.reply({ - poll: { - question: { - text: "Do you like bananas?" - }, - answers: [ - { - answer_id: 0, - poll_media: { text: "Yes!" } - }, - { - answer_id: 1, - poll_media: { text: "No!" } - } - ], - allow_multiselect: false, - duration: 168 - } - }); - - setTimeout(async () => { - const msg = new Message( - interaction.client, - await interaction.client.rest.getOriginalInteractionResponse(interaction.client.application.id, interaction.token) - ); - - console.log(await msg.poll?.answers[0].fetchVoters({})); - }, 3000); - } -}); diff --git a/packages/test/src/commands-adv/ping.ts b/packages/test/src/commands-adv/ping.ts deleted file mode 100644 index b58ef58..0000000 --- a/packages/test/src/commands-adv/ping.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { $applicationCommand } from "../handlers.js"; - -$applicationCommand({ - name: "ping", - description: "pong", - handle: async (interaction) => { - await interaction.deferReply(); - const { ws, rest } = await interaction.client.ping(); - - await interaction.editReply({ - content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\`` - }); - } -}); diff --git a/packages/test/src/commands/bananas-poll.ts b/packages/test/src/commands/bananas-poll.ts index 2977f5f..1c4c03a 100644 --- a/packages/test/src/commands/bananas-poll.ts +++ b/packages/test/src/commands/bananas-poll.ts @@ -1,10 +1,12 @@ -import type { ApplicationCommand } from "@lilybird/handlers/simple"; +import { $applicationCommand } from "../handlers.js"; import { Message } from "@lilybird/transformers"; -export default { - post: "GLOBAL", - data: { name: "bananas-poll", description: "create bananas poll" }, - run: async (interaction) => { +import type { Interaction } from "@lilybird/transformers"; + +$applicationCommand({ + name: "bananas-poll", + description: "create bananas poll", + handle: async (interaction: Interaction): Promise => { await interaction.reply({ poll: { question: { @@ -34,4 +36,4 @@ export default { console.log(await msg.poll?.answers[0].fetchVoters({})); }, 3000); } -} satisfies ApplicationCommand; +}); diff --git a/packages/test/src/commands/ping.ts b/packages/test/src/commands/ping.ts index 4ae87c0..b58ef58 100644 --- a/packages/test/src/commands/ping.ts +++ b/packages/test/src/commands/ping.ts @@ -1,28 +1,14 @@ -import type { ApplicationCommand } from "@lilybird/handlers/simple"; -import { ApplicationIntegrationType, InteractionContextType } from "lilybird"; +import { $applicationCommand } from "../handlers.js"; -export default { - post: "GLOBAL", - data: { - name: "ping", - description: "pong", - integration_types: [ - ApplicationIntegrationType.GUILD_INSTALL, - ApplicationIntegrationType.USER_INSTALL - ], - contexts: [ - InteractionContextType.BOT_DM, - InteractionContextType.GUILD, - InteractionContextType.PRIVATE_CHANNEL - ] - }, - run: async (interaction) => { +$applicationCommand({ + name: "ping", + description: "pong", + handle: async (interaction) => { await interaction.deferReply(); - const { ws, rest } = await interaction.client.ping(); await interaction.editReply({ content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\`` }); } -} satisfies ApplicationCommand; +}); diff --git a/packages/test/src/commands-adv/search.ts b/packages/test/src/commands/search.ts similarity index 100% rename from packages/test/src/commands-adv/search.ts rename to packages/test/src/commands/search.ts diff --git a/packages/test/src/commands/search.tsx b/packages/test/src/commands/search.tsx deleted file mode 100644 index 7bfffc2..0000000 --- a/packages/test/src/commands/search.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { - ApplicationCommand as JSXApplicationCommand, - StringOption, - UserOption, - EmbedImage, - Embed, -} from "@lilybird/jsx"; - -import type { ApplicationCommand } from "@lilybird/handlers/simple"; - -interface GoogleAPIResponse { - kind: string; - url: Record; - queries: Record; - context: { - title: string - }; - searchInformation: Record; - items: Array; -} - -interface GoogleAPIItem { - kind: string; - title: string; - htmlTitle: string; - link: string; - displayLink: string; - snippet: string; - htmlSnippet: string; - cacheId: string; - formattedUrl: string; - htmlFormattedUrl: string; - pagemap: { - cse_thumbnail: Array<{ src: string, width: string, height: string }>, - xfn: Array>, - BreadcrumbList: Array>, - metatags: Array, - cse_image: Array<{ src: string }> - }; -} - -interface Metatag { - "og:image": string; - "theme-color": string; - "og:type": string; - "og:image:width": string; - "og:image:alt": string; - "twitter:card": string; - "og:site_name": string; - "og:title": string; - "og:image:height": string; - "og:image:type": string; - "og:description": string; - "twitter:creator": string; - viewport: string; - "og:locale": string; - position: string; - "og:url": string; -} - -// shit cache -const localCache = new Map(); - -export default { - data: ( - - - ) as never, - post: "GLOBAL", - run: async (interaction) => { - const cacheId = interaction.data.getString("query", true); - const tags = localCache.get(cacheId); - if (!tags) throw new Error("WTF"); - - const userId = interaction.data.getUser("user"); - - const embed = ( - - ) as never; - - await interaction.reply({ - content: userId ? `<@${userId}> learn how to fucking google` : "", - embeds: [embed] - }); - }, - autocomplete: async (interaction) => { - const query = interaction.data.getFocused().value; - if (query.length === 0) return; - - const url = `https://www.googleapis.com/customsearch/v1?key=${process.env.SEARCH_KEY}&cx=${process.env.CX}&q=${query}&num=10`; - - const response = await fetch(url); - const body: GoogleAPIResponse = await response.json() as never; - - populateCache(body.items); - - await interaction.showChoices(body.items.map((val) => ({ name: val.title, value: val.cacheId }))); - } -} satisfies ApplicationCommand; - -function populateCache(items: Array): void { - for (let i = 0, { length } = items; i < length; i++) { - const item = items[i]; - const meta = item.pagemap.metatags; - - if (localCache.has(item.cacheId)) continue; - - localCache.set(item.cacheId, meta[0]); - } -} diff --git a/packages/test/src/events-adv/ready.ts b/packages/test/src/events/ready.ts similarity index 68% rename from packages/test/src/events-adv/ready.ts rename to packages/test/src/events/ready.ts index fb40fc0..9d184e1 100644 --- a/packages/test/src/events-adv/ready.ts +++ b/packages/test/src/events/ready.ts @@ -1,4 +1,4 @@ -import { $listener } from "@lilybird/handlers/advanced"; +import { $listener } from "../handlers.js"; $listener({ event: "ready", diff --git a/packages/test/src/events/ready.tsx b/packages/test/src/events/ready.tsx deleted file mode 100644 index 3e98833..0000000 --- a/packages/test/src/events/ready.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { Event } from "@lilybird/handlers/simple"; - -export default { - event: "ready", - run(client) { - console.log("Connected as", client.user.username); - } -} satisfies Event<"ready">; diff --git a/packages/test/src/handlers.ts b/packages/test/src/handlers.ts index a8a3bed..cb9e71a 100644 --- a/packages/test/src/handlers.ts +++ b/packages/test/src/handlers.ts @@ -1,9 +1,12 @@ -import { defaultTransformers } from "@lilybird/transformers"; -import { Handler } from "@lilybird/handlers/advanced"; +import { makeTransformersObject } from "@lilybird/transformers"; +import { Handler } from "@lilybird/handlers"; -import type { DefaultTransformers } from "@lilybird/transformers"; +import type { MergeTransformers } from "@lilybird/transformers"; +import type { Client } from "lilybird"; -export const handler = new Handler({ transformers: defaultTransformers }); +export const defaultTransformers = makeTransformersObject(); + +export const handler = new Handler, true>({ transformers: defaultTransformers }); export const $applicationCommand = handler.storeCommand.bind(handler); export const $listener = handler.storeListener.bind(handler); export const $component = handler.buttonCollector.bind(handler); diff --git a/packages/test/src/index.ts b/packages/test/src/index.ts index 3c0254e..dfe95f9 100644 --- a/packages/test/src/index.ts +++ b/packages/test/src/index.ts @@ -1,16 +1,22 @@ -import { Intents, createClient } from "lilybird"; +import { Intents, Client, ListenerCompiler } from "lilybird"; import { handler } from "./handlers.js"; +import type { MergeTransformers } from "@lilybird/transformers"; +import type { defaultTransformers } from "./handlers.js"; + handler.cachePath = `${import.meta.dir}/lily-cache/handler`; -await handler.scanDir(`${import.meta.dir}/commands-adv`); -await handler.scanDir(`${import.meta.dir}/events-adv`); +await handler.scanDir(`${import.meta.dir}/commands`); +await handler.scanDir(`${import.meta.dir}/events`); -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], - setup: async (client) => { +const compiler = new ListenerCompiler>({}) + .addListener("ready", async (client) => { await handler.loadGlobalCommands(client); - }, - listeners: handler.getListenersObject() + }, true) + .addListenersFromObject(handler.getListenersObject()); + +const client = new Client({ + intents: Intents.GUILDS }); + +await client.login(process.env.TOKEN, compiler.getDispatchFunction(client, client.ws.resumeInfo)); diff --git a/packages/transformers/package.json b/packages/transformers/package.json index fffe173..36db59e 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -1,6 +1,6 @@ { "name": "@lilybird/transformers", - "version": "0.4.3", + "version": "0.5.0", "description": "Event transformers and more for lilybird", "main": "./dist/index.js", "author": "DidaS", @@ -48,6 +48,6 @@ "transformers" ], "peerDependencies": { - "lilybird": "^0.7.0" + "lilybird": "^0.9.0" } } \ No newline at end of file diff --git a/packages/transformers/src/index.ts b/packages/transformers/src/index.ts index b09ef10..ce28a54 100644 --- a/packages/transformers/src/index.ts +++ b/packages/transformers/src/index.ts @@ -1,17 +1,15 @@ import type { BaseCachingStructure } from "lilybird"; -import { transformers } from "./transformers.js"; import type { Transformers } from "lilybird"; -export type DefaultTransformers = MergeTransformers; -export const defaultTransformers: DefaultTransformers = transformers; +export { makeTransformersObject } from "./transformers.js"; export const cacheKeys: Required["customKeys"] = { guild_voice_states: "voiceStates" }; -export type MergeTransformers = T & { +export type MergeTransformers> = T & { // eslint-disable-next-line @typescript-eslint/no-empty-object-type - [K in keyof Transformers as T[K] extends {} ? never : K]: Transformers[K] + [K in keyof Transformers as T[K] extends {} ? never : K]: Transformers[K] }; export type { diff --git a/packages/transformers/src/transformers.ts b/packages/transformers/src/transformers.ts index 2006f0c..1eed25e 100644 --- a/packages/transformers/src/transformers.ts +++ b/packages/transformers/src/transformers.ts @@ -8,81 +8,84 @@ import { Message } from "./index.js"; import type { Channel, ExtendedThreadChannel } from "./factories/channel.js"; import type { GuildMemberWithGuildId } from "./factories/guild-member.js"; -import type { Transformers, Guild as LilyGuild } from "lilybird"; +import type { Transformers, Guild as LilyGuild, Client } from "lilybird"; import type { Interaction, PartialMessage } from "./index.js"; import type { Guild, NewGuild } from "./factories/guild.js"; -export const transformers = { - channelCreate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Channel => channelFactory(client, data) - }, - channelUpdate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Channel => channelFactory(client, data) - }, - channelDelete: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Channel => channelFactory(client, data) - }, - channelPinsUpdate: { - return: TransformerReturnType.MULTIPLE, - handler: (_, data): [guildId: string | undefined, channelId: string, lastPinTimestamp: Date | null] => [ - data.guild_id, - data.channel_id, - typeof data.last_pin_timestamp === "string" ? new Date(data.last_pin_timestamp) : null - ] - }, - threadCreate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): ExtendedThreadChannel => channelFactory(client, data) - }, - threadUpdate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Channel => channelFactory(client, data) - }, - threadDelete: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Pick => new ThreadChannel(client, data, false) - }, - guildCreate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): LilyGuild.UnavailableStructure | NewGuild => guildFactory(client, data) - }, - guildUpdate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Guild => guildFactory(client, data) - }, - guildMemberAdd: { - return: TransformerReturnType.SINGLE, - handler: (client, data): GuildMemberWithGuildId => new GuildMember(client, data) - }, - guildMemberRemove: { - return: TransformerReturnType.MULTIPLE, - handler: (client, data): [id: string, user: User] => [data.guild_id, new User(client, data.user)] - }, - guildMemberUpdate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): GuildMemberWithGuildId => new GuildMember(client, data) - }, - interactionCreate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Interaction => interactionFactory(client, data) - }, - messageCreate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): Message => new Message(client, data) - }, - messageUpdate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): PartialMessage => new Message(client, data) - }, - messageDelete: { - return: TransformerReturnType.SINGLE, - handler: (client, data): PartialMessage => new Message(client, data) - }, - userUpdate: { - return: TransformerReturnType.SINGLE, - handler: (client, data): User => new User(client, data) - } -} satisfies Transformers; +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export function makeTransformersObject() { + return { + channelCreate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Channel => channelFactory(client, data) + }, + channelUpdate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Channel => channelFactory(client, data) + }, + channelDelete: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Channel => channelFactory(client, data) + }, + channelPinsUpdate: { + return: TransformerReturnType.MULTIPLE, + handler: (_, data): [guildId: string | undefined, channelId: string, lastPinTimestamp: Date | null] => [ + data.guild_id, + data.channel_id, + typeof data.last_pin_timestamp === "string" ? new Date(data.last_pin_timestamp) : null + ] + }, + threadCreate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): ExtendedThreadChannel => channelFactory(client, data) + }, + threadUpdate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Channel => channelFactory(client, data) + }, + threadDelete: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Pick => new ThreadChannel(client, data, false) + }, + guildCreate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): LilyGuild.UnavailableStructure | NewGuild => guildFactory(client, data) + }, + guildUpdate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Guild => guildFactory(client, data) + }, + guildMemberAdd: { + return: TransformerReturnType.SINGLE, + handler: (client, data): GuildMemberWithGuildId => new GuildMember(client, data) + }, + guildMemberRemove: { + return: TransformerReturnType.MULTIPLE, + handler: (client, data): [id: string, user: User] => [data.guild_id, new User(client, data.user)] + }, + guildMemberUpdate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): GuildMemberWithGuildId => new GuildMember(client, data) + }, + interactionCreate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Interaction => interactionFactory(client, data) + }, + messageCreate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): Message => new Message(client, data) + }, + messageUpdate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): PartialMessage => new Message(client, data) + }, + messageDelete: { + return: TransformerReturnType.SINGLE, + handler: (client, data): PartialMessage => new Message(client, data) + }, + userUpdate: { + return: TransformerReturnType.SINGLE, + handler: (client, data): User => new User(client, data) + } + } satisfies Transformers; +} From 52b6054d47c8e957b26ab4d8aaca1cbeff267bb2 Mon Sep 17 00:00:00 2001 From: DidaS Date: Mon, 18 Nov 2024 21:10:27 +0000 Subject: [PATCH 02/25] `ready` should be the first listener to be compiled This just helps simplify the other logic --- packages/core/src/compiler.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/compiler.ts b/packages/core/src/compiler.ts index ce5d8c4..3d48d01 100644 --- a/packages/core/src/compiler.ts +++ b/packages/core/src/compiler.ts @@ -34,6 +34,7 @@ export class ListenerCompiler> { // We want to guarantee the raw listener will always be the first thing on the stack even if its not included this.#stack.set("raw", ""); + this.#stack.set("ready", ""); } #createListener( @@ -133,7 +134,7 @@ export class ListenerCompiler> { } //! TODO: Support a once listener and always listener simultaneously - #addReadyListener(once: boolean, listener?: (client: C, payload: Ready["d"]) => Awaitable): void { + #addReadyListener(once: boolean = false, listener?: (client: C, payload: Ready["d"]) => Awaitable): void { const readyArr = []; readyArr.push( "if(payload.t === \"READY\"){", @@ -385,6 +386,7 @@ export class ListenerCompiler> { return this; } + //? Raw listener should probably be completely removed now that dispatch is exposed public addRawListener(listener: (payload: ReceiveDispatchEvent) => Awaitable): this { this.#stack.set("raw", "await raw(payload)"); this.#callbacks.set("raw", listener); @@ -432,6 +434,9 @@ export class ListenerCompiler> { }, stack: string } { + const hasReadyListener = !(this.#stack.get("ready") === ""); + if (!hasReadyListener) this.#addReadyListener(); + const names = [...this.#callbacks.keys()]; const handlers = [...this.#callbacks.values()]; const compiledListeners = [...this.#stack.values()].join(""); @@ -449,6 +454,7 @@ export class ListenerCompiler> { this.#stack.clear(); this.#callbacks.clear(); this.#stack.set("raw", ""); + this.#stack.set("ready", ""); } public getDispatchFunction(clientPointer: C, resumeInfoPointer: { url: string, id: string }): DispatchFunction { From e6f549db18a7ab30ffdec5bcc1649dda404ae280 Mon Sep 17 00:00:00 2001 From: DidaS Date: Mon, 18 Nov 2024 21:15:40 +0000 Subject: [PATCH 03/25] Keep WSManager's old functionality --- packages/core/src/client.ts | 1 + packages/core/src/compiler.ts | 2 +- packages/core/src/ws/manager.ts | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 5337614..850a2d1 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -38,6 +38,7 @@ export class Client { intents: options.intents, presence: options.presence }, + undefined, debug ); diff --git a/packages/core/src/compiler.ts b/packages/core/src/compiler.ts index 3d48d01..0ecef46 100644 --- a/packages/core/src/compiler.ts +++ b/packages/core/src/compiler.ts @@ -26,7 +26,7 @@ export class ListenerCompiler> { readonly #transformers: T; readonly #shouldTransformClientUser: boolean; - public constructor(options: { transformers?: T, transformClient?: boolean }) { + public constructor(options: { transformers?: T, transformClient?: boolean } = {}) { this.#stack = new Map(); this.#callbacks = new Map(); this.#transformers = options.transformers ?? {}; diff --git a/packages/core/src/ws/manager.ts b/packages/core/src/ws/manager.ts index cbf6587..27eea44 100644 --- a/packages/core/src/ws/manager.ts +++ b/packages/core/src/ws/manager.ts @@ -39,9 +39,10 @@ export class WebSocketManager { id: string } = {}; - public constructor(options: ManagerOptions, debug?: DebugFunction) { + public constructor(options: ManagerOptions, dispatch?: DispatchFunction, debug?: DebugFunction) { if (typeof options.intents !== "number" && Number.isNaN(options.intents)) throw new Error("Invalid intents"); + this.#dispatch = dispatch; this.#debug = debug; this.#options = options; } From 30315902f103aa21ea417137b517ff2c0acd1d7a Mon Sep 17 00:00:00 2001 From: DidaS Date: Mon, 18 Nov 2024 21:21:17 +0000 Subject: [PATCH 04/25] Add beta tags so they can be published --- packages/handlers/package.json | 6 +++--- packages/transformers/package.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/handlers/package.json b/packages/handlers/package.json index 33cdf94..cc2a86f 100644 --- a/packages/handlers/package.json +++ b/packages/handlers/package.json @@ -1,6 +1,6 @@ { "name": "@lilybird/handlers", - "version": "0.7.0", + "version": "0.7.0-beta.0", "description": "Command handlers and more for lilybird", "main": "./dist/index.js", "author": "DidaS", @@ -47,7 +47,7 @@ "bun-first" ], "peerDependencies": { - "lilybird": "^0.9.0", - "@lilybird/transformers": "^0.4.0" + "lilybird": "^0.9.0-beta.0", + "@lilybird/transformers": "^0.5.0-beta.0" } } \ No newline at end of file diff --git a/packages/transformers/package.json b/packages/transformers/package.json index 36db59e..c81e875 100644 --- a/packages/transformers/package.json +++ b/packages/transformers/package.json @@ -1,6 +1,6 @@ { "name": "@lilybird/transformers", - "version": "0.5.0", + "version": "0.5.0-beta.0", "description": "Event transformers and more for lilybird", "main": "./dist/index.js", "author": "DidaS", @@ -48,6 +48,6 @@ "transformers" ], "peerDependencies": { - "lilybird": "^0.9.0" + "lilybird": "^0.9.0-beta.0" } } \ No newline at end of file From 5715ab5999307573b5a01e6c27705496fe7d423c Mon Sep 17 00:00:00 2001 From: DidaS Date: Mon, 25 Nov 2024 03:33:23 +0000 Subject: [PATCH 05/25] Bring back `createClient` --- packages/core/src/client.ts | 36 ++++++++++++++- packages/core/src/compiler.ts | 46 +++++++++++++++---- packages/core/src/index.ts | 1 + packages/core/src/typings/client.ts | 6 ++- .../create/src/templates/basic-template.ts | 13 +++--- .../create/src/templates/handlers-template.ts | 23 +++++----- .../src/templates/transformers-template.ts | 27 +++++------ 7 files changed, 107 insertions(+), 45 deletions(-) diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 850a2d1..3e62ac7 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -1,15 +1,20 @@ import { CachingManager } from "./cache/manager.js"; import { DebugREST, REST } from "./http/rest.js"; +import { ListenerCompiler } from "./compiler.js"; import { WebSocketManager } from "#ws"; +import type { CompilerOptions } from "./compiler.js"; import type { DispatchFunction } from "#ws"; import type { CacheManagerStructure, + CachingOptions, ClientOptions, DebugFunction, Transformers, - Application + Application, + MockClient, + Listeners } from "./typings/index.js"; type GetUserType> = (T["userUpdate"] & {}) extends { handler: ((...args: infer U) => infer R) } @@ -18,7 +23,7 @@ type GetUserType> = (T["userUpdate"] & {}) extends { : R : never ; -export class Client { +export class Client implements MockClient { public readonly rest: REST; public readonly cache: CacheManagerStructure; public readonly ws: WebSocketManager; @@ -70,3 +75,30 @@ export class Client { }; } } + +export interface CreateClientOptions> extends Omit, CompilerOptions { + token: string; + listeners: Listeners; + caching?: CachingOptions; + debug?: DebugFunction; +} + +export async function createClient = Transformers>(options: CreateClientOptions): Promise { + const compiler = new ListenerCompiler({ + transformers: options.transformers, + transformClient: options.transformClient + }); + + compiler.addListenersFromObject(options.listeners); + if (typeof options.caching !== "undefined") compiler.appendCachingHandlers(options.caching); + + const client = new Client({ + intents: options.intents, + presence: options.presence, + useDebugRest: typeof options.debug !== "undefined", + cachingManager: options.cachingManager + }, options.debug); + + await client.login(options.token, compiler.getDispatchFunction(client, client.ws.resumeInfo)); + return client; +} diff --git a/packages/core/src/compiler.ts b/packages/core/src/compiler.ts index 0ecef46..c8c49c8 100644 --- a/packages/core/src/compiler.ts +++ b/packages/core/src/compiler.ts @@ -19,6 +19,11 @@ import type { Ready } from "./typings/index.js"; +export interface CompilerOptions> { + transformers?: T; + transformClient?: boolean; +} + export class ListenerCompiler> { readonly #stack: Map; readonly #callbacks: Map any>; @@ -26,7 +31,7 @@ export class ListenerCompiler> { readonly #transformers: T; readonly #shouldTransformClientUser: boolean; - public constructor(options: { transformers?: T, transformClient?: boolean } = {}) { + public constructor(options: CompilerOptions = {}) { this.#stack = new Map(); this.#callbacks = new Map(); this.#transformers = options.transformers ?? {}; @@ -135,6 +140,7 @@ export class ListenerCompiler> { //! TODO: Support a once listener and always listener simultaneously #addReadyListener(once: boolean = false, listener?: (client: C, payload: Ready["d"]) => Awaitable): void { + const onceListener = this.#callbacks.get("once_ready"); const readyArr = []; readyArr.push( "if(payload.t === \"READY\"){", @@ -152,12 +158,32 @@ export class ListenerCompiler> { "Object.assign(resume_info_ptr{url:payload.d.resume_gateway_url,id:payload.d.session_id});" ); - if (typeof listener !== "undefined") { - if (once) readyArr.push("if(!client_ptr.ready){client_ptr.ready=true;"); - this.#callbacks.set("ready", listener); + if (typeof listener !== "undefined") this.#callbacks.set("ready", listener); + if (once || onceListener) readyArr.push("if(!client_ptr.ready){client_ptr.ready=true;"); + if (typeof this.#transformers.ready !== "undefined") { + this.#callbacks.set("t_ready", this.#transformers.ready.handler); + switch (this.#transformers.ready.return) { + case TransformerReturnType.SINGLE: { + if (onceListener) readyArr.push("await once_ready(t_ready(client_ptr, payload.d));"); + if (once && typeof listener !== "undefined") readyArr.push("await ready(t_ready(client_ptr, payload.d));"); + break; + } + case TransformerReturnType.MULTIPLE: { + if (onceListener) readyArr.push("await once_ready(...t_ready(client_ptr, payload.d));"); + if (once && typeof listener !== "undefined") readyArr.push("await ready(...t_ready(client_ptr, payload.d));"); + break; + } + } + } else { + if (onceListener) readyArr.push("await once_ready(client_ptr, payload.d);"); + if (once && typeof listener !== "undefined") readyArr.push("await ready(client_ptr, payload.d);"); + } + + if (once || onceListener) readyArr.push("}"); + + if (!once && typeof listener !== "undefined") { if (typeof this.#transformers.ready !== "undefined") { - this.#callbacks.set("t_ready", this.#transformers.ready.handler); switch (this.#transformers.ready.return) { case TransformerReturnType.SINGLE: { readyArr.push("await ready(t_ready(client_ptr, payload.d));"); @@ -169,7 +195,6 @@ export class ListenerCompiler> { } } } else readyArr.push("await ready(client_ptr, payload.d);"); - if (once) readyArr.push("}"); } readyArr.push("}"); @@ -405,12 +430,15 @@ export class ListenerCompiler> { once: boolean = false ): this { if (name === "raw") { - this.addRawListener(handler); + this.addRawListener(handler); return this; } - if (name === "ready") { - this.#addReadyListener(once, handler); + if (name === "setup") { + this.#callbacks.set("once_ready", handler); + return this; + } else if (name === "ready") { + this.#addReadyListener(once, handler); return this; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 88d5864..08f9907 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,6 +4,7 @@ export { REST } from "./http/rest.js"; export * as CDN from "./http/cdn.js"; export * from "./enums/index.js"; +export * from "./ws/manager.js"; export * from "./compiler.js"; export * from "./client.js"; export * from "./utils.js"; diff --git a/packages/core/src/typings/client.ts b/packages/core/src/typings/client.ts index 64c6754..5404366 100644 --- a/packages/core/src/typings/client.ts +++ b/packages/core/src/typings/client.ts @@ -97,6 +97,10 @@ export interface Transformers { return: TransformerReturnType, handler: (payload: ReceiveDispatchEvent) => unknown }; + /** + * Special case, its a `ready` handler that only fires once + */ + setup?: Transformer; ready?: Transformer; resumed?: Transformer; applicationCommandPermissionsUpdate?: Transformer; @@ -239,7 +243,7 @@ export type CachingOptions = (DefaultCache | ExternalCache | TransformersCache) export interface MockClient { /** By default this is a UserStructure, but can change according to your transformers*/ - readonly user: unknown; + readonly user: any; readonly sessionId: string; readonly application: Application.Structure; readonly cache: CacheManagerStructure; diff --git a/packages/create/src/templates/basic-template.ts b/packages/create/src/templates/basic-template.ts index 46f3ee7..fa60d4f 100644 --- a/packages/create/src/templates/basic-template.ts +++ b/packages/create/src/templates/basic-template.ts @@ -1,10 +1,11 @@ -import { Client, GatewayEvent, Intents } from "lilybird"; +import { createClient, Intents } from "lilybird"; -const client = new Client({ +await createClient({ + token: process.env.TOKEN, intents: Intents.GUILDS, - dispatch: (payload) => { - if (payload.t === GatewayEvent.Ready) console.log(`Logged in as ${client.user.username}`); + listeners: { + setup: (_, payload) => { + console.log(`Logged in as ${payload.user.username}`); + } } }); - -await client.login(process.env.TOKEN); diff --git a/packages/create/src/templates/handlers-template.ts b/packages/create/src/templates/handlers-template.ts index 3a2b6c2..eb0ac95 100644 --- a/packages/create/src/templates/handlers-template.ts +++ b/packages/create/src/templates/handlers-template.ts @@ -1,5 +1,4 @@ -import type { Transformers } from "lilybird"; -import { Intents, Client, ListenerCompiler } from "lilybird"; +import { Intents, createClient } from "lilybird"; import { handler } from "@lilybird/handlers"; handler.cachePath = `${import.meta.dir}/lily-cache/handler`; @@ -7,14 +6,14 @@ handler.cachePath = `${import.meta.dir}/lily-cache/handler`; await handler.scanDir(`${import.meta.dir}/commands`); await handler.scanDir(`${import.meta.dir}/listeners`); -const compiler = new ListenerCompiler>({}) - .addListener("ready", async (client) => { - await handler.loadGlobalCommands(client); - }, true) - .addListenersFromObject(handler.getListenersObject()); - -const client = new Client({ - intents: Intents.GUILDS +await createClient({ + token: process.env.TOKEN, + intents: Intents.GUILDS, + listeners: { + // Order is important + setup: async (client) => { + await handler.loadGlobalCommands(client); + }, + ...handler.getListenersObject() + } }); - -await client.login(process.env.TOKEN, compiler.getDispatchFunction(client, client.ws.resumeInfo)); diff --git a/packages/create/src/templates/transformers-template.ts b/packages/create/src/templates/transformers-template.ts index 4fada45..9cfd2c6 100644 --- a/packages/create/src/templates/transformers-template.ts +++ b/packages/create/src/templates/transformers-template.ts @@ -1,21 +1,18 @@ +import { createClient, Intents } from "lilybird"; import { makeTransformersObject } from "@lilybird/transformers"; -import { Intents, Client, ListenerCompiler } from "lilybird"; +import type { Client } from "lilybird"; import type { MergeTransformers } from "@lilybird/transformers"; -const transformers = makeTransformersObject(); +const transformers = makeTransformersObject(); -const compiler = new ListenerCompiler>({ - transformClient: true, - transformers +await createClient>({ + token: process.env.TOKEN, + intents: Intents.GUILDS, + transformers, + listeners: { + setup: (_, payload) => { + console.log(`Logged in as ${payload.user.username}`); + } + } }); - -compiler.addListener("ready", (client) => { - console.log(`Logged in as ${client.user.username}`); -}, true); - -const client = new Client({ - intents: Intents.GUILDS -}); - -await client.login(process.env.TOKEN, compiler.getDispatchFunction(client, client.ws.resumeInfo)); From 301aa4174d7f02dff018f0f8271d1dd643758318 Mon Sep 17 00:00:00 2001 From: DidaS Date: Mon, 25 Nov 2024 05:32:02 +0000 Subject: [PATCH 06/25] Remove `raw` listener support --- packages/core/package.json | 2 +- packages/core/src/compiler.ts | 16 ---- packages/core/src/typings/client.ts | 5 - .../content/docs/guides/create-first-bot.mdx | 96 +++++++++++++++++++ 4 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 packages/guides/src/content/docs/guides/create-first-bot.mdx diff --git a/packages/core/package.json b/packages/core/package.json index 8b51882..4a95319 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "lilybird", - "version": "0.9.0-beta.0", + "version": "0.9.0-beta.1", "description": "A bun-first discord api wrapper written in TS", "main": "./dist/index.js", "author": "DidaS", diff --git a/packages/core/src/compiler.ts b/packages/core/src/compiler.ts index c8c49c8..5c9604c 100644 --- a/packages/core/src/compiler.ts +++ b/packages/core/src/compiler.ts @@ -8,7 +8,6 @@ import { import type { DispatchFunction } from "#ws"; import type { - ReceiveDispatchEvent, SelectiveCache, CachingOptions, Transformers, @@ -37,8 +36,6 @@ export class ListenerCompiler> { this.#transformers = options.transformers ?? {}; this.#shouldTransformClientUser = options.transformClient ?? false; - // We want to guarantee the raw listener will always be the first thing on the stack even if its not included - this.#stack.set("raw", ""); this.#stack.set("ready", ""); } @@ -411,13 +408,6 @@ export class ListenerCompiler> { return this; } - //? Raw listener should probably be completely removed now that dispatch is exposed - public addRawListener(listener: (payload: ReceiveDispatchEvent) => Awaitable): this { - this.#stack.set("raw", "await raw(payload)"); - this.#callbacks.set("raw", listener); - return this; - } - /** * @param name - The name of the event you are listening to * @param handler - The handler that will be executed when the event is received @@ -429,11 +419,6 @@ export class ListenerCompiler> { handler: L[K] & {}, once: boolean = false ): this { - if (name === "raw") { - this.addRawListener(handler); - return this; - } - if (name === "setup") { this.#callbacks.set("once_ready", handler); return this; @@ -481,7 +466,6 @@ export class ListenerCompiler> { public clearStack(): void { this.#stack.clear(); this.#callbacks.clear(); - this.#stack.set("raw", ""); this.#stack.set("ready", ""); } diff --git a/packages/core/src/typings/client.ts b/packages/core/src/typings/client.ts index 5404366..134f9dd 100644 --- a/packages/core/src/typings/client.ts +++ b/packages/core/src/typings/client.ts @@ -29,7 +29,6 @@ import type { GuildIntegrationsUpdate, MessageReactionRemove, MessagePollVoteRemove, - ReceiveDispatchEvent, GuildStickersUpdate, StageInstanceCreate, StageInstanceUpdate, @@ -93,10 +92,6 @@ export type Transformer = { }; export interface Transformers { - raw?: { - return: TransformerReturnType, - handler: (payload: ReceiveDispatchEvent) => unknown - }; /** * Special case, its a `ready` handler that only fires once */ diff --git a/packages/guides/src/content/docs/guides/create-first-bot.mdx b/packages/guides/src/content/docs/guides/create-first-bot.mdx new file mode 100644 index 0000000..6a33a3f --- /dev/null +++ b/packages/guides/src/content/docs/guides/create-first-bot.mdx @@ -0,0 +1,96 @@ +--- +title: Creating your first bot +description: Configure Lilybird however you would like. +sidebar: + order: 1 +--- + +import { Tabs, TabItem } from "@astrojs/starlight/components"; + +In this guide you will learn how to create a simple ticket bot using lilybird **without** any modules. + +## Setup + +To setup your bot follow the instructions in the [getting started page](../getting-started/), this will be the foundation throughout this guide. + +:::tip +It is highly recommended that you use discord's web version to test your bot, the discord app (both mobile and desktop) cache commands and when you edit/add commands you are pretty much required to restart the apps to see the changes, using the web version you can simply hit `F5` to see your changes. +::: + +## Handling your first interaction + +Interactions are hte most fundamental building blocks in modern discord bots as they allow us to use buttons, modals and other features. + +Lets start by creating our first command, we will call it `ping`. + +```ts title="index.ts" showLineNumbers {"1. Make the function async":7-8} ins={9-13} collapse={4-6} +import { createClient, Intents } from "lilybird"; + +await createClient({ + token: process.env.TOKEN, + intents: Intents.GUILDS, + listeners: { + + setup: async (client, payload) => { + await client.rest.bulkOverwriteGlobalApplicationCommand(client.user.id, [ + { + name: "ping" + } + ]); + + console.log(`Logged in as ${payload.user.username}`); + } + } +}); + +``` + +Lets create a simple listener that will listen for an interaction called `ping` and reply with some message. + +```ts title="index.ts" showLineNumbers del={1} ins={2-5} collapse={11-22} ins={"1. Define the listener":23-24} ins={"2. Narrow down the interaction type so only interactions in guilds will be handled": 25-26} ins={"3. Only chat input interactions should be handled in this case":27-31} ins={"4. Handle the ping command":32-43} +import { createClient, Intents } from "lilybird"; +import { + InteractionCallbackType, + ApplicationCommandType, + InteractionType, + createClient, + Intents +} from "lilybird"; + +await createClient({ + token: process.env.TOKEN, + intents: Intents.GUILDS, + listeners: { + setup: async (client, payload) => { + await client.rest.bulkOverwriteGlobalApplicationCommand(client.user.id, [ + { + name: "ping" + } + ]); + + console.log(`Logged in as ${payload.user.username}`); + } + + interactionCreate: async (client, payload) => { + + if (!("guild_id" in payload)) return; + + if ( + payload.type === InteractionType.APPLICATION_COMMAND + && payload.data.type === ApplicationCommandType.CHAT_INPUT + ) { + + if (payload.data.name === "ping") { + await client.rest.createInteractionResponse(payload.id, payload.token, { + type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, + data: { + content: "Pong!" + } + }); + return; + } + } + } + } +}); +``` \ No newline at end of file From 8d78786ff0833584bf424276bda1b96f69e9ae5b Mon Sep 17 00:00:00 2001 From: DidaS Date: Mon, 25 Nov 2024 07:00:45 +0000 Subject: [PATCH 07/25] docs -> guides --- .gitignore | 4 +- bun.lockb | Bin 304624 -> 323256 bytes packages/core/src/client.ts | 6 +- packages/core/src/compiler.ts | 3 +- packages/docs/astro.config.ts | 154 --- packages/docs/package.json | 24 - .../docs/src/assets/diagrams/compiler.svg | 988 ------------------ packages/docs/src/content/docs/api/setup.md | 12 - .../src/content/docs/guides/manual-setup.mdx | 71 -- packages/guides/astro.config.ts | 71 ++ packages/{docs => guides}/bunfig.toml | 0 packages/{docs => guides}/compile.sh | 0 .../{docs => guides}/diagrams/api-flow.d2 | 0 .../{docs => guides}/diagrams/compiler.d2 | 0 packages/{docs => guides}/diagrams/shared.d2 | 0 .../diagrams/websocket-flow.d2 | 0 packages/{docs => guides}/eslint.config.js | 0 packages/guides/package.json | 21 + packages/{docs => guides}/public/favicon.svg | 0 .../{docs => guides}/public/oembed/main.json | 0 .../src/assets/diagrams/api-flow.svg | 0 .../src/assets/diagrams/websocket-flow.svg | 0 .../{docs => guides}/src/assets/houston.webp | Bin .../{docs => guides}/src/content/config.ts | 0 .../src/content/docs/api/cache.md | 0 .../src/content/docs/api/compiler.mdx | 9 +- .../content/docs/api/internal-workflow.mdx | 6 + .../src/content/docs/api/no-events.md | 0 .../src/content/docs/api/transformers.md | 0 .../src/content/docs/community.md | 0 .../src/content/docs/guides/coloring.md | 0 .../content/docs/guides/embeds-attachments.md | 0 .../content/docs/guides/getting-started.mdx | 0 .../content/docs/guides/handling-commands.md | 0 .../content/docs/guides/receiving-messages.md | 0 .../src/content/docs/index.mdx | 2 +- .../handlers/default/application-commands.md | 0 .../docs/modules/handlers/default/events.md | 0 .../handlers/simple/application-commands.md | 0 .../docs/modules/handlers/simple/events.md | 0 .../handlers/simple/message-commands.md | 0 .../content/docs/modules/jsx/attachment.mdx | 0 .../src/content/docs/modules/jsx/command.md | 2 +- .../content/docs/modules/jsx/components.md | 0 .../docs/modules/jsx/configuring-jsx.mdx | 0 .../src/content/docs/modules/jsx/embed.md | 0 .../src/content/docs/philosophy.md | 0 packages/{docs => guides}/src/env.d.ts | 0 .../{docs => guides}/src/styles/index.css | 0 packages/{docs => guides}/tsconfig.json | 0 50 files changed, 111 insertions(+), 1262 deletions(-) delete mode 100644 packages/docs/astro.config.ts delete mode 100644 packages/docs/package.json delete mode 100644 packages/docs/src/assets/diagrams/compiler.svg delete mode 100644 packages/docs/src/content/docs/api/setup.md delete mode 100644 packages/docs/src/content/docs/guides/manual-setup.mdx create mode 100644 packages/guides/astro.config.ts rename packages/{docs => guides}/bunfig.toml (100%) rename packages/{docs => guides}/compile.sh (100%) rename packages/{docs => guides}/diagrams/api-flow.d2 (100%) rename packages/{docs => guides}/diagrams/compiler.d2 (100%) rename packages/{docs => guides}/diagrams/shared.d2 (100%) rename packages/{docs => guides}/diagrams/websocket-flow.d2 (100%) rename packages/{docs => guides}/eslint.config.js (100%) create mode 100644 packages/guides/package.json rename packages/{docs => guides}/public/favicon.svg (100%) rename packages/{docs => guides}/public/oembed/main.json (100%) rename packages/{docs => guides}/src/assets/diagrams/api-flow.svg (100%) rename packages/{docs => guides}/src/assets/diagrams/websocket-flow.svg (100%) rename packages/{docs => guides}/src/assets/houston.webp (100%) rename packages/{docs => guides}/src/content/config.ts (100%) rename packages/{docs => guides}/src/content/docs/api/cache.md (100%) rename packages/{docs => guides}/src/content/docs/api/compiler.mdx (87%) rename packages/{docs => guides}/src/content/docs/api/internal-workflow.mdx (74%) rename packages/{docs => guides}/src/content/docs/api/no-events.md (100%) rename packages/{docs => guides}/src/content/docs/api/transformers.md (100%) rename packages/{docs => guides}/src/content/docs/community.md (100%) rename packages/{docs => guides}/src/content/docs/guides/coloring.md (100%) rename packages/{docs => guides}/src/content/docs/guides/embeds-attachments.md (100%) rename packages/{docs => guides}/src/content/docs/guides/getting-started.mdx (100%) rename packages/{docs => guides}/src/content/docs/guides/handling-commands.md (100%) rename packages/{docs => guides}/src/content/docs/guides/receiving-messages.md (100%) rename packages/{docs => guides}/src/content/docs/index.mdx (97%) rename packages/{docs => guides}/src/content/docs/modules/handlers/default/application-commands.md (100%) rename packages/{docs => guides}/src/content/docs/modules/handlers/default/events.md (100%) rename packages/{docs => guides}/src/content/docs/modules/handlers/simple/application-commands.md (100%) rename packages/{docs => guides}/src/content/docs/modules/handlers/simple/events.md (100%) rename packages/{docs => guides}/src/content/docs/modules/handlers/simple/message-commands.md (100%) rename packages/{docs => guides}/src/content/docs/modules/jsx/attachment.mdx (100%) rename packages/{docs => guides}/src/content/docs/modules/jsx/command.md (92%) rename packages/{docs => guides}/src/content/docs/modules/jsx/components.md (100%) rename packages/{docs => guides}/src/content/docs/modules/jsx/configuring-jsx.mdx (100%) rename packages/{docs => guides}/src/content/docs/modules/jsx/embed.md (100%) rename packages/{docs => guides}/src/content/docs/philosophy.md (100%) rename packages/{docs => guides}/src/env.d.ts (100%) rename packages/{docs => guides}/src/styles/index.css (100%) rename packages/{docs => guides}/tsconfig.json (100%) diff --git a/.gitignore b/.gitignore index ad0853c..bd4d17a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,6 @@ coverage/ .obsidian .dccache -packages/docs/diagrams/*.svg -packages/docs/src/content/docs/**/documentation/ +packages/guides/diagrams/*.svg +packages/guides/src/content/docs/**/documentation/ packages/test/**/lily-cache/ \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index fe39fbf48c8c58ad3b9fca23bde999c43de561cb..88090afa96ad99943bc4c204691913cedd155119 100644 GIT binary patch delta 77619 zcmeFacU%+O*EOC5A{i7F8^wwhRHRBkFccdgDA-X^kghaof}%k|1r*z;V^^$TuZRW3 zb``AH5EXmxUF^TLGZW19d!OfhpWpi{|J;1MS!b=&_St8jG6Q=J<4*`vUA@PwB(Fr1vg;XTc1#ctZ4gnhr=u-o2BR?`EDVkdC2~Op01ndfA zz=K0VV$GEi@uEP9NK^+xJv2ZWE;6341yaR@z}mnX$gc&w3;WvuF9Au9h&K<5j2R^w zio#U>6!e;;hegN4g@{B&0uPOd4mMAW70pE^qWoQ76O*n)y&xq%+v(3p^v zBpN{uIOT^%1|}qg1dCRn92p!EpO_F487~TnADb8wmm)d@Ysi3*_}I7*bUXv)>VrF= zFbym)C?<|vqJ&NZ2|#Dijnl?JXaFHNWK?1p3Pic^WZWwt>`d#*rbwDcoI!Kc;!b=S1AWi~4J|Y z$s6$|$_#ju1^}tAD(F<+NwBOLuo-v*APrGlXzwR0)-v#TXUNw!MOQSi#z3;A07wI; z=?R3R-XPXA$C=VtBx;EKSVRDY*hMx_%Rs4ikGd~)Ep;iiTcglgYt&kg+RcFj!C^?+ zKyb3c6@g2({ux&{Fc7lpPzzx!COjV%7Z?;0FB&SE0D&qV0g|ihx8w^%g~Wx0SpN>F z_>icikT{X(1Uf`pX<5Lg!1ZF@5HE1*NNq?b)>qp=ITEo;t$&5>GoJ|N4cw|Im%BYCA;Ae;vghARdAmz^ll40t6Z$aMxB*Qm!;5%Ldq;e^N zk&zMc38Ky&c|+O&>xz`3v=~&N9i|hIMid_&7#EB5I17Gcqk!ZYUm%qmW65{m08SIU z2^Cuan}btuXMtmi(rSW}XQIHH06zk00DXZJytj!fk+2ek(?F`w5J(lO%=rnd08&I9 z2a+LMfaK!UKw4`-F_AHtcTt`lKe7+N+R%H-`Ft$0xESOIIEX}WcG^`zw{yheC*d9v zWYGm6x%N1a=BjyD-r_8vE_g_MP+(+Wa7aCHJ@B7Syut5)RQ>?!(cG$Ct&T_o=rn-W z&b(&~y6^)m?gIbULB?$e4t zo{`w|hPZX-%PmAZWathc^`l<-zjrqHpFR>;NUnW@g5-fUKyqPNNOXKkbdYFtd`xs_ zl(T_Op85e-8Ur5!DF%80$-q&8@ge4RwxUh~ZzSZKdGhVW#zn+OjgA*3g+@eTOBOv= zB7-cn>&?&g4RCU$K9Cyj>BZ+~U}WTx&mMfoiO~_E5!n3I4Pk~8!@@x2k`AS0iqveS^_d? zSp)zn`uhk4?FaE^K_{Rt^!h-W`+&jxRP`3n21o{v9m3cDK9G-zH$d{FlhB^2(0)B2 z^%oQrlNgz`o$DEM3>cmwtU>w|9s(%hFJpBf%HMkK z`lkktMDqo=0IB0SF?_>w!D%Gpfz-h;U~}M4L^Q>M`lhlIhLVBmpvwa%!xzT!Q=k{m zw-*~27axLk91@)nkq{9Q|20m@kK`c=HbjL)%yVsO7)T9hCGvAN4-Jy*l|UMKG*BNH zpTyT22_(aZ3HhP+d^-}Xf6`xp6W#z){oX)|jS?ji6nx`jFd6vl75KXpzC-nnrrtW; zy7MESGoG(^Qz)n2=}v%?r`8GWTu9|R!mV^%e56R!e#x1-tZYf)%4Nb zIwn-qc{-hf7?8Y>5FZp19U7z@lMoS|5|SJe6crd25rlJdY8GEn zJq>Px`_=A_jf;tjO^6p=%jO##6B&|-P!h$O;|`t}B4TIo9ZUm~0kP(h5m6BdBJ~|# zb%&1#!JU0nVnn1eSafg}Kd<>f3X}yv3Y>I7j}iEAA>UKb?F79AkOH-qz(3~j@o*bR z`WYa#TL`3f*8?g3<`KtYNJ~Y63Pu2_-~b@43>PW@Y%kN-lMI?lT zV%5Zjgr~$}qxo}b)>_ENj(Vj;#YII#pITKjc-N^L zR6j4#2vJWI8XuB`jU+xcCO!eyi`20a9T*)Gfz=!r6B&u;J5jGy{N~mHNSj%0fqz`d zFNbm<-6?jhg#TM3!9r*OOcXNu0qMZ71hxd~1DgZit>E!8kQ&${@WnvtXcCZi$>8{q z1Q9;L5uu@kL6PyvgfVe}1R_^lnd_jOoe~LhRZAevwHh0M*9MmX$<;sC^U-db&+F5G zR4*nvB!MPVx{=p=oAU$O3r;a)zKPe>9#h{dmCy~4uN=9VFQDFjn<9fYq-k6DxgQl1 z6G>OB=or<2MDfrmNd18nv;%~4u7chfNCwwNJ2b-P<|0u?pdOHd_WgFg{oXrNQ>08Q zMS@1!ZzqrLK#B=Dkao!SyZDBhfRk(0*R_jih&=WPs0Vzrn?E=5f%U-80%-~l2t2fa zpL_Kxi46=335`%j2XEZVyJ|j=MwA9@030LeLx42m2rK(&x|Iyu#~ZoU(K*C>M*V!3gL>rZ|LS1_cYW0qsITJcD}O~X-ybbUPtt7 zJs*tfV}8tGK6ccn?HMHkhg>Qght;RypL@G{2mGJ!aR2=Vr~JRW!>P}VKhFyFR<7Q{ z&9Ctz{_{)_&H!cgJ2CYL)jO4XtLy}qk%yWBX+Jo0oyU6MHNn5%5VmF@l{i=X0!Kw2#mpc?}NfQ=DNS3fqMhV@-9Gf{F;xvf#Og6zBe13{HgYY+OUwQDAlPw z>@z>XJ%Kc^RFtQ7BC7a9zCTd)7Rd}9(1gW(;X|qkIE_pSGytkSqV~j%r+mjz-}rj3 z!O38?2i2ab`<>4(L%uHZEq?Ir9s#F=f~J5CHfSLE{$tm8gB99_N%M?ujGxl{)AslW z{f5~Uuh=)LX!d~v4dYqMChlxilM8ionn%5VlW^E1b-w*rov6ikSs&9DhB1rI*O`>* z#reF>-7xf2>hSn1_n*7hwN7R0nznF%b9`d#vhp*R-f`Qn8mF4PYv}QCklwu3_x;AN zE&AB-*49TpDKc`ovttK-TDsf>z%_M>U5~Elbf~a#aZbh+q=f4 zf34kp{K}s4+3upyL&smv8GG~jlO;>~TO9N1buY3<`y+ez1sM00FB??zY?ruEBf1)H z8h6k^eDY#Q#g~Z(`=>b`nO^zXYxZla2d%=rpD_2?z$)d2%={6`(1zbkYE07MI&Y18 zRqK>}S&N}Hc7566r1<$TLT`IlhZE<rwoYcV{x?I-H2NOwgaZy-p{=Lv{u5S!pkvDiy~ij7|=c4;LNKY zy$8+xtKY*8`h|_p#MFEyfhJI%4UOkukMZ#N8TtUv%wey$iEC zVwO?s#iR?{{L8*($Z~gCUlR9AsoUkpg!MhrN}sJ;Ww-f;!XVu!>`ZC9k!)CVle+!` z9@(uudHPA;eJgewUl|a=W;S=X+`X{>g5w2eENVU<*?&UUT6c3N-E7%wl6Kv3<5K++ zb3W;q^_lu({@R}Bm#lxEeWGqvH2bER&#t;H!o|upFGlU@J!q$v(1meOoHAu-OtV9| z6Kuba>A1|bpbuCO1BWs3NMc8hr*|;$_;Jzd|UkR z$K%28TOVcnHfLOBpZhwy-J4E*%ij*)ua$o||CjC9KJJ^I)s5SE{!z9=*RRLQyT6$H zctY!=%072vZXXz7|FX+sf1T<2=63g=_v)@Z!p62WG*kz(IzC++Y>F`6HfRDnr>!A- zs;P^MZr{^srK>9Kp4I4BH2T(q4KAx9ON^=_KQ^gKtGWB|=E>^TpDT)2wwtu?Mc1$k z!Ku?nxAywW-rvJX)rA_bOeX4sVZq~p{FIta}sq4qCYGumG+P09Lzdza1 ztI_altZ7?QS;gns!TVP|c*`2LY#|%jw|VQ9N3YIcWvwy|&rWUjWcQwc0Z)>XF6Ve< z8TY?6?EbLIw3=*M>lSQYYZsRpKgySm9{FgRY`0#C+0#Cs{5G|ErZ+X|;PAk6r7yf> zlhym#@vYWRBgSZ#B)&&qXQ?+etnc6R`rJ1Eq-#!)20Sb@WjefT5ad_LMS+zD^xX?v= zS$OmE5&S~EHHD&&v9){I&w!7um+Whqhx8vs1hO{im*nQ1z_y*ZJ z8@*l;YqVPRp2rrqcW?3MF1`Cpv+HxxRuroD)_Ls=*%s~G**Wb3=9#*%?xsFU^+uMv zE^hpR^-b%lcN6vMSBJkk?9~BWGbv1Y<@V1G>UdO#*q@>GXKsE}dt3!|}!q^*pN^P)|~=cBc*f zSLU|ZCF@XE%vyGI-?gY?xY%@K#JDl7J{`aHMsApGV&vcdMz_X4YAY-zPdc|MMR!0= zFU6z>Llf`q@nkJK8OjdVNf`Cl)4fkw_fBcz;cQ7KXX$6LNHmZ=+sRQ{TMO?4C?#IO zW?49k9oQ0lC$fyCGjl*H5;@^Om9qMla`9(2%hFjKz?L8nPMIMY#U|7tgn@vsUZ`I@W7R(#rithvn9yi3WZu1sak)=de}IN zJF!_d&P+6ecNMCd)bAaZv2|uzYJ zDo~~d>uV!te88xNlr8g=Gs}cLZ56v-U6S$)aiOzUm&6#d?3`tDp;24hjxdQOTii*; zoQL8LMW$-O1UDJ79`?XSe>O|*%q)UNb+kxhu7XkjV4daSTC9h?Gt&<@KGGzrQDm|u z&`v=UynNeUhMQVv6wqbo+Q}I=us&caqs4RB5(j7IB{UmoGS+vay$tsq7oK3m5n$wF z6z?Qw)`0m`SANejj?PRQ+{ZdXldyBG<%};F*{eg=Foj^&syuj4_Do>h4K-P_$GY*#Smg1iNf61F;6tR&`{H`(bx59k$Fu&Lo3TL2cG|fV~WaY~XGF z21YHyKk#5LTtmq-VpiW;E>35&6wb^YXtt{I`iQCqxPVeqDBoEw^#b!@^{gD3Wk?NI z>2RaCiY;+=7JIUci?etl>*3}n(u_2BcD+0)PV$%$gJP8`HCuFm3o*2C4Ad4e2Q z7W9P`|>+2`H|2YkQ9qK=P1({O3Ty!-J)Omm zS*DjWy71`bES=O$B=T2n6c3Qsd}ca~xzMEcd`rHespSC?UScHqMk!G-PcDO)nYnQ_HV zBP@Yf91;Va#e3PTfzHyAs9IN5b>5&_;yTrhNMg)c&!GzG3=5ISi}fDrC@n{-k1Ey1 z5}u?KGaf0LWz}kx7J>~>m8fNf_sFcbi=#9KsUa%uBvN7Q+2Kywm^Hr1@kk9*6}y6z z58t?rNHkKVWgx|Cm#Z}!#G|S%qeN<$s%wO{^g5Wos+PPn9@AB+MM&`mKSHXX%3yo= zfDA@}BTSOO&@m=cAIoDmn2V}K3NHy3IE6S)L~rcQU{nvAkAqx)HW)1oZE_y|)8pRu zlytx*EfP(3z?*br;k*?YvAzR4&~c^bi@>HWhg4&BawlFL?D$V*X_bxmj}}HirM1hq zP-elmuof!qiDZZ&c1)GZbnc3IfGSpPe#^j6Okmf*$P%o246-i9)D29+`u36Q_XqQ( zl4wne|D09BGC)eS7FoT7EaG)bOSre8k~XTt_&G9Z|fBx{VKAh0Kx?Qy&MnEOIRGYcpA~3iAC{rI2 zg#fNXHgZW1H@0+(Lh_dztGiVpHG*qA*&J&}CWulfg^UP5c~a2q#IcWT*qj-V2ZE34hU9L8pVhu>k`XoL-1AI25Na=>qi!Df}P}2IlSsf zXR0L5lP!%?F!>P3D#Qz7=>r(e8Ez-IQuXhR-%wF8x+v}e!(yY;Db!vYWC*Kg>B#hl z2dGL7)jend7`mq3P^A)Vcr|nI=C{e3s>3lAjPJNC!CnSJQH#M~mD~fPn8TjG&|ZdX z1!buMh8ZLH~e1Hj17e7*Hx!lK?OmoiwVJyrgUq!dcQI2^l21jYx14yq&SglR}S!^j}eAf+mQ zP6VUq8fWeh%dMdB>Mqt4+!qhAVBe$ro>#{3g6ph05b~B?hnD<5XeOGq!PlYjp z71f5wrXmTKshdEAUV%c9r#h^d)_#2W@I&$kqXF_m$_JymJo^I14~at38zZ+x9)F3N z493@^;ocTl4V5c;;&|d`hTJq0%z>|mR!)FXE0}5IHAGNSl=8Pue=t7WkSEOpQ?O?} z9T_8kO}EE`Axo%p6O5vsx2hR@hdl}#2yMuLVA%V>sIUECyq9p-lpV(TAC0``!F`6j=J( z5J_zS;OXoa)c$qGgu&c^{iFVn%CP?~2|VA#q8uMUn-#|SNgGhh_j{PTt}45d|w zH9o{%20~`>p*3}kNR$Aknl{-NuQSCdKJPOnEH)SQ1WKD%J zz#Q`?AOvK$z%+*a7o!vNx3yx+h+|k?TZQZ%bWFZRXKt~7`fw2xxcYY)snJ+0Ue-It zNrnV1BYyKe2}TanRXtAD#UMPv@MK~mmktGUQU&=Mq$rS~W2b2x&)dz1^++&UG&-v1 zSQgA44AX(VF@z734o0KrcfFHfG>v@8#)ayRhLDkpZAA6JTZt6e z!5?$YCt@B*mxUrl9^%h=x(4iqLfe2UzNN3gELm@RN2b{%k*JfZ1sazZm>n4J^(kQd zDD?-+nd4wCs*<#nw3WO+(6e05c!H5H`GqwLjOyXcSYa;%aRk8)9Uat6$!zIl1=DBm9wbhIUcO6WDdVCNWW~c+2@_fL^MXHmW znFeOfGYh$_5Ue{`wcGW-Lm^j)@M(ts_$LxQqKc{NYXq&6Ibbd*sPdUMh=O)@N$qKD z>0$-rJ&iX=^_*35=Rm)stM#VoR4OB(<~I zY{YkHwyO0dj?8MLc*pss&-^=vN_Y7#IxDb zY0lc9e8vN$+*L(7&Y>byI1VX3w*VxYHc1;eN@^Vr1;#9^Qv1xim$sL zDZag*)ml$hU1J_ne81O_@>I3ga=yCmXr%a-3#zrBNMU-XI>{FB9^s$&lEG|M9zpOk z8d0ZE^O^Vmh}E@JFkKe&Ht?@P7lZkr z2p^VD!JNVH@|UxhE#YS!@qtTrsK8XqR&OmBZ9LjoYmL><_RMXlU7+$-8!hE6Lk+xS z>I)LymFE+fU)uv(|ior4r_!BeCLQw_#tJ-FiF1#=bx8NlBmn9Y3Ds2)QkQJYy$8wIliA{F76!%HxCFx5|I zq_ejmAjwy<5~OG|R-G*Kjx#!2dEfCrd2zYI$RPAV0Y0uLf(=kJN!eCb_pq~g8|$h1 z&OYoc+oq0YxI(W2EEc8zK17f0ycwu_&2zK{}Zgt`z@2}a8im#isr#$Y!;>wL#yVB~*(W@UT$ z%OW2w0bpLp<4bM^i{{yBds%@xi!4-v4XrL%0mko;l-FV}?{L-4ia88M)50^ILVmvl zi?)}6P~7ra9N6!Ix53CEsyzKx`}ige;Ly7GPxkxmWCXG(7IsXiueYqS0_^jgn157rDg#FzZ>Ykfa=Dv-1?v%ERy_o0I1#ZCH#@X3s8hlng9pvo|WF3m|l- z>$3DIQf`!DIvnBmd4v+~9{yl7EeIamymGc8DFHhYUg>URuvNRVHXAUqge~2rkghMG2f-ZF)-Gi|cPpfxrI<5Sx66>SSEVi^#T#XR zTJ_urhb}}4KVU%WCQ<>al+zg+Zq5!TZ6pS%qzkGP+LyLHD^z7NkfK;t9RkcHux?;@ z5rR{{(K)tsl0xcnj&{4VlN_0Jq$sddJGG?b9IKnAkTyC`exM&*4nm6N54T%yxn#+C zR(F|#xe1YG1s&ihH@u*pRJsoO2#o&`WhNNKBd(EnBLZRzf-Eb!v{o4%PI{dk87HJ@ zH^2?1yPO#dHmsVR0rRV7Z7yo&rGe49mv9JIv&CS6)$A+S?~;Qr<3O&? zI|LR|&01Z-(Ob=?{EgiO``wD-s%FXMV85;Y1U9(374PzDFW@0bW zyMOs@{+!}Zm_1^1f#&^^J4GvTMd6C7K3@w4>e@9?(ug1 z?k^MyKa8?mdl?ALfa?9a4oDx6-({Wd^JgLde7XjVRub;cIMlv_QEA+<;5W|)bhoFU z%I`voN&a`gKT}(v%+yOOE4S(OXSiIVEEaNt)o=&M77zt;<*$libve9 z3+=^E*iugg^9CC2+WcL^^r@QRt(D$bFje;={8BG|u?9C6cSiPH?L&0KD8NRjI>l}w zdH9^wy{3@%dqJ1locm5PBxwHe17`gA?HQOiG2x?7o!d2t1-)c-D-;slSFC4+f^mDr zj|3BnA5F%A(WGIU$*|W3>8E;;Ht03Km=Oa#y*g1HLO zMO6e(-jYUd+3Xt%$&j~fDe`8$#XqHiJUm&^PXoYc;#5H_Yxa)!0{=1suXkk-$Ylte z5c|3y6F_Jb^x+59kdh|v`Hh0o^x?DgD7g+<0aS~fi*r=^f!{T-Pv9lH0~iI7>aMG= z1RMCz?cYJxfBI9^wfRTk#zURqqtSICvOHAH)6b{$KjA5mewxhqAmt7n@xEEkECM5^ zBl564T?TVeF>+ml&#dPi1(WcZ|7j{_>4sc-0?dQWNyjgFRnc!ecw0HjrXn>M)qek4 z(@iM7`A#h4`t82(Ol9KVt(=K0+ADAqg_Tdh+~~;CZ~ygoNNS^s?(c`Z@yH5cy{|bk zw~^|O%7}CH(&`(()FdeWyNb$9laNIP{~_xrvQ#nfw_2*nGx^RhQEb-Of8)Qi*$))@ zdm#Q7O?^R@-GB6=YRc&c28JyDVQmtab2TdhqxB5`_OoD%AG$L-Kl#9ef?JgH&t0C6 zE^4!_pVt+OSo7z-l(dxUiB-SHE;0quM-xdh!xv$D0Xq=EhmevT1x`pwbAc05vXj6G zDQO{api(7R3c^1jWm(~i&{`-*NdC7K&<;pha{3YwP7x)6(*RR}b$~N~Qs5k++#InI zOb}?E=OaT4$O#4hcaVxM63S~LwYN;r3CZ9UKpo&ZAoY`{6bj@4$+9g1C!`A71g?ox zai^dYQn}p%C#2*ad{MbVK_{f-K7kWbJ>>yGAS9tk;F?Gh{Ubv3A*6D}K$4CKx+d0x zeo4r`EaVeXy{iIVfEqZ03qzG&WMy<6c71%+*jzG${6!L8Zv;)$x zoPbomD-eG~_}whkhmZobJCN$T18V^X0I59v+dZWFNrbgM6asw+slYIxJ}^=!NdG7h z>09&@&R;+(cMeDfT?A6ZRwxCb5=af)1JZ|(EPN>F z&jfr8qz2vt=|f26J^?Ah#301A@kJ+rJ`n-QuzCU~Bm=ySkRZ!UfYfFyAQ@r`qzYz0 z{1J7eF9FE_OK>W04Wx25z#6RHyXLBh>ME2bq+Er-390pNg5DiSS-t4%zd-86L&ztj z_B?@PzOSGYlHN~Ch^YQT#{UUYg9C(iG?ApiLWe_uiEmPDl;K2%M1e#|oT~ zcs!8GB?vkpCC3R|NeN1i7X(78FabynOcZoYBwcN|CQ`jLp}ZyN}zd-tNuIQjp@i34&C>AObQu3I< z{|Tx5aUuV|Ln?PdXz!Fzo=_G4ngWgFvQU7KlH~%|M7j|?74-iDPUispbqHY;G-fs6d(eU&Ip{4 zcGTKHIs%)~{!0xt7Yb-1=`DnO6CwYGB#?!D zg^YhfsyJ9^U?h-65G>RS73vX^PXAH?84?MktXM%$B!bT$sQgnADNQ$(9r0W2wT^YGajTC<*PzEEQu26uG8mKLBLTb1!kfeqJ zHvsB_n*gbumIAgCur-kCwFQzPWM-u;sNbPk+J@S|r zkecun+R;Sv;9x2IPZj)x0-8u8848^?nn)lO8H+C}9}lGb1R#whS>P!^`e-7R8!zNf z5HM9Jmns$3KM7QDB9Ja-nLz4ziBRzWHB|l&I-q5^8s%wf)(My=)Yn8BNWP%|6VevH zN2sqXM1pp(BSHm2a`7=BHFO+E=l2bP-xTt10m-08K&t;*;Gcn1uS&o#KnhMROgNRV zNfR$1xKdOf0?LRCft1$_UvwAlAaE-njo21Q4R;38hmeA-yP)?FbVAx%2MYQiK_{g8 zgMrlEP?e7PrvwGj2$k@6B*TKB)A}AQ=gHgyQj0I9hV}JNRKR0`ega4zO{9)Wpc9@I@(GET z0ja%9f=)>7UKThZ1#M*wcRu5mkU>ZzxeFxev7i%D15X4_NF6*A@I{SX;~%a5_bnXx z2%mr7!u|Ue?%%g?s-5KDw{ZWyg`>A|bS(Y*7EX1D{`(d#0RchRjB)gZ&%bZs{@b^1 zeuEu7}tI(jz!_buH2_FKDu-@^U-7Vh7- zaR2|kh3obI&|A2@DGkJHthrQou@@KKOB~EK=_U5sYv?X+qs0~ZhL1fbCos!4B>&!A{O$FklxqmtZ&dlwc3%Is{O_EhgB@y(K8*Jp9CimHV)b zt3TS-q~h%A3PqPjs1DR%LlDrM2(1gxxXj6ZJMua>}S(nD93D$BE5t ztCV_kLu+>HANsu372SgLRznBjrX&{qtV6&3v|rsQP{par}c98C_mo z-1@8K8sp3F>ztn-*6i+!*pbe`OY0TTukxuIKB(F7>1&lU!TP7O{YCN6YV$k!7o{_I@cDyUh_!qbnr}ZmR63V;BcHA&Y={)#V>U^^vPkg+Zk66EWuV>o9DICHx?j*Q=BB4>`pV+(T}AB!Vp814uh(r> zQd(aqF-CD1rG;S>4ctYQ(X5K zn}7fH2J`I`yWiT{bk*or^IBdQH=%~>jD61zO%;&5m{&({DU^}AO@QIVtub152 zbI9w{kd3uVGLEfSH(}D(#dg8^%KQ3<%Z``Djn|6i_U7#L;hf95DQCaCd7#I4Q?Hq0 z?|)ixeQ2}o#|LOwd{nbxKle`?hYxkTRG7Zz*~7C%M)F36PRrkQ=>6XIVfG^Jnq5oA z7q`s3m$&CO(|@D6PJFShc>iB+14@PrTXp`cyu1TxEpi$v7YDcWu93G#&6b zJYhiLMXqwN*ie~R&b5EkReQYiQoZDdn?p*%AGRAP-a9q>CKEGParIh_XA>KkMw|%m z;WG8Pa{;y-lS&xQBGXW z5V0W_QX=jxIVs|HmWT&&?tWrDPFf`H?OmeT)$!Lm4y;P)5N1;^!Fs}~URv9q%=ZoL z9e3o_>0ZHuW|-714K;)lS2_vzq30U+mtr@d~$Ejk2$-GB~!p;pD|5Cmz&l=E^+HF&;X7(&Dfl&fPsN z7^69UF%g3r_kP$ZYEYM?rQcr+vpc}reQMQXAm=>{^VGB$vvOXu;j_;#L^ryA;8xhY znf*46`&G;0V$HkS4n6&E+O?l(R%>d`;UgbbgeNA}uWiAeJan=oXv{?Sm=5#DjG1#2 zf0b_iyItH5wCjC*AM~dMHv7R#Q`)aSMpYuD18SERByehMuUFMt|i-6aT38j8_3+=tSoR~1R zyyL(bSM19BN8i!@abfJ4fuBO%W$ho&N@~-#^GSP+zRNW|bEnQl-NTzMRFgVZ(g zubXg1BM=dKL6TtQAgy%c?3-L*V}!;HP625j03YW zv{!n+t0C`v$nn{?ua<3WPR6b`VEEs(=Xb&iP4hljZ7Enh``}F1m>*k@E(ou@o>lc~ z?&>tDgT*?X7u_~aw(IJX@owA`qb4W)I&nu`>%BRY89zK@cE~BKy=Se=zjZpwMU6y6 z6de-x_PU|jaJ`}p@o&HN++`4~?^e%jzn9re)_d{0cSkE4-MgGCo}wFG_F+HQeb0)d z{G+2<-fnZaDky98a?h=`EiWWHT}<1^H3<+Kc|Y-s-?#g8a8yafwzC6Tv`KakI=40a zFY&wZHeE}mzK%U$kvnL5+ifo%H$3(HW4ZID_?OMcik|mc-sylu8j- zdIpnyEdbN62Xnn|Ynr>K(7SM^_t-(7uFh-6CH0%tyQXec(=ZFq;+2iJ{+d`>(|y2d z*BU(y&Sw{fY`3`WyL_3&+|M3QZ^V6RxI?_~noE6dVj$eG2iuk8u88Ybgr4k2A*`nS z{CvZ0vQNMGD@O0;XFB+W*69(q`29JNaZj@;3l?4qKKXpr!>XhyS1aAjDs3v8HGXC= zM89NH=ed^`znT4Q*j~COsn7EJn)c3Z)uyC}zu(J!n|tdUS7oL>+>q=1I8`)o=+G@6 zDkuK9SjTTjnnToPqtCS-J1-s3LKDL18=d zk~tk(?kXL33$|68_fW(w*^Xf^3W9@wY=;?(^CRUORj9BcrZ646oR)mgx6ezKLlAA zgtPt--g5dR6q0aaIE43HZZL%Ka0s0PAbjM$kYE@A!9N7TXD&Yk!YLBg1wr`2y$ynp zJQ{*tC}IkKQK$N&{myIPN2{uJ$xTfoy}Ni`v}qT1W!zW9`p&_-C$-<9EWrQC!1GGr zmW1bfLk=hQUGlN*(lTe`9sOeq{IB}?GtaZRrenm0UZ0B{G9ojkI~7lsB_-%gZ@*{Z zopDF4o=Nqa?keAylDu-;qR&&G4N{KxZ8u@tWu{Nc<&jr6dYa`o?r_zA(SdDg7d0IB zgL~=)bFYm-V9uKK>y_8ui|zW&y?yR+izBJbmA&^o);$YP{dL}9`6$a5o{!6GPg>m~ z{muQdyOA>=oH_Q&I+jh?->BF3o*$ymANSE{_E+_gaTPGDvu^S|k4I6N9(#=TT`aqr zd#ibTJI}N0XAW_U9=9Q`ro+t6eUq2x7!9xi7%$%!IrdD^B4rV%L6{zR%L1 zn;41xZpORH`r2REu>%kExtOWzYoI;ceW`=X`AG%SQ;O1NXX_0)YEh%tM4PxLH;xBO zTc;Y<+3q0qTr$PN^ugLg9eV`S&@fMowECmHxmKi%&l*-%H*?{&E9HFxU)+1Ix4h%4HbWxg zm}#Sb-1+G9)~M6&b_qF^Aqxg3EjM!f8FR^qdpzRsi}S-*aFt}9xzUKNy9`&QJ@1=$ zXWgTuz$~T3kxmO5ynNK4$tb6NqO7|w61nzf`J0*@9PM$W(cBp~UZ0A~tUq@1^49Lg zeS-39YnUh1H18*8W3)5VP3MwT!-Nr*;-YiDXZFuos=K4(IG@oubG_pqd|k{AZ#vuP z!nFBKrw=LZ)45{G8=oxWJq55IRT zZ%$Om^sNf_1@q*MHWi$1KD2n!ITOaft&f;Cu-1K6U*Fn!j;)u5c{-Zr z*^GFl?R2Ky<-WSB&p1}Dvb~~J*kEIM&w__7jzWPMeJa;`Y5#4ifKn z5wBC!evmvYH|N7vt%ls@QC!y;?BY3Nu%$3!?)n(oWMZ(kyGCNM*W|Jyv3l}iAuJz- zB6?!(ULcAL84Kkl6{*8HM4^Z*j*3J<(C40#P)LGzG=%!x;%Err@eqEH(2(g{T9&P7=~p>LVt&qkrlndVEp zIxTH=$M=AJ$}*D^Z#UQ-OyRnYr`uyZhG51;#A66m6=#+Np^$`}BnUQK1qtDkAaorEp);2?4uYW)!b=k5oI^5% zQzR@;hTyX(@79KxQIy*sz^9XLT}DU z2_bJ9go#QB-dqU@L#9J8PlM2xOG$$u%Y;x#f-h$_8A2fmIg=su=PDo!rKeT(N1H8Q zHgW8u7T=~0n6*de!mW<)6Y_j-kJ0MV^E7w=RQSe0+3q9u^eoZKG21hLcAX6US{IJ{ zTQnTFbn5y(t9`~E`LRf2?F~fyKSZ<`W~0NdQ_$gHE}tUxlu8IhK>3NeDFNa^lA&U* zUtjSc@i0z|$dwGo;v(5!%w=N*Nk)jdE~9`W#oRN(0K_5IfFw}Nl@g8uA}%FCVy@+A zV6d1Q5A+Ms92wJnZqwB4*;Y>upUK`K`^(;A@aZ4N+b`oD=hj@|dh+&+V}td)uO!U5 z!ikEU(iCMI%^kCyzwNMjG-Gb7X9jQ0Z8Sy}%I$!e;xNu+Ga#J%vO_%BFG903#e~LB ze|(a4U3lxx67zXBwZo>C=q-KQV`hU{Z}&=@o#Sd~ckVW9bI}M;(CBP5xA*{>AEVj)W2Rs4p5>_{k8D2F%BjZtk5~6)ICNWI-hKVT z<`Z5dCr_C$t9^UZnCSlXw>x*qQ}&JiF>XOpq4YpZNT|iZ;TKAFYcwCpxn^JxQCvkK zAeyt<2Z-Ua2x2+?jexP-U1D+EzI;GD=dd4;z|AE{K^4)fIsrA?J zb%`3IOylNmgSC^nl)VT*`^AXJ9@`L+>D*)tn|ngSj|>P?xxf_=7A=9WWh#W}+?S~k z+?PV|p9Ud|%by0JiUhss5N2?G(;?(7gHS|54##9d7_uBfR3?Pk+&*elwgN)al@R7~ z5i21Sl5my;mNQxfA$%o-iK`$i;7Uj^Tm`{A7dx%`uLP?+^R7Vbc)#ON_f~qXwsp!{ zw9?+c*md94ET8+lHH6B|5SDXhTOeE`A!iGOm0Sf0nQI_) z-3lR>%i0RTel3KeJQ%x1GgxI0`m9gN8@;^yHGkW(P6gXmb-8%AVnsyE>hO^zUDFrH zI|TLLq@Dk%pZ>RvlVZIr;;r9&5YM`~^ zgAkAW*V#rh9=gBwHd(jflkG=&CsVWG#chwSp26kSztQfjb-cywnO6O0u6%rNZs62K zxz-C#oPO=!@TXgyHeAj++@FWG$m-}Sne#F=T{m;Xw%oRQ&Oev$Z8|==_mdjCC0lQe zvNMQzF}#!X;DqvnO>No?9O_jyw)|V4BUj6IY>Bk5r<<=4WE(gJ3xsoD4>z>QhF|iz zKoY7*NZbfv6L$m)ip$#oA!Ip(E!>Xf5QgMIXt)BxHm)oef-E0G=hYB)aFbU8u-@}>AKx`LqDFl1DGJ-$ab#>=TN z`8$J}Fl|TGJW;DEdgHEx*Ru{*TIhG`yXVEQI7@?@C3n9YulkVPAh)1fqhC%XQ(8D& zt((JWOx}4->t;_3OMa z621pY2-b`E3g>|%gflD<>v@&B?;8=fRe30ZTi(gYA!Kj9Zi@o@<$5D*GmHzS9@lA` zk{I5u{rblngP&{nalLcv;pU^B-6FRPImJ4TwmqC{Nq;|F{Ys#mE7^o*PoY_*LHx-_ zeS!`cx4myQ>S zL&VpSuRkT7FtQ7n@9g7eeM8>ZKl$2eqe+uiq_}O`mVWFC7qyp`_hPu=2Ism2_L>&L zFYA^-sN~*~aE*k4OCj9m0(V2m+=mgxU#-|s@MYBHpc`5%o>m0i4$`e^qO|!u>7!4Q zdwl0EHySi;YCO2Nzw@fA)2n9g^=LF>SW0@oPD!tFUF-W_u3bm-B|ZIZfAvTEW_rD$ zTHk8Uo9C9FS1!BT{$R(VSaXAk_Z<@qzjz(`+V;S-O?TY4t~T;7xbxuCG3nX96${VS zwQn}_+1b087mvEluuOl`b8A4`xgp(m*>2ieTmNWK;j`6kuib6ut}z1- zIimtJyXXM?GO+;tJmyMBa6bsad@pv&rG_2tT;pQ7mj|&^#uuJ& zHEJ{B(eA}gkCQ`+T!-&y)ValxQ@J&#mDjtmre#jvzJL{%WiNl^Z@Bhm$Ef5X6K@4y zx;VJ9#RP|`Q_Zs1ycO)H-!Xm8MP$L;ydq4aeVe|^(wFW^Px|E5f424Qeg|jG`_z5Y zvyty?Ph1@0RR2ImpNG@6o`0J?&iVM_)f*R7J@%e>d0%MAN8P!p!F>)Mpnu{*9q%tS z2ko0UvwgjDNBS*&9V6>^xz0#~YyF>Z9@q9|znzEQ-OYH@y4In>J{xMjH>%a7Rhg`x z#jd*X2TpCznB4e;&-;5?-Hb=NaNdV7jaO(Rf6WDA2NS>H_7ULK(0srr zXS4wDksC|!i7O%a%$Y0%RB0e*5>1i!eu050=5 z-i%dHf%ihClDt7I}s*Y^gB4hM>{Zl#r*I+BdzxM zI-UzR8EPkPx4+RqE0b$;V@`D2WANtXenmw@!g>4nUf+s$J)HMFsYlhEC8?Fq2P-Wj zL)YnKIdhdK;INl#u*FFwT$i=zY0+QkY57_xIug!p9TfMIP}Z%3!brGxq*ReIa6Oco z5^nW+D0!!#NH;*ylW_euKp9d3We*hZI+_-{?kuuBWbfe|`#@`8y_d!>zqDGoZRLns zaf-Zor+fsWH7*v4n$oSyAZTJL!BAlb-r{`BeH3kFSTeP!$O`VXd$8Q0bS?TLLy zjJjry5t%UCYkj=aRwDxSxw(%pafPKYxAOxGpuU8Q%f_bIoyD7jfCYlAT;Nml8|{G zLJ#!ft^QX!Xl}JH76sqy)9*z0`shc$3r>ejr*FFh}Xfwyjg@tny|7tORh-K56H6MGlyxP(3}Ty=jh{X-|} zh1!a9ErYpFF2LM%Ww5^u_m+f3We^5lgwT%5y$HemA_VCr2xgq`B?wg{>>;58C%z0J z?-GQN%Mi@D9V867458r_2o_x66$r8`5KfR_#pz##P)I@|{3x-Ja2uD=>|BM?rW{2& zb7RY~0PVOE0y$@L4Peiu5IAsU0A&}=2)wlMSDy}JY|ht8pX=`%t7~yb(xzc-$ei8B+PWU!7V|D>ZTYnG z&FgIXRF#^`?2Fj5i)&heIZMpC+pKB3w&~4R^o)EGu|C7EohW#Wg~Pc6i*J57`m^q` zo`(*OZ+>9JzUR*dt=ii4Sn01Hw`%Xd?`DweKYU_bN{w+E=5^&x``?vBJm(7<`Ez;oHOQ(@7t%lJ!cr1`Q7{OTkHLC&*G!1ckSA>t7=#1>gwvm>qDy@IkqLe>LYU> zv)Tc7-e#V$$*H;a&k57_uK8MG^_&kET5juT{-@)G!fk^`8g;GsYEy7Y%eOp@S4=@U z;6-bwb%#ziudN#0s8~`t`@s4(%N7)xbGu0M5mOfDyf0F^;nNh~zM-x5dDONyd+odP zfE=}NGE zrKG*d{#3kP)6EatQXz`MRrcGC^0N39>D9n_@-k5SvxucKiYJ^`UVg+2kH!3_|$Frgu-o`Ud+ z3DHkMXiOVGz@{eCXKMHrX+OJd)a0-aZd>%D?6y)59P{(2T<%_&o&SI}@U);SmrXGhxjU5TfZb6Q(}`q0>lIAT)RkLXl%2#FFW85I!*>;W!9!w4DjdpMc7GOj4_!h zey>3phII$^@2G%Ppgd;E4NNVuMAGmElA8VoggKbDt4BZ#KiX^X7L+wNz%q(HGs`C? zbh-(`7+P@?MlqIDx8RJU_Uw$O4eU%H)7x++QUp7bXgfQTspuUzQz(|5skD!sX=HyF zP8RiKhv*nP)2ZY=I9SJEXC|G6qn-tG+W?iye}M*DFw;@bQImQTD3_TMy$O_gYTC$@ zkzYY6z8RDSFv88C_)MPbE)q6Ss0xQx z>^P{`_nn}gpCER*8Pd7<3F(}olAl3nR|te*pFuc7XPID97zCd$m=&GX?=I&ZSr!^H z)Z)#wx~5@C?c%NNQmeefstCfwCuQO<}cN#!+wY4;Is6XH+yCx@S=}RXL-b6~$4t)4F)#dThP!9hjC#jsk80NL)y$ZYnTNO7X!J64USGSizr9seoD$$PtH*@k zu!FOIm|18BowhGI`~4!%j=Qa&JTBgBeoC^_6?>W18u!)TP!1O<_&dtM&K%{i~@##tfTw`57%pEiOct+U5;H4|8J-Bsc;oAcn?_Dmnu*mR9 zlRjTt?K+wgEReF{4kHaxSkfR#{K-X4eGY4+hhu=>JCM) zbC+w0Ot|)Walv*W9JE#6xD4r_&hV^C;nD6;a-h7N3-l) z8ycz(O=woaJ*QXp6A!C^83wcetp0ZJn!(F&u4y#(ZNp376FQwYjF@p?*nq-a+Z_2( z^wyeD!>Z5KtCnZ_a+htg7_+O#^F^zj>t`m+wlFF+VOdVEFDq|ojJ^7`9v{K)wsQ-cnv-6U!z{%j# z*p#0*B7X5jhe=JEubVVH%=O cHx$+%LMuv+(Z0kEu!4?TALHyOZ0CaY-|NJXm^Q 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 delta 67194 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 diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index 3e62ac7..5e0b47e 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -63,7 +63,11 @@ export class Client implements MockClient { this.ws.close(); } - /** Both numbers are represented in `ms` */ + /** + * Both numbers are represented in `ms`. + * + * This function requires Bun to work. + */ public async ping(): Promise<{ ws: number, rest: number }> { const start = performance.now(); await this.rest.getGateway(); diff --git a/packages/core/src/compiler.ts b/packages/core/src/compiler.ts index 5c9604c..bb6cb27 100644 --- a/packages/core/src/compiler.ts +++ b/packages/core/src/compiler.ts @@ -135,7 +135,6 @@ export class ListenerCompiler> { this.#stack.set(event, temp.join("")); } - //! TODO: Support a once listener and always listener simultaneously #addReadyListener(once: boolean = false, listener?: (client: C, payload: Ready["d"]) => Awaitable): void { const onceListener = this.#callbacks.get("once_ready"); const readyArr = []; @@ -447,7 +446,7 @@ export class ListenerCompiler> { }, stack: string } { - const hasReadyListener = !(this.#stack.get("ready") === ""); + const hasReadyListener = this.#stack.get("ready") !== ""; if (!hasReadyListener) this.#addReadyListener(); const names = [...this.#callbacks.keys()]; diff --git a/packages/docs/astro.config.ts b/packages/docs/astro.config.ts deleted file mode 100644 index e5633c5..0000000 --- a/packages/docs/astro.config.ts +++ /dev/null @@ -1,154 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections"; -import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers"; -import { createStarlightTypeDocPlugin } from "starlight-typedoc"; -import { defineConfig } from "astro/config"; -import starlight from "@astrojs/starlight"; - -const [createCoreDocumentation, coreDocumentationSidebar] = createStarlightTypeDocPlugin(); -const [createTransformersDocumentation, transformersDocumentationSidebar] = createStarlightTypeDocPlugin(); - -export default defineConfig({ - integrations: [ - starlight({ - title: "Lilybird", - description: "Lightweight and performant Discord API Wrapper built by the community for the community.", - customCss: ["./src/styles/index.css"], - lastUpdated: true, - social: { - github: "https://github.com/Didas-git/lilybird", - discord: "https://discord.gg/vER3sh7uyY" - }, - editLink: { - baseUrl: "https://github.com/Didas-git/lilybird/edit/main/packages/docs" - }, - expressiveCode: { - plugins: [pluginLineNumbers(), pluginCollapsibleSections()], - defaultProps: { - showLineNumbers: false - } - }, - plugins: [ - createCoreDocumentation({ - entryPoints: ["../core/src/index.ts"], - output: "documentation", - tsconfig: "../core/tsconfig.json", - sidebar: { - label: "Documentation", - collapsed: true - }, - typeDoc: { - sort: ["enum-value-ascending", "source-order"], - parametersFormat: "table", - enumMembersFormat: "table", - publicPath: "/documentation/" - } - }), - createTransformersDocumentation({ - entryPoints: ["../transformers/src/index.ts"], - output: "modules/transformers/documentation", - tsconfig: "../transformers/tsconfig.json", - sidebar: { - label: "Documentation", - collapsed: true - }, - typeDoc: { - sort: ["enum-value-ascending", "source-order"], - excludeExternals: true, - parametersFormat: "table", - enumMembersFormat: "table", - publicPath: "/modules/transformers/documentation/" - } - }) - ], - head: [ - { - tag: "link", - attrs: { - type: "application/json+oembed", - href: "/oembed/main.json" - } - }, - { - tag: "meta", - attrs: { - name: "theme-color", - content: "#ad9ee7" - } - } - ], - sidebar: [ - { - label: "Philosophy", - link: "philosophy" - }, - { - label: "Community", - link: "community" - }, - { - label: "Guides", - autogenerate: { - directory: "/guides" - } - }, - { - label: "API", - autogenerate: { - directory: "/api", - collapsed: true - } - }, - { - label: "Modules", - collapsed: true, - items: [ - { - label: "JSX Components", - collapsed: true, - autogenerate: { - directory: "/modules/jsx" - } - }, - { - label: "Transformers", - collapsed: true, - items: [transformersDocumentationSidebar] - }, - { - label: "Handlers", - collapsed: true, - items: [ - { - label: "Simple", - collapsed: true, - badge: { - text: "Deprecated", - variant: "danger" - - }, - autogenerate: { - directory: "/modules/handlers/simple" - } - }, - { - label: "Advanced", - collapsed: true, - badge: { - text: "New", - variant: "tip" - - }, - autogenerate: { - directory: "/modules/handlers/default" - } - } - ] - } - ] - }, - coreDocumentationSidebar - ] - }) - ] -}); diff --git a/packages/docs/package.json b/packages/docs/package.json deleted file mode 100644 index 6c7559d..0000000 --- a/packages/docs/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "private": true, - "name": "@lilybird/docs", - "type": "module", - "version": "0.0.1", - "scripts": { - "dev": "astro dev", - "pre:build": "rm -rf src/assets/diagrams && bash compile.sh", - "build": "astro check && astro build", - "preview": "astro preview", - "check": "eslint \"src/**/*.ts\"" - }, - "devDependencies": { - "@expressive-code/plugin-line-numbers": "^0.36.1", - "@expressive-code/plugin-collapsible-sections": "^0.36.1", - "@astrojs/check": "^0.9.3", - "@astrojs/starlight": "^0.26.3", - "starlight-typedoc": "^0.16.0", - "typedoc": "^0.26.6", - "typedoc-plugin-markdown": "^4.2.6", - "astro": "^4.15.3", - "sharp": "^0.33.5" - } -} \ No newline at end of file diff --git a/packages/docs/src/assets/diagrams/compiler.svg b/packages/docs/src/assets/diagrams/compiler.svg deleted file mode 100644 index 2c816f5..0000000 --- a/packages/docs/src/assets/diagrams/compiler.svg +++ /dev/null @@ -1,988 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -YOUUSEMAY USEMODULESCLIENTWS MANAGER

● DEFINITIONS

-
    -
  • RAW - DEFINE THE RAW LISTENER IF IT EXISTS, RAW IS ALWAYS FIRST LEVEL WITH 0 CONDITIONALS.
  • -
  • READY - DEFINE THE READY LISTENER, THIS IS DONE MANUALLY FOR A FEW REASONS. -
      -
    • THE SETUP API NEEDS TO ALSO BE ADDED IF IT EXISTS.
    • -
    • THE INNER USER OBJECT OF THE CLIENT MIGHT HAVE A TRANSFORMER ASSIGNED.
    • -
    -
  • -
  • USER DEFINED - THIS HANDLES CREATING THE LISTENERS YOU PASSED IN ON THE API. -
      -
    • THIS ALWAYS ADDS THE TRANSFORMER FOR THE EVENT IF IT EXISTS.
    • -
    -
  • -
  • CACHE - DEFINE THE CACHING LISTENERS.
  • -
  • DCE - DEAD CODE ELIMINATION HAPPENS FOR EACH LISTENER TO GET RID OF UNUSED TRANSFORMERS.
  • -
-
APICOMPILERLISTENERSRAWREADYUSER DEFINEDCACHEDCETRANSFORMERSLISTENERSCACHE - - - - - - - - - - - - - - - - - - - -
diff --git a/packages/docs/src/content/docs/api/setup.md b/packages/docs/src/content/docs/api/setup.md deleted file mode 100644 index 9e9cb63..0000000 --- a/packages/docs/src/content/docs/api/setup.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Setup -description: Lets talk about the setup api. -sidebar: - order: 1 ---- - -The setup api in lilybird is what allows you to do actions to setup external resources like databases or even publish your commands. - -This api exists purely because unlike other frameworks lilybird does not ignore ready events sent after a failed resume attempt or reconnection, and since we don't have real events (we don't use `EventEmitter` or `EventTarget`), we added this api so you can execute certain actions only once at startup. - -To learn how you can make use of it we have an example in [one of the guides](/guides/handling-commands). diff --git a/packages/docs/src/content/docs/guides/manual-setup.mdx b/packages/docs/src/content/docs/guides/manual-setup.mdx deleted file mode 100644 index 1772f34..0000000 --- a/packages/docs/src/content/docs/guides/manual-setup.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Manual Setup -description: Configure Lilybird however you would like. -sidebar: - order: 1 ---- - -import { Tabs, TabItem } from "@astrojs/starlight/components"; - -## Creating the bot - -Creating the bot with Lilybird is a trivial task. - -```ts title="index.ts" -import { createClient, Intents } from "lilybird"; - -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], - listeners: {/* your listeners */} -}) -``` - -## Adding types to `process.env` - -In case you are using TypeScript we highly encourage adding proper types to your process.env. - -This can be done by creating a `globals.d.ts` file on the root of your project. - - - - ```ts title="globals.d.ts" - declare module "bun" { - interface Env { - TOKEN: string; - // Other env variables - } - } - ``` - - - ```ts title="globals.d.ts" - declare global { - namespace NodeJS { - interface ProcessEnv { - TOKEN: string; - // Other env variables - } - } - } - ``` - - - -And then adding the following to your `tsconfig.json`. - -:::caution -Setting `types` on the compiler options will make it so TypeScript won't search for `@types` packages. - -You can read more about it on the [TypeScript handbook](https://www.typescriptlang.org/tsconfig#types) -::: - -```json title="tsconfig.json" -{ - "compilerOptions": { - "types": [ - "./globals" - ] - } -} -``` \ No newline at end of file diff --git a/packages/guides/astro.config.ts b/packages/guides/astro.config.ts new file mode 100644 index 0000000..998275a --- /dev/null +++ b/packages/guides/astro.config.ts @@ -0,0 +1,71 @@ +import { pluginCollapsibleSections } from "@expressive-code/plugin-collapsible-sections"; +import { pluginLineNumbers } from "@expressive-code/plugin-line-numbers"; +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; + +export default defineConfig({ + integrations: [ + starlight({ + title: "Lilybird", + description: "Lightweight and performant Discord API Wrapper built by the community for the community.", + customCss: ["./src/styles/index.css"], + lastUpdated: true, + social: { + github: "https://github.com/Didas-git/lilybird", + discord: "https://discord.gg/vER3sh7uyY" + }, + editLink: { + baseUrl: "https://github.com/Didas-git/lilybird/edit/main/packages/guides" + }, + expressiveCode: { + plugins: [pluginLineNumbers(), pluginCollapsibleSections()], + defaultProps: { + showLineNumbers: false + } + }, + head: [ + { + tag: "link", + attrs: { + type: "application/json+oembed", + href: "/oembed/main.json" + } + }, + { + tag: "meta", + attrs: { + name: "theme-color", + content: "#ad9ee7" + } + } + ], + sidebar: [ + { + label: "Philosophy", + link: "philosophy" + }, + { + label: "Community", + link: "community" + }, + { + label: "Guides", + autogenerate: { + directory: "/guides" + } + }, + { + label: "API", + autogenerate: { + directory: "/api", + collapsed: true + } + }, + { + label: "Documentation", + link: "https://docs.lilybird.dev" + } + ] + }) + ] +}); diff --git a/packages/docs/bunfig.toml b/packages/guides/bunfig.toml similarity index 100% rename from packages/docs/bunfig.toml rename to packages/guides/bunfig.toml diff --git a/packages/docs/compile.sh b/packages/guides/compile.sh similarity index 100% rename from packages/docs/compile.sh rename to packages/guides/compile.sh diff --git a/packages/docs/diagrams/api-flow.d2 b/packages/guides/diagrams/api-flow.d2 similarity index 100% rename from packages/docs/diagrams/api-flow.d2 rename to packages/guides/diagrams/api-flow.d2 diff --git a/packages/docs/diagrams/compiler.d2 b/packages/guides/diagrams/compiler.d2 similarity index 100% rename from packages/docs/diagrams/compiler.d2 rename to packages/guides/diagrams/compiler.d2 diff --git a/packages/docs/diagrams/shared.d2 b/packages/guides/diagrams/shared.d2 similarity index 100% rename from packages/docs/diagrams/shared.d2 rename to packages/guides/diagrams/shared.d2 diff --git a/packages/docs/diagrams/websocket-flow.d2 b/packages/guides/diagrams/websocket-flow.d2 similarity index 100% rename from packages/docs/diagrams/websocket-flow.d2 rename to packages/guides/diagrams/websocket-flow.d2 diff --git a/packages/docs/eslint.config.js b/packages/guides/eslint.config.js similarity index 100% rename from packages/docs/eslint.config.js rename to packages/guides/eslint.config.js diff --git a/packages/guides/package.json b/packages/guides/package.json new file mode 100644 index 0000000..0b4001b --- /dev/null +++ b/packages/guides/package.json @@ -0,0 +1,21 @@ +{ + "private": true, + "name": "@lilybird/guides", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "pre:build": "rm -rf src/assets/diagrams && bash compile.sh", + "build": "astro check && astro build", + "preview": "astro preview", + "check": "eslint \"src/**/*.ts\"" + }, + "devDependencies": { + "@astrojs/check": "^0.9.4", + "@astrojs/starlight": "^0.29.2", + "@expressive-code/plugin-collapsible-sections": "^0.38.3", + "@expressive-code/plugin-line-numbers": "^0.38.3", + "astro": "^4.16.13", + "sharp": "^0.33.5" + } +} \ No newline at end of file diff --git a/packages/docs/public/favicon.svg b/packages/guides/public/favicon.svg similarity index 100% rename from packages/docs/public/favicon.svg rename to packages/guides/public/favicon.svg diff --git a/packages/docs/public/oembed/main.json b/packages/guides/public/oembed/main.json similarity index 100% rename from packages/docs/public/oembed/main.json rename to packages/guides/public/oembed/main.json diff --git a/packages/docs/src/assets/diagrams/api-flow.svg b/packages/guides/src/assets/diagrams/api-flow.svg similarity index 100% rename from packages/docs/src/assets/diagrams/api-flow.svg rename to packages/guides/src/assets/diagrams/api-flow.svg diff --git a/packages/docs/src/assets/diagrams/websocket-flow.svg b/packages/guides/src/assets/diagrams/websocket-flow.svg similarity index 100% rename from packages/docs/src/assets/diagrams/websocket-flow.svg rename to packages/guides/src/assets/diagrams/websocket-flow.svg diff --git a/packages/docs/src/assets/houston.webp b/packages/guides/src/assets/houston.webp similarity index 100% rename from packages/docs/src/assets/houston.webp rename to packages/guides/src/assets/houston.webp diff --git a/packages/docs/src/content/config.ts b/packages/guides/src/content/config.ts similarity index 100% rename from packages/docs/src/content/config.ts rename to packages/guides/src/content/config.ts diff --git a/packages/docs/src/content/docs/api/cache.md b/packages/guides/src/content/docs/api/cache.md similarity index 100% rename from packages/docs/src/content/docs/api/cache.md rename to packages/guides/src/content/docs/api/cache.md diff --git a/packages/docs/src/content/docs/api/compiler.mdx b/packages/guides/src/content/docs/api/compiler.mdx similarity index 87% rename from packages/docs/src/content/docs/api/compiler.mdx rename to packages/guides/src/content/docs/api/compiler.mdx index 4626cbd..ff4fc90 100644 --- a/packages/docs/src/content/docs/api/compiler.mdx +++ b/packages/guides/src/content/docs/api/compiler.mdx @@ -2,21 +2,18 @@ title: Listeners Compiler description: Lets try to break down how the compiler works. sidebar: - order: 4 - badge: - text: Internal - variant: note + order: 2 --- -import overviewDiagram from "../../../assets/diagrams/compiler.svg" import compilerDiagram from "../../../assets/diagrams/api-flow.svg" import { Image } from 'astro:assets'; + The compiler in lilybird is what makes our api possible, it builds all the listeners **once** at startup so you only get both, what you defined and with proper transformers (if present) without any runtime checks after the bot starts. The lack of this extra checks on every event is what makes lilybird so fast, the workflow is just
`event -> listener` with the only thing possibly in the middle being the transformers you define or in some cases the cache code. -overview +## Internal Workflow The great advantage of the compiler is that your listeners run smoothly without having to do any checks because it does all the checks when its compiling them. diff --git a/packages/docs/src/content/docs/api/internal-workflow.mdx b/packages/guides/src/content/docs/api/internal-workflow.mdx similarity index 74% rename from packages/docs/src/content/docs/api/internal-workflow.mdx rename to packages/guides/src/content/docs/api/internal-workflow.mdx index 6058837..499a4b2 100644 --- a/packages/docs/src/content/docs/api/internal-workflow.mdx +++ b/packages/guides/src/content/docs/api/internal-workflow.mdx @@ -10,6 +10,12 @@ sidebar: import workflowDiagram from "../../../assets/diagrams/websocket-flow.svg" import { Image } from 'astro:assets'; +:::note +As of lilybird 0.9 the compilation step has been made completely optional and the bellow diagram will suffer some small changes in the future. + +Im currently trying to figure out how to get the free TALA license for open source projects. +::: + The diagram bellow illustrates all the steps taken by lilybird when your bot starts. internal flow diff --git a/packages/docs/src/content/docs/api/no-events.md b/packages/guides/src/content/docs/api/no-events.md similarity index 100% rename from packages/docs/src/content/docs/api/no-events.md rename to packages/guides/src/content/docs/api/no-events.md diff --git a/packages/docs/src/content/docs/api/transformers.md b/packages/guides/src/content/docs/api/transformers.md similarity index 100% rename from packages/docs/src/content/docs/api/transformers.md rename to packages/guides/src/content/docs/api/transformers.md diff --git a/packages/docs/src/content/docs/community.md b/packages/guides/src/content/docs/community.md similarity index 100% rename from packages/docs/src/content/docs/community.md rename to packages/guides/src/content/docs/community.md diff --git a/packages/docs/src/content/docs/guides/coloring.md b/packages/guides/src/content/docs/guides/coloring.md similarity index 100% rename from packages/docs/src/content/docs/guides/coloring.md rename to packages/guides/src/content/docs/guides/coloring.md diff --git a/packages/docs/src/content/docs/guides/embeds-attachments.md b/packages/guides/src/content/docs/guides/embeds-attachments.md similarity index 100% rename from packages/docs/src/content/docs/guides/embeds-attachments.md rename to packages/guides/src/content/docs/guides/embeds-attachments.md diff --git a/packages/docs/src/content/docs/guides/getting-started.mdx b/packages/guides/src/content/docs/guides/getting-started.mdx similarity index 100% rename from packages/docs/src/content/docs/guides/getting-started.mdx rename to packages/guides/src/content/docs/guides/getting-started.mdx diff --git a/packages/docs/src/content/docs/guides/handling-commands.md b/packages/guides/src/content/docs/guides/handling-commands.md similarity index 100% rename from packages/docs/src/content/docs/guides/handling-commands.md rename to packages/guides/src/content/docs/guides/handling-commands.md diff --git a/packages/docs/src/content/docs/guides/receiving-messages.md b/packages/guides/src/content/docs/guides/receiving-messages.md similarity index 100% rename from packages/docs/src/content/docs/guides/receiving-messages.md rename to packages/guides/src/content/docs/guides/receiving-messages.md diff --git a/packages/docs/src/content/docs/index.mdx b/packages/guides/src/content/docs/index.mdx similarity index 97% rename from packages/docs/src/content/docs/index.mdx rename to packages/guides/src/content/docs/index.mdx index 9c9f5f6..0054363 100644 --- a/packages/docs/src/content/docs/index.mdx +++ b/packages/guides/src/content/docs/index.mdx @@ -19,7 +19,7 @@ hero: icon: external variant: secondary - text: "" - link: /guides/getting-started/ + link: https://docs.lilybird.dev icon: open-book variant: minimal --- diff --git a/packages/docs/src/content/docs/modules/handlers/default/application-commands.md b/packages/guides/src/content/docs/modules/handlers/default/application-commands.md similarity index 100% rename from packages/docs/src/content/docs/modules/handlers/default/application-commands.md rename to packages/guides/src/content/docs/modules/handlers/default/application-commands.md diff --git a/packages/docs/src/content/docs/modules/handlers/default/events.md b/packages/guides/src/content/docs/modules/handlers/default/events.md similarity index 100% rename from packages/docs/src/content/docs/modules/handlers/default/events.md rename to packages/guides/src/content/docs/modules/handlers/default/events.md diff --git a/packages/docs/src/content/docs/modules/handlers/simple/application-commands.md b/packages/guides/src/content/docs/modules/handlers/simple/application-commands.md similarity index 100% rename from packages/docs/src/content/docs/modules/handlers/simple/application-commands.md rename to packages/guides/src/content/docs/modules/handlers/simple/application-commands.md diff --git a/packages/docs/src/content/docs/modules/handlers/simple/events.md b/packages/guides/src/content/docs/modules/handlers/simple/events.md similarity index 100% rename from packages/docs/src/content/docs/modules/handlers/simple/events.md rename to packages/guides/src/content/docs/modules/handlers/simple/events.md diff --git a/packages/docs/src/content/docs/modules/handlers/simple/message-commands.md b/packages/guides/src/content/docs/modules/handlers/simple/message-commands.md similarity index 100% rename from packages/docs/src/content/docs/modules/handlers/simple/message-commands.md rename to packages/guides/src/content/docs/modules/handlers/simple/message-commands.md diff --git a/packages/docs/src/content/docs/modules/jsx/attachment.mdx b/packages/guides/src/content/docs/modules/jsx/attachment.mdx similarity index 100% rename from packages/docs/src/content/docs/modules/jsx/attachment.mdx rename to packages/guides/src/content/docs/modules/jsx/attachment.mdx diff --git a/packages/docs/src/content/docs/modules/jsx/command.md b/packages/guides/src/content/docs/modules/jsx/command.md similarity index 92% rename from packages/docs/src/content/docs/modules/jsx/command.md rename to packages/guides/src/content/docs/modules/jsx/command.md index 71a9008..b382648 100644 --- a/packages/docs/src/content/docs/modules/jsx/command.md +++ b/packages/guides/src/content/docs/modules/jsx/command.md @@ -13,7 +13,7 @@ Creating slash commands with Lilybird's JSX components is a trivial task. You ca import { ApplicationCommand, StringOption, UserOption } from "@lilybird/jsx"; const command = ( - + diff --git a/packages/docs/src/content/docs/modules/jsx/components.md b/packages/guides/src/content/docs/modules/jsx/components.md similarity index 100% rename from packages/docs/src/content/docs/modules/jsx/components.md rename to packages/guides/src/content/docs/modules/jsx/components.md diff --git a/packages/docs/src/content/docs/modules/jsx/configuring-jsx.mdx b/packages/guides/src/content/docs/modules/jsx/configuring-jsx.mdx similarity index 100% rename from packages/docs/src/content/docs/modules/jsx/configuring-jsx.mdx rename to packages/guides/src/content/docs/modules/jsx/configuring-jsx.mdx diff --git a/packages/docs/src/content/docs/modules/jsx/embed.md b/packages/guides/src/content/docs/modules/jsx/embed.md similarity index 100% rename from packages/docs/src/content/docs/modules/jsx/embed.md rename to packages/guides/src/content/docs/modules/jsx/embed.md diff --git a/packages/docs/src/content/docs/philosophy.md b/packages/guides/src/content/docs/philosophy.md similarity index 100% rename from packages/docs/src/content/docs/philosophy.md rename to packages/guides/src/content/docs/philosophy.md diff --git a/packages/docs/src/env.d.ts b/packages/guides/src/env.d.ts similarity index 100% rename from packages/docs/src/env.d.ts rename to packages/guides/src/env.d.ts diff --git a/packages/docs/src/styles/index.css b/packages/guides/src/styles/index.css similarity index 100% rename from packages/docs/src/styles/index.css rename to packages/guides/src/styles/index.css diff --git a/packages/docs/tsconfig.json b/packages/guides/tsconfig.json similarity index 100% rename from packages/docs/tsconfig.json rename to packages/guides/tsconfig.json From 19245c9eca0dab6176ca6d5304147a3af42e1810 Mon Sep 17 00:00:00 2001 From: DidaS Date: Sat, 30 Nov 2024 07:54:03 +0000 Subject: [PATCH 08/25] new guide --- .gitignore | 2 +- packages/guides/astro.config.ts | 2 +- .../src/content/docs/guides/coloring.md | 2 +- .../content/docs/guides/create-first-bot.mdx | 96 ------------------- .../content/docs/guides/embeds-attachments.md | 18 ++-- .../content/docs/guides/getting-started.mdx | 82 ++++++++++------ .../content/docs/guides/handling-commands.md | 6 +- .../src/content/docs/guides/manual-setup.mdx | 87 +++++++++++++++++ .../content/docs/guides/receiving-messages.md | 2 +- packages/guides/src/content/docs/index.mdx | 22 ++--- .../handlers/default/application-commands.md | 76 --------------- .../docs/modules/handlers/default/events.md | 37 ------- .../handlers/simple/application-commands.md | 55 ----------- .../docs/modules/handlers/simple/events.md | 41 -------- .../handlers/simple/message-commands.md | 59 ------------ .../content/docs/modules/jsx/attachment.mdx | 63 ------------ .../src/content/docs/modules/jsx/command.md | 25 ----- .../content/docs/modules/jsx/components.md | 69 ------------- .../docs/modules/jsx/configuring-jsx.mdx | 39 -------- .../src/content/docs/modules/jsx/embed.md | 19 ---- packages/test/src/index.ts | 57 +++++++---- 21 files changed, 205 insertions(+), 654 deletions(-) delete mode 100644 packages/guides/src/content/docs/guides/create-first-bot.mdx create mode 100644 packages/guides/src/content/docs/guides/manual-setup.mdx delete mode 100644 packages/guides/src/content/docs/modules/handlers/default/application-commands.md delete mode 100644 packages/guides/src/content/docs/modules/handlers/default/events.md delete mode 100644 packages/guides/src/content/docs/modules/handlers/simple/application-commands.md delete mode 100644 packages/guides/src/content/docs/modules/handlers/simple/events.md delete mode 100644 packages/guides/src/content/docs/modules/handlers/simple/message-commands.md delete mode 100644 packages/guides/src/content/docs/modules/jsx/attachment.mdx delete mode 100644 packages/guides/src/content/docs/modules/jsx/command.md delete mode 100644 packages/guides/src/content/docs/modules/jsx/components.md delete mode 100644 packages/guides/src/content/docs/modules/jsx/configuring-jsx.mdx delete mode 100644 packages/guides/src/content/docs/modules/jsx/embed.md diff --git a/.gitignore b/.gitignore index bd4d17a..c084cb3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ coverage/ .dccache packages/guides/diagrams/*.svg -packages/guides/src/content/docs/**/documentation/ +packages/guides/src/content/docs/temp/ packages/test/**/lily-cache/ \ No newline at end of file diff --git a/packages/guides/astro.config.ts b/packages/guides/astro.config.ts index 998275a..f19825b 100644 --- a/packages/guides/astro.config.ts +++ b/packages/guides/astro.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ integrations: [ starlight({ title: "Lilybird", - description: "Lightweight and performant Discord API Wrapper built by the community for the community.", + description: "The most performant and lightweight Discord API Wrapper for JavaScript built by developers for developers.", customCss: ["./src/styles/index.css"], lastUpdated: true, social: { diff --git a/packages/guides/src/content/docs/guides/coloring.md b/packages/guides/src/content/docs/guides/coloring.md index 787ed9f..2b71153 100644 --- a/packages/guides/src/content/docs/guides/coloring.md +++ b/packages/guides/src/content/docs/guides/coloring.md @@ -47,7 +47,7 @@ import { await createClient({ token: process.env.TOKEN, - intents: [Intents.GUILDS], + intents: Intents.GUILDS, listeners: { messageCreate: async (client, message) => { await client.rest.createMessage(message.channel_id, { diff --git a/packages/guides/src/content/docs/guides/create-first-bot.mdx b/packages/guides/src/content/docs/guides/create-first-bot.mdx deleted file mode 100644 index 6a33a3f..0000000 --- a/packages/guides/src/content/docs/guides/create-first-bot.mdx +++ /dev/null @@ -1,96 +0,0 @@ ---- -title: Creating your first bot -description: Configure Lilybird however you would like. -sidebar: - order: 1 ---- - -import { Tabs, TabItem } from "@astrojs/starlight/components"; - -In this guide you will learn how to create a simple ticket bot using lilybird **without** any modules. - -## Setup - -To setup your bot follow the instructions in the [getting started page](../getting-started/), this will be the foundation throughout this guide. - -:::tip -It is highly recommended that you use discord's web version to test your bot, the discord app (both mobile and desktop) cache commands and when you edit/add commands you are pretty much required to restart the apps to see the changes, using the web version you can simply hit `F5` to see your changes. -::: - -## Handling your first interaction - -Interactions are hte most fundamental building blocks in modern discord bots as they allow us to use buttons, modals and other features. - -Lets start by creating our first command, we will call it `ping`. - -```ts title="index.ts" showLineNumbers {"1. Make the function async":7-8} ins={9-13} collapse={4-6} -import { createClient, Intents } from "lilybird"; - -await createClient({ - token: process.env.TOKEN, - intents: Intents.GUILDS, - listeners: { - - setup: async (client, payload) => { - await client.rest.bulkOverwriteGlobalApplicationCommand(client.user.id, [ - { - name: "ping" - } - ]); - - console.log(`Logged in as ${payload.user.username}`); - } - } -}); - -``` - -Lets create a simple listener that will listen for an interaction called `ping` and reply with some message. - -```ts title="index.ts" showLineNumbers del={1} ins={2-5} collapse={11-22} ins={"1. Define the listener":23-24} ins={"2. Narrow down the interaction type so only interactions in guilds will be handled": 25-26} ins={"3. Only chat input interactions should be handled in this case":27-31} ins={"4. Handle the ping command":32-43} -import { createClient, Intents } from "lilybird"; -import { - InteractionCallbackType, - ApplicationCommandType, - InteractionType, - createClient, - Intents -} from "lilybird"; - -await createClient({ - token: process.env.TOKEN, - intents: Intents.GUILDS, - listeners: { - setup: async (client, payload) => { - await client.rest.bulkOverwriteGlobalApplicationCommand(client.user.id, [ - { - name: "ping" - } - ]); - - console.log(`Logged in as ${payload.user.username}`); - } - - interactionCreate: async (client, payload) => { - - if (!("guild_id" in payload)) return; - - if ( - payload.type === InteractionType.APPLICATION_COMMAND - && payload.data.type === ApplicationCommandType.CHAT_INPUT - ) { - - if (payload.data.name === "ping") { - await client.rest.createInteractionResponse(payload.id, payload.token, { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: "Pong!" - } - }); - return; - } - } - } - } -}); -``` \ No newline at end of file diff --git a/packages/guides/src/content/docs/guides/embeds-attachments.md b/packages/guides/src/content/docs/guides/embeds-attachments.md index 7bb5695..5269e3a 100644 --- a/packages/guides/src/content/docs/guides/embeds-attachments.md +++ b/packages/guides/src/content/docs/guides/embeds-attachments.md @@ -21,10 +21,10 @@ import { await createClient({ token: process.env.TOKEN, - intents: [Intents.GUILDS], - // We pass the setup function we created above - setup, + intents: Intents.GUILDS, listeners: { + // We pass the setup function we created above + setup, interactionCreate: async (client, payload) => { // We only want to handle guild interactions if (!("guild_id" in payload)) return; @@ -63,10 +63,10 @@ import { await createClient({ token: process.env.TOKEN, - intents: [Intents.GUILDS], - // We pass the setup function we created above - setup, + intents: Intents.GUILDS, listeners: { + // We pass the setup function we created above + setup, interactionCreate: async (client, payload) => { // We only want to handle guild interactions if (!("guild_id" in payload)) return; @@ -98,10 +98,10 @@ import { await createClient({ token: process.env.TOKEN, - intents: [Intents.GUILDS], - // We pass the setup function we created above - setup, + intents: Intents.GUILDS, listeners: { + // We pass the setup function we created above + setup, interactionCreate: async (client, payload) => { // We only want to handle guild interactions if (!("guild_id" in payload)) return; diff --git a/packages/guides/src/content/docs/guides/getting-started.mdx b/packages/guides/src/content/docs/guides/getting-started.mdx index ac0b5da..ec8c2a6 100644 --- a/packages/guides/src/content/docs/guides/getting-started.mdx +++ b/packages/guides/src/content/docs/guides/getting-started.mdx @@ -1,21 +1,23 @@ --- title: Getting Started -description: Learn how to setup Lilybird. +description: Get started fast with Lilybird. sidebar: order: 0 --- import { Tabs, TabItem, FileTree } from "@astrojs/starlight/components"; -:::danger[Important] -Lilybird does not officially support **deno**. While the core should work, there's no guarantee that the modules or, for that matter the examples will. -::: +## Before you start + +Lilybird is a thing wrapper on top of the Discord API, if you are not using transformers the best documentation you can read is the official one that can be found [here](https://discord.com/developers/docs/). + +All the `interfaces`, `types` and `REST` methods in lilybird follow the naming scheme found in the official documentation, if you ever find something that doesn't match feel free to open a pull request. ## Installation The fastest way to get started with Lilybird is by using our template generator. - + ```bash frame="none" bun create @lilybird @@ -42,10 +44,6 @@ The fastest way to get started with Lilybird is by using our template generator. After running the command you should see a file structure somewhat like this: -:::note -Depending on the selected language for the template the structure may differ: The JavaScript template will not contain type or config files (`globals.d.ts` and `tsconfig.json`) -::: - - globals.d.ts @@ -57,38 +55,68 @@ Depending on the selected language for the template the structure may differ: Th -### Adding your token +Don't forget to add your token as mentioned above. -To add your bot token open your `.env` file and add the token after `TOKEN=`. +:::note +Depending on the selected language for the template the structure may differ: The JavaScript template will not contain type or config files (`globals.d.ts` and `tsconfig.json`) +::: ### Starting your bot -:::tip -If you are using Bun you can run `bun start` no matter what language you are using. -::: +To start your first bot all you need to do is run the `dev` script found in your `package.json`. - - + + + ```bash frame="none" + bun dev + ``` + + ```bash frame="none" npm run dev ``` - + ```bash frame="none" - npm run start + pnpm run dev + ``` + + + ```bash frame="none" + yarn run dev ``` ### Ready for production -If you are using TypeScript we highly recommend adding `NODE_ENV="production"` to your `.env` file and if you are using Node, do the following. +Running your bot is rather simple, however we have a few recommendations that can help improve performance in some cases: +- If you are using Bun or Node, add `NODE_ENV="production"` to your `.env` file. +- If you are using Bun, change or add a script to run production using `bun --smol`. -:::note -If you are using **Bun** we highly encourage changing or adding a script to run production using `bun --smol`. -::: - -```bash frame="none" -npm run build -npm run start -``` + + + ```bash frame="none" + bun run build + bun start + ``` + + + ```bash frame="none" + npm run build + npm run start + ``` + + + ```bash frame="none" + pnpm run build + pnpm run start + ``` + + + ```bash frame="none" + yarn run build + yarn run start + ``` + + diff --git a/packages/guides/src/content/docs/guides/handling-commands.md b/packages/guides/src/content/docs/guides/handling-commands.md index dcb5a53..0b64ea7 100644 --- a/packages/guides/src/content/docs/guides/handling-commands.md +++ b/packages/guides/src/content/docs/guides/handling-commands.md @@ -103,10 +103,10 @@ async function handleCommand( await createClient({ token: process.env.TOKEN, - intents: [Intents.GUILDS], - // We pass the setup function we created above - setup, + intents: Intents.GUILDS, listeners: { + // We pass the setup function we created above + setup, interactionCreate: async (client, payload) => { // We only want to handle guild interactions if (!("guild_id" in payload)) return; diff --git a/packages/guides/src/content/docs/guides/manual-setup.mdx b/packages/guides/src/content/docs/guides/manual-setup.mdx new file mode 100644 index 0000000..bc805d0 --- /dev/null +++ b/packages/guides/src/content/docs/guides/manual-setup.mdx @@ -0,0 +1,87 @@ +--- +title: Manual Setup +description: Learn how to setup Lilybird all by yourself. +sidebar: + order: 1 +--- + +import { Tabs, TabItem, FileTree } from "@astrojs/starlight/components"; + +## Initial setup + +To start with lilybird you firstly need to create a new project, in this guide we will be using the template provided by `@lilybird/create` but you can always use another template generator or make it from scratch. + +### Installing lilybird + +Lilybird is available on the npm registry, therefor it can be downloaded with the package manager of your choice. + + + + ```bash frame="none" + bun add lilybird + ``` + + + ```bash frame="none" + npm i lilybird + ``` + + + ```bash frame="none" + pnpm i lilybird + ``` + + + ```bash frame="none" + yarn add lilybird + ``` + + + +## Fast Setup + +The `createClient` function is a helper provided by lilybird that allows you to spin up a client fast without having to worry about managing the compiler yourself. +If all you want is to spin up a bot and do your stuff this is the way to go, however, if you want full control over your bot skip this section. + +Using this helper function will still allow you to use transformers, handlers and other things but it requires every listener to be its own function. + +### Making your first bot + +Making your first bot is extremely simple, however it lacks functionality. All the bot functionality is up to you to implement using the tools provided, we have other guides that can help you with specific problems but we highly recommend that you read the official Discord documentation. + +```ts showLineNumbers {"The setup listener is a ready listener that is only ever called once":7-8} +import { createClient, Intents } from "lilybird"; + +await createClient({ + token: process.env.TOKEN, + intents: Intents.GUILDS, + listeners: { + + setup: (client) => { + console.log(`Logged in as: ${client.user.username} (${client.user.id})`) + } + } +}) +``` + +## In-depth setup + +:::danger[Be aware] +This is a more advanced guide and more intended for people who are writing frameworks for lilybird, if you are having trouble understanding it feel free to join our discord and ask questions. +::: + +### The "client" + +In lilybird there is no strong concept of what the client is, the provided `Client` class is just a convenient wrapper that gets passed around each listener that includes an instance of the `REST` helpers and the active `ws` connection, however this is not the strict definition of a `Client`. + +#### Definition + +The `Client` in lilybird is defined as an object that includes the fields returned by the `READY` event. + +The current interface all client representations should respect is called `MockClient` (name pending change). + +### The AOT Compiler + +The AOT Compiler in lilybird is one of the things that allows lilybird to provide apis like the transformers without having any impact on performance. Its job its to compile down your listeners and transformers into a simple linear dispatch function that has no useless/redundant checks. + +While this is an important piece, the AOT Compiler is **not** required for lilybird to work, you can always make your own dispatch functions manually. \ No newline at end of file diff --git a/packages/guides/src/content/docs/guides/receiving-messages.md b/packages/guides/src/content/docs/guides/receiving-messages.md index 9d91aec..25001b4 100644 --- a/packages/guides/src/content/docs/guides/receiving-messages.md +++ b/packages/guides/src/content/docs/guides/receiving-messages.md @@ -16,7 +16,7 @@ import { await createClient({ token: process.env.TOKEN, - intents: [Intents.GUILDS], + intents: Intents.GUILDS, listeners: { messageCreate: async (client, message) => { await client.rest.createMessage(message.channel_id, { diff --git a/packages/guides/src/content/docs/index.mdx b/packages/guides/src/content/docs/index.mdx index 0054363..eb83313 100644 --- a/packages/guides/src/content/docs/index.mdx +++ b/packages/guides/src/content/docs/index.mdx @@ -1,12 +1,8 @@ --- title: Lilybird template: splash -banner: - content: | - As of lilybird 0.4 the core has been converted to use - ESM! hero: - tagline: Built with modularity in mind, the only Discord API Wrapper you will ever need. + tagline: The most performant and lightweight Discord API Wrapper for JavaScript built by developers for developers. image: file: ../../assets/houston.webp actions: @@ -27,16 +23,16 @@ hero: import { Card, CardGrid } from "@astrojs/starlight/components"; - - With no dependencies and built with performance and DX in mind. + + Lilybird aims to provide full control to the developer while making it simple to modify anything you want. - - With lots of modules and an extensive public api. + + Lilybird provides an extensive API that allows you to modify every step. - - As a new framework we focus on new standards like ES modules. + + With no dependencies lilybird weighs less than 500kb uncompressed. - - With bun in mind we do not guarantee node compatibility **for our modules**. + + Lilybird has been crafted with performance and the developer in mind. diff --git a/packages/guides/src/content/docs/modules/handlers/default/application-commands.md b/packages/guides/src/content/docs/modules/handlers/default/application-commands.md deleted file mode 100644 index ccd4589..0000000 --- a/packages/guides/src/content/docs/modules/handlers/default/application-commands.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Handling Application Commands -description: How to use Lilybird's handlers for application commands. -sidebar: - order: 0 ---- - -## Creating a simple command - -Lets create a simple `ping` command to demonstrate how it works. - -```diff lang="ts" title="index.ts" -import { createClient, Intents } from "lilybird"; -+import { handler } from "@lilybird/handlers/advanced"; - -+handler.cachePath = `${import.meta.dir}/lily-cache/handler`; -+await handler.scanDir(`${import.meta.dir}/commands`); - -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], -- listeners: {/* your listeners */} -+ setup: async (client) => { -+ await handler.loadGlobalCommands(client); -+ }, -+ listeners: handler.getListenersObject() -}) -``` - -```ts title="commands/ping.ts" showLineNumbers -import { $applicationCommand } from "@lilybird/handlers/advanced"; - -$applicationCommand({ - name: "ping", - description: "pong", - handle: async (client, interaction) => { - const { ws, rest } = await client.ping(); - await client.rest.createInteractionResponse(interaction.id, interaction.token, { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\`` - } - }); - }, -}); -``` - -## Handling Sub Commands - -Handling sub commands with lilybird's handler is extremely simple, you write it just like you would for a normal command. - -```js showLineNumbers -import { $applicationCommand } from "@lilybird/handlers/advanced"; -import { ApplicationCommandOptionType } from "lilybird"; - -$applicationCommand({ - name: "wrapper", - description: "A wrapper group for basic commands", - options: [ - { - type: ApplicationCommandOptionType.SUB_COMMAND, - name: "ping", - description: "pong", - handle: async (client, interaction) => { - const { ws, rest } = await client.ping(); - await client.rest.createInteractionResponse(interaction.id, interaction.token, { - type: InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE, - data: { - content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\`` - } - }); - }, - } - ] -}); -``` \ No newline at end of file diff --git a/packages/guides/src/content/docs/modules/handlers/default/events.md b/packages/guides/src/content/docs/modules/handlers/default/events.md deleted file mode 100644 index 6455326..0000000 --- a/packages/guides/src/content/docs/modules/handlers/default/events.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Handling Events -description: How to use lilybird's handlers for events. -sidebar: - order: 1 ---- - -## Creating a listener - -```diff lang="ts" title="index.ts" -import { createClient, Intents } from "lilybird"; -+import { handler } from "@lilybird/handlers/advanced"; - -+handler.cachePath = `${import.meta.dir}/lily-cache/handler`; -+await handler.scanDir(`${import.meta.dir}/events`); - -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], -- listeners: {/* your listeners */} -+ setup: async (client) => { -+ await handler.loadGlobalCommands(client); -+ }, -+ listeners: handler.getListenersObject() -}) -``` - -```ts title="events/ping.ts" showLineNumbers -import { $listener } from "@lilybird/handlers/advanced"; - -$listener({ - event: "ready", - handle: (client) => { - console.log("Connected as", client.user.username); - } -}); -``` \ No newline at end of file diff --git a/packages/guides/src/content/docs/modules/handlers/simple/application-commands.md b/packages/guides/src/content/docs/modules/handlers/simple/application-commands.md deleted file mode 100644 index 356829f..0000000 --- a/packages/guides/src/content/docs/modules/handlers/simple/application-commands.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Handling Application Commands -description: How to use Lilybird's handlers for application commands. -sidebar: - order: 0 ---- - -Currently the `@lilybird/handlers` package provides only one way of handling application commands, however, I can assure you there are more to come. - -To be completely honest, the current API is not the greatest but was the fastest one for a demo. - -## Creating a simple command - -Lets create a simple `ping` command to demonstrate how it works. - -```diff lang="ts" title="index.ts" -import { createClient, Intents } from "lilybird"; -+import { createHandler } from "@lilybird/handlers/simple"; - -+const listeners = await createHandler({ -+ dirs: { -+ slashCommands: `${import.meta.dir}/commands`, -+ } -+}) - -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], -- listeners: {/* your listeners */} -+ ...listeners -}) -``` - -```tsx title="commands/ping.tsx" -import { SlashCommand } from "@lilybird/handlers/simple"; - -export default { - post: "GLOBAL", - data: { - name: "ping", - description: "pong" - }, - run: async (interaction) => { - const { ws, rest } = await interaction.client.ping(); - - await interaction.reply({ - content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\`` - }); - }, -} satisfies SlashCommand -``` - -:::note -The above code was taken from the [Bun Discord bot](https://github.com/xHyroM/bun-discord-bot), join the Bun Discord server to see it in action. -::: \ No newline at end of file diff --git a/packages/guides/src/content/docs/modules/handlers/simple/events.md b/packages/guides/src/content/docs/modules/handlers/simple/events.md deleted file mode 100644 index b101bee..0000000 --- a/packages/guides/src/content/docs/modules/handlers/simple/events.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Handling Events -description: How to use lilybird's handlers for events. -sidebar: - order: 1 ---- - -Currently the `@lilybird/handlers` package provides only one way of handling events, however, I can assure you there are more to come. - -## Creating a listener - -```diff lang="ts" title="index.ts" -import { createClient, Intents } from "lilybird"; -+import { createHandler } from "@lilybird/handlers/simple"; - -+const listeners = await createHandler({ -+ dirs: { -+ events: `${import.meta.dir}/events`, -+ } -+}) - -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], -- listeners: {/* your listeners */} -+ ...listeners -}) -``` - -```ts title="events/ping.ts" -import { Event } from "@lilybird/handlers/simple"; - -export default { - event: "ready", - run: (client) => { - console.log(`Logged in as ${client.user.username}`); - }, -// This duplication is needed for TypeScript types to work properly -// This is also why this API isn't the best -} satisfies Event<"ready"> -``` \ No newline at end of file diff --git a/packages/guides/src/content/docs/modules/handlers/simple/message-commands.md b/packages/guides/src/content/docs/modules/handlers/simple/message-commands.md deleted file mode 100644 index 528fb05..0000000 --- a/packages/guides/src/content/docs/modules/handlers/simple/message-commands.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Handling Message Commands -description: How to use lilybird's handlers for application commands. -sidebar: - order: 2 ---- - -Currently the `@lilybird/handlers` package provides only one way of handling application commands, however, I can assure you there are more to come. - -## Creating a simple command - -Let's create a simple `ping` command to demonstrate how it works. - -```diff lang="ts" title="index.ts" -import { createClient, Intents } from "lilybird"; -+import { createHandler } from "@lilybird/handlers/simple"; - -+const listeners = await createHandler({ -+ dirs: { -+ messageCommands: `${import.meta.dir}/commands`, -+ } -+}) - -await createClient({ - token: process.env.TOKEN, - intents: [Intents.GUILDS], -- listeners: {/* your listeners */} -+ ...listeners -}) -``` - -:::note -The second argument, `args`, is the result of running the following code: -```ts -message.content.slice(this.prefix.length) - .trim() - .split(/\s+/g) - .shift() -``` -::: - -```ts title="commands/ping.ts" -import { MessageCommand } from "@lilybird/handlers/simple"; - -export default { - name: "ping", - run: async (message, args) => { - const { ws, rest } = await message.client.ping(); - - await message.reply({ - content: `🏓 WebSocket: \`${ws}ms\` | Rest: \`${rest}ms\`` - }); - }, -} satisfies MessageCommand -``` - -:::note -The above code was taken from the [Bun Discord bot](https://github.com/xHyroM/bun-discord-bot), join the Bun Discord server to see it in action. -::: \ No newline at end of file diff --git a/packages/guides/src/content/docs/modules/jsx/attachment.mdx b/packages/guides/src/content/docs/modules/jsx/attachment.mdx deleted file mode 100644 index 0ab86cc..0000000 --- a/packages/guides/src/content/docs/modules/jsx/attachment.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Attachments -description: How to use Lilybird's Attachment component. -sidebar: - order: 4 ---- - -import { Tabs, TabItem } from "@astrojs/starlight/components"; - -## Creating attachments - -The `Attachment` component is really simple to use, it just gets either a file or a path and returns a `LilybirdAttachment`. - -:::caution -Passing in `path` instead of `file` will make it so Lilybird will try to use `Bun` APIs, so if you are using `Node` pass in a `Blob` or extension of blob instead. -::: - - - - ```tsx - import { Attachment } from "@lilybird/jsx"; - import { join } from "node:path" - - const att = ; - ``` - - - ```tsx - import { Attachment } from "@lilybird/jsx"; - import { readFile } from "node:fs/promises"; - - const att = ; - ``` - - - -## Using attachments - -When using the the `Attachment` component you can easily pass in `.uri` to embeds and other places that use the URI form of attachments. - -An example would be: - -```tsx ins="att.uri" -import { Attachment, Embed, EmbedImage } from "@lilybird/jsx"; -import { join } from "node:path" - -const att = ; - -const embed = ( - - - -); -``` \ No newline at end of file diff --git a/packages/guides/src/content/docs/modules/jsx/command.md b/packages/guides/src/content/docs/modules/jsx/command.md deleted file mode 100644 index b382648..0000000 --- a/packages/guides/src/content/docs/modules/jsx/command.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Application Commands -description: How to use lilybird's ApplicationCommand component. -sidebar: - order: 1 ---- - -## Slash Commands - -Creating slash commands with Lilybird's JSX components is a trivial task. You can create one with the parent element `ApplicationCommand` and add options by importing the respective `Option`. - -```tsx title="doc-command.tsx" -import { ApplicationCommand, StringOption, UserOption } from "@lilybird/jsx"; - -const command = ( - - - - -); -``` - -:::note -The above code was taken from the [Bun Discord bot](https://github.com/xHyroM/bun-discord-bot) as they have been a huge help in this project. -::: \ No newline at end of file diff --git a/packages/guides/src/content/docs/modules/jsx/components.md b/packages/guides/src/content/docs/modules/jsx/components.md deleted file mode 100644 index 7c0322b..0000000 --- a/packages/guides/src/content/docs/modules/jsx/components.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Message Components -description: How to use Lilybird's message components. -sidebar: - order: 3 ---- - -The building block of all message components is the `ActionRow` component. -It wraps all the other components and exists exactly just for that reason. - -All of the components have all the properties that Discord allows, and they are really simple to use. - -Discord reference: [Message Components](https://discord.com/developers/docs/interactions/message-components) - -## Buttons - -Buttons are the simplest and most used component type, and Lilybird makes their usage even more trivial. - -```tsx -import { ActionRow, Button } from "@lilybird/jsx"; -import { ButtonStyle } from "lilybird"; - -const buttonRow = ( - -