diff --git a/bun.lockb b/bun.lockb index e9e6a68..5197d02 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index c4b8ae5..8013ab2 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,15 @@ "bun-types": "^1.1.6" }, "dependencies": { - "@lilybird/handlers": "^0.4.0", - "@lilybird/jsx": "0.2.0", - "@lilybird/transformers": "^0.2.0", + "@lilybird/handlers": "^0.6.0-beta.9", + "@lilybird/jsx": "0.3.0", + "@lilybird/transformers": "^0.4.1", "@paperdave/logger": "^3.0.1", "@purplet/serialize": "^2.0.0", "@wolfram-alpha/wolfram-alpha-api": "^23.1004.144821-RELEASE", "algoliasearch": "^4.23.2", "bun-tracestrings": "github:oven-sh/bun.report", "gray-matter": "^4.0.3", - "lilybird": "^0.7.1-alpha.0" + "lilybird": "^0.7.3" } -} +} \ No newline at end of file diff --git a/src/commands/docs.tsx b/src/commands/docs.ts similarity index 71% rename from src/commands/docs.tsx rename to src/commands/docs.ts index 15a9278..004d2ce 100644 --- a/src/commands/docs.tsx +++ b/src/commands/docs.ts @@ -1,9 +1,5 @@ -import { - ApplicationCommand as JSXApplicationCommand, - StringOption, - UserOption, -} from "@lilybird/jsx"; -import { ApplicationCommand } from "@lilybird/handlers"; +import { AllowedMentionType, ApplicationCommandOptionType } from "lilybird"; +import { $applicationCommand } from "@lilybird/handlers/advanced"; import algoliasearch from "algoliasearch"; import { safeSlice } from "src/util.ts"; @@ -14,19 +10,22 @@ const algoliaClient = algoliasearch( ); const algoliaIndex = algoliaClient.initIndex("bun"); -export default { - post: "GLOBAL", - data: ( - - - - - ), +$applicationCommand({ + name: "docs", + description: "Search at docs", + options: [ + { + type: ApplicationCommandOptionType.STRING, + name: "query", + description: "Select query", + required: true, + autocomplete: true, + }, { + type: ApplicationCommandOptionType.USER, + name: "target", + description: "User to mention" + } + ], autocomplete: async (interaction) => { const query = interaction.data.getFocused().value; const result = await algoliaIndex.search(query, { @@ -44,7 +43,7 @@ export default { }) ); }, - run: async (interaction) => { + handle: async (interaction) => { await interaction.deferReply(); const query = interaction.data.getString("query"); @@ -69,21 +68,20 @@ export default { snippetContent ? snippetContent : "", notice ? notice - .split("\n") - .map((s: any) => `> ${s}`) - .join("\n") + .split("\n") + .map((s: any) => `> ${s}`) + .join("\n") : "", ].join("\n"); - // @ts-expect-error allowed_mentions await interaction.editReply({ content, allowed_mentions: { - parse: target ? ["users"] : [], + parse: target ? [AllowedMentionType.UserMentions] : [], }, }); }, -} satisfies ApplicationCommand; +}); function getHitName(hit: any) { const type = hit.hierarchy.lvl0 === "Documentation" ? "πŸ“–" : "πŸ—ΊοΈ"; diff --git a/src/commands/github.tsx b/src/commands/github.ts similarity index 72% rename from src/commands/github.tsx rename to src/commands/github.ts index 432baca..85bfdea 100644 --- a/src/commands/github.tsx +++ b/src/commands/github.ts @@ -1,10 +1,6 @@ -import { - ApplicationCommand as JSXApplicationCommand, - BooleanOption, - CommandOptions, - StringOption, -} from "@lilybird/jsx"; -import { ApplicationCommand } from "@lilybird/handlers"; + +import { $applicationCommand } from "@lilybird/handlers/advanced"; +import { ApplicationCommandOptionType } from "lilybird"; import { safeSlice, silently } from "../util.ts"; type State = @@ -37,56 +33,57 @@ interface Item { }; } -export default { - post: "GLOBAL", - data: ( - - - - - - - - - - - - - - - - - - - ), - run: async (interaction) => { +$applicationCommand({ + name: "github", + description: "Query an issue, pull request or direct link to issue, pull request", + options: [ + { + type: ApplicationCommandOptionType.STRING, + name: "query", + description: "Issue/Pull request number or name", + autocomplete: true, + required: true, + max_length: 100 + }, { + type: ApplicationCommandOptionType.STRING, + name: "state", + description: "Issue or Pull request state", + choices: [ + { name: "πŸ”΄πŸŸ  Open", value: "open" }, + { name: "🟒 Closed as completed", value: "closed_as_completed" }, + { name: "βšͺ️ Closed as not planned", value: "closed_as_not_planned" }, + { name: "⚫️ Closed", value: "closed" }, + { name: "🟣 Merged", value: "merged" }, + { name: "πŸ“ Draft", value: "draft" }, + { name: "🌍 All", value: "all" } + ] + }, { + type: ApplicationCommandOptionType.STRING, + name: "type", + description: "Issue/Pull request number or name", + choices: [ + { name: "πŸ› Issues", value: "issues" }, + { name: "πŸ”¨ Pull Requests", value: "pull_requests" }, + { name: "🌍 Both", value: "both" } + ], + }, + { + type: ApplicationCommandOptionType.BOOLEAN, + name: "hide", + description: "Show this message only for you" + } + ], + handle: async (interaction) => { const hide = interaction.data.getBoolean("hide") ?? false; await interaction.deferReply(hide); const query = interaction.data.getString("query", true); - const state: State = - (interaction.data.getString("state") as State) || "all"; + const state: State = (interaction.data.getString("state") as State) || "all"; const type: Type = (interaction.data.getString("type") as Type) || "both"; const result = (await search(query, state, type))[0]; if (!result) { - // @ts-expect-error allowed_mentions interaction.editReply({ content: `❌ Couldn't find issue or pull request \`${query}\``, allowed_mentions: { @@ -96,13 +93,10 @@ export default { return; } - // @ts-expect-error allowed_mentions interaction.editReply({ content: [ - `${result.emoji.type} ${result.emoji.state} [#${ - result.number - } in oven-sh/bun](<${result.html_url}>) by [${result.user.login}](<${ - result.user.html_url + `${result.emoji.type} ${result.emoji.state} [#${result.number + } in oven-sh/bun](<${result.html_url}>) by [${result.user.login}](<${result.user.html_url }>) ${stateToText(result)} ${stateToTimestamp(result)}`, result.title, ].join("\n"), @@ -111,7 +105,6 @@ export default { }, }); }, - autocomplete: async (interaction) => { const query = interaction.data.getFocused().value; const state: State = @@ -132,7 +125,7 @@ export default { ) ); }, -} satisfies ApplicationCommand; +}); function stateToText(item: Item) { switch (item.emoji.state) { diff --git a/src/commands/mdn.tsx b/src/commands/mdn.ts similarity index 79% rename from src/commands/mdn.tsx rename to src/commands/mdn.ts index 3ccf635..f1c7ad5 100644 --- a/src/commands/mdn.tsx +++ b/src/commands/mdn.ts @@ -1,13 +1,7 @@ // https://github.com/discordjs/discord-utils-bot/blob/main/src/functions/autocomplete/mdnAutoComplete.ts#L23-L47 thanks // https://github.com/discordjs/discord-utils-bot/blob/main/src/functions/mdn.ts#L59C1-L78C3 thanks - -import { - BooleanOption, - ApplicationCommand as JSXApplicationCommand, - StringOption, - UserOption, -} from "@lilybird/jsx"; -import { ApplicationCommand } from "@lilybird/handlers"; +import { ApplicationCommandOptionType, AllowedMentionType } from "lilybird"; +import { $applicationCommand } from "@lilybird/handlers/advanced"; import { MDN_API, MDN_DISCORD_EMOJI } from "src/constants.ts"; import { safeSlice, silently } from "src/util.ts"; @@ -43,25 +37,29 @@ const MDN_DATA = (await ( const cache = new Map(); -export default { - post: "GLOBAL", - data: ( - - - - - - ), - run: async (interaction) => { +$applicationCommand({ + + name: "mdn", + description: "Search the Mozilla Developer Network documentation", + options: [ + { + type: ApplicationCommandOptionType.STRING, + name: "query", + description: "Class or method to search for", + required: true, + autocomplete: true, + max_length: 100 + }, { + type: ApplicationCommandOptionType.USER, + name: "target", + description: "User to mention" + }, { + type: ApplicationCommandOptionType.BOOLEAN, + name: "hide", + description: "Show this message only for you" + } + ], + handle: async (interaction) => { const hide = interaction.data.getBoolean("hide") ?? false; await interaction.deferReply(hide); @@ -101,14 +99,13 @@ export default { intro, ]; - // @ts-expect-error allowed_mentions await interaction.editReply({ content: [ target ? `*Suggestion for <@${target}>:*\n` : "", parts.join("\n"), ].join("\n"), allowed_mentions: { - parse: target ? ["users"] : [], + parse: target ? [AllowedMentionType.UserMentions] : [], }, }); }, @@ -151,7 +148,7 @@ export default { ) ); }, -} satisfies ApplicationCommand; +}); function escape(text: string) { return text.replaceAll("||", "|\u200B|").replaceAll("*", "\\*"); diff --git a/src/commands/ping.tsx b/src/commands/ping.tsx index cc388a3..e3e985d 100644 --- a/src/commands/ping.tsx +++ b/src/commands/ping.tsx @@ -1,17 +1,42 @@ -import { - ActionRow, - Button, - ApplicationCommand as JSXApplicationCommand, -} from "@lilybird/jsx"; -import { ApplicationCommand } from "@lilybird/handlers"; +import { $applicationCommand } from "@lilybird/handlers/advanced"; import { serializers as S } from "@purplet/serialize"; -import { possibleClosedForm } from "../util.ts"; -import { ButtonStyle } from "lilybird"; +import { ActionRow, Button } from "@lilybird/jsx"; +import { possibleClosedForm, silently } from "../util.ts"; +import { ButtonStyle, ComponentType } from "lilybird"; -export default { - post: "GLOBAL", - data: , - run: async (interaction) => { +$applicationCommand({ + name: "ping", + description: "pong", + components: [{ + type: ComponentType.Button, + id: "ping", + customMatcher: `custom_id[0] == "0" && custom_id[1] == "-"`, + handle: (interaction) => { + const combined = interaction.data.id.split("-")?.[1]; + if (!combined) return; + + const [ws, wsClosedForm, rest, restClosedForm] = S.generic.decodeCustomId(combined); + + silently( + interaction.reply({ + content: [ + `πŸ“`, + "**WebSocket:**", + `\`${wsClosedForm}\``, + `\`β‰ˆ ${ws} ms\``, + "", + "**Rest:**", + `\`${restClosedForm}\``, + `\`β‰ˆ ${rest} ms\``, + "", + "Mathematics is the language of the universe, it's truly fascinating! πŸ˜„", + ].join("\n"), + ephemeral: true, + }) + ); + } + }], + handle: async (interaction) => { await interaction.deferReply(); const { ws, rest } = await interaction.client.ping(); @@ -46,4 +71,4 @@ export default { ], }); }, -} satisfies ApplicationCommand; +}); diff --git a/src/commands/tag.tsx b/src/commands/tag.ts similarity index 63% rename from src/commands/tag.tsx rename to src/commands/tag.ts index e5ca776..c564ace 100644 --- a/src/commands/tag.tsx +++ b/src/commands/tag.ts @@ -1,32 +1,32 @@ -import { - ApplicationCommand as JSXApplicationCommand, - StringOption, - UserOption, -} from "@lilybird/jsx"; +import { AllowedMentionType, ApplicationCommandOptionType } from "lilybird"; +import { $applicationCommand } from "@lilybird/handlers/advanced"; import { getTags, searchTag } from "../loaders/tags.ts"; -import { ApplicationCommand } from "@lilybird/handlers"; -export default { - post: "GLOBAL", - data: ( - - - - - ), - run: async (interaction) => { +$applicationCommand({ + name: "tag", + description: "Get tag", + options: [ + { + + type: ApplicationCommandOptionType.STRING, + name: "query", + description: "Select query", + required: true, + autocomplete: true + }, + { + type: ApplicationCommandOptionType.USER, + name: "target", + description: "User to mention" + } + ], + handle: async (interaction) => { if (!interaction.inGuild()) return; const query = interaction.data.getString("query", true); const target = interaction.data.getUser("target"); const tag = searchTag(interaction.channel, query, false); if (!tag) { - // @ts-expect-error allowed_mentions return interaction.reply({ content: `\`❌\` Could not find a tag \`${query}\``, ephemeral: true, @@ -36,7 +36,6 @@ export default { }); } - // @ts-expect-error allowed_mentions await interaction.reply({ content: [ target ? `*Suggestion for <@${target}>:*\n` : "", @@ -44,7 +43,7 @@ export default { tag.answer, ].join("\n"), allowed_mentions: { - parse: target ? ["users"] : [], + parse: target ? [AllowedMentionType.UserMentions] : [], }, }); }, @@ -60,4 +59,4 @@ export default { return await interaction.showChoices(getTags(interaction.channel, 25)); }, -} satisfies ApplicationCommand; +}); diff --git a/src/commands/version.tsx b/src/commands/version.ts similarity index 64% rename from src/commands/version.tsx rename to src/commands/version.ts index d873f63..59107fe 100644 --- a/src/commands/version.tsx +++ b/src/commands/version.ts @@ -1,3 +1,5 @@ +import { $applicationCommand } from "@lilybird/handlers/advanced"; + import { COMMIT_HASH, PRODUCTION, @@ -5,17 +7,14 @@ import { LILYBIRD_HANDLERS_VERSION, LILYBIRD_JSX_VERSION, } from "../constants.ts"; -import { ApplicationCommand as JSXApplicationCommand } from "@lilybird/jsx"; -import { ApplicationCommand } from "@lilybird/handlers"; -export default { - post: "GLOBAL", - data: , - run: (interaction) => { +$applicationCommand({ + name: "version", + description: "Show version", + handle: (interaction) => { interaction.reply({ content: [ - `[git-bun-discord-bot-${COMMIT_HASH}]() ${ - !PRODUCTION ? "(dev)" : "" + `[git-bun-discord-bot-${COMMIT_HASH}]() ${!PRODUCTION ? "(dev)" : "" }`, `[Bun v${Bun.version} (${Bun.revision})]()`, "", @@ -26,4 +25,4 @@ export default { ephemeral: true, }); }, -} satisfies ApplicationCommand; +}); diff --git a/src/handler.ts b/src/handler.ts new file mode 100644 index 0000000..925bac0 --- /dev/null +++ b/src/handler.ts @@ -0,0 +1,12 @@ +import { defaultTransformers } from "@lilybird/transformers"; +import { Handler } from "@lilybird/handlers/advanced"; + +const { interactionCreate, ...simpleTransformers } = defaultTransformers; + +export type SimpleTransformers = typeof simpleTransformers; +export const transformers = simpleTransformers; + +export const handler = new Handler({}); +export const $applicationCommand = handler.storeCommand.bind(handler); +export const $listener = handler.storeListener.bind(handler); +export const $component = handler.buttonCollector.bind(handler); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index c50cf87..0817778 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,21 +1,34 @@ import "./loaders/tags.ts"; +import { SimpleTransformers, transformers, handler } from "./handler.ts"; +import { ClientListeners, Intents, createClient } from "lilybird"; import { createHandler } from "@lilybird/handlers/simple"; -import { createClient, Intents } from "lilybird"; +import { info } from "@paperdave/logger" // Make sure bubu will not crash process.on("unhandledRejection", console.error); process.on("uncaughtException", console.error); -const listeners = await createHandler({ + + +handler.cachePath = `${import.meta.dir}/lily-cache/handler`; + +await handler.scanDir(`${import.meta.dir}/commands`); +await handler.scanDir(`${import.meta.dir}/listeners`); + +const { listeners: { messageCreate } } = await createHandler({ prefix: process.env.MESSAGE_PREFIX, dirs: { - messageCommands: `${import.meta.dir}/message-commands`, - slashCommands: `${import.meta.dir}/commands`, - listeners: `${import.meta.dir}/listeners`, + messageCommands: `${import.meta.dir}/message-commands` }, }); +const listeners = handler.getListenersObject() as ClientListeners; + +if (typeof listeners.messageCreate !== "undefined" && typeof messageCreate !== "undefined") + listeners.messageCreate = async (m) => { await listeners.messageCreate!(m); await messageCreate(m) } +else if (typeof messageCreate !== "undefined") listeners.messageCreate = messageCreate; + await createClient({ token: process.env.DISCORD_BOT_TOKEN, intents: [ @@ -24,5 +37,10 @@ await createClient({ Intents.MESSAGE_CONTENT, Intents.GUILD_MEMBERS, ], - ...listeners, + setup: async (client) => { + info(`Logged in as ${client.user.username} (${client.user.id})`); + await handler.loadGlobalCommands(client); + }, + transformers, + listeners }); diff --git a/src/listeners/interaction_create.ts b/src/listeners/interaction_create.ts deleted file mode 100644 index a36275d..0000000 --- a/src/listeners/interaction_create.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Event } from "@lilybird/handlers"; -import { serializers as S } from "@purplet/serialize"; -import { silently } from "src/util.ts"; - -export default { - event: "interactionCreate", - run: (interaction) => { - if ( - !interaction.isMessageComponentInteraction() || - !interaction.data.isButton() - ) - return; - - const id = interaction.data.id; - - if (id?.[0] == "0" && id?.[1] == "-") { - const combined = interaction.data.id.split("-")?.[1]; - if (!combined) return; - - const [ws, wsClosedForm, rest, restClosedForm] = - S.generic.decodeCustomId(combined); - - silently( - interaction.reply({ - content: [ - `πŸ“`, - "**WebSocket:**", - `\`${wsClosedForm}\``, - `\`β‰ˆ ${ws} ms\``, - "", - "**Rest:**", - `\`${restClosedForm}\``, - `\`β‰ˆ ${rest} ms\``, - "", - "Mathematics is the language of the universe, it's truly fascinating! πŸ˜„", - ].join("\n"), - ephemeral: true, - }) - ); - } - }, -} satisfies Event<"interactionCreate">; diff --git a/src/listeners/member_add.ts b/src/listeners/member_add.ts index 22a3cfe..584c246 100644 --- a/src/listeners/member_add.ts +++ b/src/listeners/member_add.ts @@ -1,9 +1,9 @@ +import { $listener } from "../handler.ts"; import { moderateNick } from "../util.ts"; -import { Event } from "@lilybird/handlers"; -export default { +$listener({ event: "guildMemberAdd", - run: (member) => { + handle: (member) => { moderateNick(member); }, -} satisfies Event<"guildMemberAdd">; +}); diff --git a/src/listeners/member_update.ts b/src/listeners/member_update.ts index e403953..5183dc9 100644 --- a/src/listeners/member_update.ts +++ b/src/listeners/member_update.ts @@ -1,9 +1,9 @@ +import { $listener } from "../handler.ts"; import { moderateNick } from "../util.ts"; -import { Event } from "@lilybird/handlers"; -export default { +$listener({ event: "guildMemberUpdate", - run: (member) => { + handle: (member) => { moderateNick(member); }, -} satisfies Event<"guildMemberUpdate">; +}); diff --git a/src/listeners/message_create.tsx b/src/listeners/message_create.tsx index 684b5fb..0b5c3b4 100644 --- a/src/listeners/message_create.tsx +++ b/src/listeners/message_create.tsx @@ -1,8 +1,9 @@ -import { ButtonStyle } from "lilybird"; -import { Message } from "@lilybird/transformers"; import { ActionRow, Button } from "@lilybird/jsx"; +import { Message } from "@lilybird/transformers"; import { extname, basename } from "node:path"; -import { Event } from "@lilybird/handlers"; +import { $listener } from "../handler.ts"; +import { ButtonStyle } from "lilybird"; + import { getBunReportDetailsInMarkdown, getRandomBunEmoji, @@ -12,18 +13,16 @@ import { const GITHUB_LINE_URL_REGEX = /(?:https?:\/\/)?(?:www\.)?(?:github)\.com\/(?[a-zA-Z0-9-_]+\/[A-Za-z0-9_.-]+)\/blob\/(?.+?)#L(?\d+)[-~]?L?(?\d*)/i; -const TWITTER_TWEET_URL_REGEX = - /https:\/\/(?:www\.)?(?:twitter|x)\.com\/(?[a-zA-Z0-9-_]+)\/status\/(?\d+)/i; const BUN_REPORT_URL_REGEX = /(https:\/\/bun\.report\/\d+\.\d+(\.\d+)?\/\S+)/g; -export default { +$listener({ event: "messageCreate", - run: async (message) => { + handle: (message) => { if (handleBunOnlyChannel(message)) return; if (!message.content?.toLowerCase().startsWith(process.env.MESSAGE_PREFIX)) return handleOthers(message); }, -} satisfies Event<"messageCreate">; +}); function handleOthers(message: Message): void { handleGithubLink(message); @@ -92,21 +91,18 @@ async function handleGithubLink(message: Message): Promise { if (extension === "zig") extension = "rs"; - // @ts-expect-error allowed_mentions message.reply({ - content: `***${basename(path)}*** β€” *(L${firstLineNumber + 1}${ - secondLineNumber ? `-L${secondLineNumber}` : "" - })*\n\`\`\`${extension}\n${safeSlice( - text, - 2000 - 6 - extension.length - )}\n\`\`\``, + content: `***${basename(path)}*** β€” *(L${firstLineNumber + 1}${secondLineNumber ? `-L${secondLineNumber}` : "" + })*\n\`\`\`${extension}\n${safeSlice( + text, + 2000 - 6 - extension.length + )}\n\`\`\``, components: [