diff --git a/bun.lockb b/bun.lockb index f32503e..6572755 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 739ef08..ec76334 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,12 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.717.0", + "@barudakrosul/textwrap": "^0.0.4", "@octokit/rest": "^21.0.2", "@t3-oss/env-core": "^0.11.1", "@types/sharp": "^0.32.0", "bufferutil": "^4.0.8", + "cowsay": "^1.6.0", "dayjs": "^1.11.13", "discord.js": "^14.16.3", "fastest-levenshtein": "^1.0.16", diff --git a/src/commands/cowsay.ts b/src/commands/cowsay.ts new file mode 100644 index 0000000..5becdf4 --- /dev/null +++ b/src/commands/cowsay.ts @@ -0,0 +1,98 @@ +import { + type ChatInputCommandInteraction, + SlashCommandBuilder, +} from "discord.js"; +import cowsay from "cowsay"; +import textwrap from "@barudakrosul/textwrap"; + +const DEFAULT_WRAP_WIDTH = 40; + +export const data = new SlashCommandBuilder() + .setName("cowsay") + .setDescription("Make a cow say it") + .addStringOption((option) => + option + .setName("text") + .setDescription("The text to say") + .setRequired(true), + ) + .addStringOption((option) => + option + .setName("cow") + .setDescription("The cow design to use") + .setRequired(false), + ) + .addIntegerOption((option) => + option + .setName("wrap_width") + .setDescription( + "Width at which to wrap the input text (0 to disable)", + ) + .setRequired(false), + ); + +export async function command(interaction: ChatInputCommandInteraction) { + const { options } = interaction; + + const text = options.getString("text", true); + const cow = options.getString("cow"); + const wrapWidth = options.getInteger("wrap_width") ?? DEFAULT_WRAP_WIDTH; + + const isChannel = !!interaction.channel; + + if (!isChannel) { + await interaction.reply({ + content: "This command can only be used in a channel", + ephemeral: true, + }); + return; + } + + if (text === "!list") { + for (const chunk of await getCowListResponseContent()) { + await interaction.reply({ + content: chunk, + ephemeral: true, + }); + } + } else { + await interaction.reply({ + content: getCowsayResponseContent(text, cow, wrapWidth), + }); + } +} + +function getCowsayResponseContent( + text: string, + cow: string | null, + wrapWidth: number, +): string { + const wrappedText = + wrapWidth > 0 ? textwrap.wrap(text, wrapWidth).join("\n") : text; + const cowsaid = cowsay.say({ + text: wrappedText, + f: cow ?? undefined, + }); + return "```\n" + cowsaid + "\n```"; +} + +async function getCowListResponseContent(): Promise { + try { + const filenames = await cowsay.list(() => {}); + const cows = filenames.map((name) => name.replace(/\.cow$/, "")); + const chunks = ["```\n"]; + for (const cow of cows) { + if (chunks[chunks.length - 1].length + cow.length + 1 + 3 > 2000) { + chunks[chunks.length - 1] += "```"; + chunks.push("```\n"); + } + chunks[chunks.length - 1] += cow + "\n"; + } + chunks[chunks.length - 1] += "```"; + return chunks; + } catch (e) { + const msg = (e as Error).message; + console.error(`Error listing cows: ${msg}`); + return ["Error listing cows"]; + } +} diff --git a/src/commands/index.ts b/src/commands/index.ts index 2e2e0b1..4bfc7b3 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -4,10 +4,11 @@ import type { } from "discord.js"; import * as summarize from "./summarize"; +import * as cowsay from "./cowsay"; type Command = { data: SlashCommandOptionsOnlyBuilder; command: (interaction: ChatInputCommandInteraction) => Promise; }; -export const commands: Command[] = [summarize]; +export const commands: Command[] = [summarize, cowsay];