From 87fcfff6dcf9628fae53b6667c23cc3638c35de2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:22:16 +0000 Subject: [PATCH 1/7] Initial plan From f02185d687f74cf0689ae0077ac8dfd5a6ce919f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:29:34 +0000 Subject: [PATCH 2/7] Add Slack support with message adapter pattern (resolves #10) - Add @slack/bolt dependency for Slack integration - Create SlackMessageAdapter to normalize Slack events to Discord message interface - Create slack.js entry point with Socket Mode support - Add start:slack npm script - Add Slack reminder daemon - Update README.md and getting-started.md with Slack setup instructions Co-authored-by: circa10a <21261388+circa10a@users.noreply.github.com> --- README.md | 26 +- docs/getting-started.md | 111 ++- lib/adapters/slack.js | 139 ++++ package-lock.json | 1551 +++++++++++++++++++++++++++++++++++++-- package.json | 8 +- slack.js | 117 +++ 6 files changed, 1857 insertions(+), 95 deletions(-) create mode 100644 lib/adapters/slack.js create mode 100644 slack.js diff --git a/README.md b/README.md index e5241fd..e83888b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![alt text](https://img.shields.io/badge/Invite%20To-Discord%20-blue)](https://discord.com/api/oauth2/authorize?client_id=876487225716662302&permissions=34359863296&scope=bot) ![Build Status](https://github.com/circa10a/ava/workflows/publish/badge.svg) -A discord implementation of the famous ava bot +A discord and slack implementation of the famous ava bot @@ -13,18 +13,32 @@ A discord implementation of the famous ava bot ## Usage +### Discord + ```bash export AVA_DISCORD_TOKEN= npm i npm start ``` +### Slack + +```bash +export AVA_SLACK_BOT_TOKEN= +export AVA_SLACK_APP_TOKEN= +npm i +npm run start:slack +``` + ## Config | | | | |----------------------------|----------|---------| | Environment Variable | Required | Default | -| `AVA_DISCORD_TOKEN` | Yes | `""` | +| `AVA_DISCORD_TOKEN` | Yes (Discord) | `""` | +| `AVA_SLACK_BOT_TOKEN` | Yes (Slack) | `""` | +| `AVA_SLACK_APP_TOKEN` | Yes (Slack) | `""` | +| `AVA_SLACK_SIGNING_SECRET` | No | `""` | | `AVA_DB_DIR` | No | `./` | | `AVA_ENABLE_REMINDERS` | No | `false` | | `AVA_REDDIT_CLIENT_ID` | No | `""` | @@ -65,10 +79,18 @@ npm start ## Docker +### Discord + ```bash docker run -e AVA_DISCORD_TOKEN="" circa10a/ava ``` +### Slack + +```bash +docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava npm run start:slack +``` + ## Contribution We welcome all contributions! Please visit the [contribution documentation](docs/CONTRIBUTION.md) to get started. diff --git a/docs/getting-started.md b/docs/getting-started.md index e3ca6db..ba40910 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -2,16 +2,19 @@ This guide will help you get started with local development. -In this guide, you will start a local server instance of Ava, create a Discord test bot, lastly, test Ava commands through the test bot in a test channel. +In this guide, you will start a local server instance of Ava for either Discord or Slack. ## Prerequisites * [Node.js](https://nodejs.org/en/) v16 or greater -* [Discord](https://discord.com/) account -* [A Discord channel](https://blog.discord.com/starting-your-first-discord-server-4dcacda8dad5) -* [Docker](https://www.docker.com/products/docker-desktop) +* [Docker](https://www.docker.com/products/docker-desktop) (optional) +* A [Discord](https://discord.com/) account **or** a [Slack](https://slack.com/) workspace -## Create Test bot +--- + +## Discord Setup + +### Create Test bot Follow the steps in this [tutorial](https://www.freecodecamp.org/news/create-a-discord-bot-with-python/) to create an Ava test bot. @@ -33,64 +36,110 @@ The next page will take you to the bot channel invite page. Go ahead and select ![View of Discord invite modal with channel prompt](../public/images/add_bot_channel.png) -## Bot Token +### Bot Token The bot token can be found on your test application's bot page. You will need the token value for the next step. ![bot token view](../public/images/bot_token_step.png) -## Start the local server +### Start the local server There are two methods for starting Ava locally. You can use Docker or run it as a node.js process. -### Docker +#### Docker -This project creates a Docker image that contains the application logic to run Ava locally. Issue the command below to spin up a docker container running Ava. +```shell +docker run -e AVA_DISCORD_TOKEN="" circa10a/ava +``` -**NOTE**: Ensure you provide a Discord token. +#### Non-Docker ```shell -docker run -e AVA_DISCORD_TOKEN="" circa10a/ava +npm run start ``` You should see a message when the server is up and running. +``` +[INFO] Ready! Logged in as ... +``` -```shell - docker run -e AVA_DISCORD_TOKEN=$AVA_DISCORD_TOKEN circa10a/ava +### Verify Ava (Discord) -> ava@1.0.0 start -> node ./index.js +Go visit your Discord test channel to verify the test bot is operational. -[INFO] Ready! -``` +Go ahead and issue the `ava help` command in the channel. If everything is working correctly, you should see a reply containing all the available Ava commands. + +![ava returns commands](../public/images/ava_validate.png) + +--- + +## Slack Setup + +### Create a Slack App + +1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App**. +2. Choose **From scratch**, give it a name (e.g. `Ava`) and select your workspace. + +### Enable Socket Mode -### Non-Docker +1. In your app settings, go to **Socket Mode** and enable it. +2. Generate an **App-Level Token** with the `connections:write` scope. Save this token — it is the `AVA_SLACK_APP_TOKEN` (starts with `xapp-`). -Ava can run locally as a node.js process. To start Ava as a node.js process, issue the command into your terminal. You may have to issue `npm install` prior to starting the server if you have not done so prior. +### Configure Bot Token Scopes -**NOTE:** The command must be issued in the project root. +Go to **OAuth & Permissions** and add the following **Bot Token Scopes**: + +- `chat:write` — Send messages +- `channels:history` — Read messages in public channels +- `groups:history` — Read messages in private channels +- `im:history` — Read direct messages +- `mpim:history` — Read group direct messages + +### Enable Event Subscriptions + +Go to **Event Subscriptions** and enable events. Under **Subscribe to bot events**, add: + +- `message.channels` +- `message.groups` +- `message.im` +- `message.mpim` + +### Install to Workspace + +Go to **Install App** and install it to your workspace. Copy the **Bot User OAuth Token** — this is the `AVA_SLACK_BOT_TOKEN` (starts with `xoxb-`). + +### Invite the Bot + +Invite the bot to a channel by typing `/invite @Ava` in the channel. + +### Start the local server + +#### Docker ```shell -npm run start +docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava npm run start:slack ``` -You should see a message when the server is up and running. -``` -> ava@1.0.0 start -> node ./index.js +#### Non-Docker -[INFO] Ready! +```shell +export AVA_SLACK_BOT_TOKEN= +export AVA_SLACK_APP_TOKEN= +npm run start:slack ``` -## Verify Ava +You should see a message when the server is up and running. +``` +[INFO] ⚡️ Ava Slack bot is running! +``` -Go vist your Discord test channel to verify the test bot is operational. +### Verify Ava (Slack) -Go ahead and issue the `ava list` command in the channel. If everything is working correctly, you should see a reply containing all the available Ava commands. +Go to the Slack channel where you invited the bot and type `ava help`. If everything is working correctly, you should see a reply containing all the available Ava commands. -![ava returns commands](../public/images/ava_validate.png) +--- ## Closing -If Ava returned all the commands then congratulations. You now have a local test server, and an ava test bot to verify new commands. +If Ava returned all the commands then congratulations. You now have a local test server and an Ava test bot to verify new commands. diff --git a/lib/adapters/slack.js b/lib/adapters/slack.js new file mode 100644 index 0000000..8161e9b --- /dev/null +++ b/lib/adapters/slack.js @@ -0,0 +1,139 @@ +import logger from '../logger/logger.js'; + +/** + * Converts a Discord EmbedBuilder's data to Slack Block Kit blocks. + * This allows commands that use Discord embeds to work with Slack. + */ +const embedToBlocks = (embed) => { + const data = embed.data || embed; + const blocks = []; + const textParts = []; + + // Title (with optional URL) + if (data.title) { + const titleText = data.url ? `<${data.url}|${data.title}>` : `*${data.title}*`; + blocks.push({ + type: 'section', + text: { type: 'mrkdwn', text: titleText }, + }); + textParts.push(data.title); + } + + // Description with optional thumbnail + if (data.description) { + const section = { + type: 'section', + text: { type: 'mrkdwn', text: data.description }, + }; + if (data.thumbnail && data.thumbnail.url) { + section.accessory = { + type: 'image', + image_url: data.thumbnail.url, + alt_text: data.title || 'thumbnail', + }; + } + blocks.push(section); + textParts.push(data.description); + } else if (data.thumbnail && data.thumbnail.url) { + blocks.push({ + type: 'image', + image_url: data.thumbnail.url, + alt_text: data.title || 'thumbnail', + }); + } + + // Fields + if (data.fields && data.fields.length > 0) { + const fields = data.fields.map((f) => ({ + type: 'mrkdwn', + text: `*${f.name}*\n${f.value}`, + })); + // Slack allows max 10 fields per section + for (let i = 0; i < fields.length; i += 10) { + blocks.push({ + type: 'section', + fields: fields.slice(i, i + 10), + }); + } + } + + // Image + if (data.image && data.image.url) { + blocks.push({ + type: 'image', + image_url: data.image.url, + alt_text: data.title || 'image', + }); + } + + return { blocks, fallbackText: textParts.join('\n') || 'Message from Ava' }; +}; + +/** + * Adapts a Slack message event to match the Discord message interface + * used by all existing commands. This allows command logic to be shared + * between Discord and Slack without modifying command files. + */ +class SlackMessageAdapter { + constructor({ event, client }) { + this._client = client; + this._event = event; + this._threadTs = event.thread_ts || event.ts; + + this.content = event.text || ''; + this.channelId = event.channel; + this.guildId = event.team; + this.id = event.ts; + this.createdTimestamp = Math.floor(parseFloat(event.ts) * 1000); + + const userId = event.user; + this.author = { + username: userId, + toString: () => `<@${userId}>`, + }; + + // Provide channel.send() to mirror Discord's message.channel.send() + this.channel = { + send: async (content) => { + try { + if (typeof content === 'string') { + await client.chat.postMessage({ + channel: event.channel, + text: content, + }); + } else if (content && content.embeds) { + const allBlocks = []; + let fallback = ''; + for (const embed of content.embeds) { + const { blocks, fallbackText } = embedToBlocks(embed); + allBlocks.push(...blocks); + fallback += fallbackText; + } + await client.chat.postMessage({ + channel: event.channel, + blocks: allBlocks, + text: fallback, + }); + } + } catch (e) { + logger.error(`Slack channel.send error: ${e}`); + } + }, + }; + } + + // Mirror Discord's message.reply() - posts as a threaded reply in Slack + async reply(content) { + try { + await this._client.chat.postMessage({ + channel: this._event.channel, + thread_ts: this._threadTs, + text: content, + }); + } catch (e) { + logger.error(`Slack reply error: ${e}`); + } + } +} + +export { SlackMessageAdapter, embedToBlocks }; diff --git a/package-lock.json b/package-lock.json index 4a32b3c..5f7c873 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,14 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^10.0.1", + "@slack/bolt": "^4.1.1", "8ball": "^1.0.6", "chrono-node": "^2.9.0", "dammit": "^0.5.1", "discord.js": "^14.25.1", + "globals": "^17.3.0", "html-entities": "^2.6.0", "image-search-engine": "^1.2.1", "lowdb": "^7.0.1", @@ -185,7 +189,7 @@ "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -204,7 +208,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -217,7 +221,7 @@ "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -227,7 +231,7 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^3.0.2", @@ -242,7 +246,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.1.0" @@ -255,7 +259,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -264,11 +268,123 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", + "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, "node_modules/@eslint/object-schema": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -278,7 +394,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.1.0", @@ -292,7 +408,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -302,7 +418,7 @@ "version": "0.16.7", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -316,7 +432,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -330,7 +446,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -437,6 +553,165 @@ "npm": ">=7.0.0" } }, + "node_modules/@slack/bolt": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.1.1.tgz", + "integrity": "sha512-Sc8QUnEHCPgRlwrMxmvOl4Vhr/7ZBDXvLR0ir6TO+W5MaDtPsrmWLxqLmb+OhCGSjDp3d4AxO6jdFlC3yNKtsg==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/oauth": "^3", + "@slack/socket-mode": "^2.0.2", + "@slack/types": "^2.13.0", + "@slack/web-api": "^7", + "@types/express": "^4.17.21", + "axios": "^1.7.4", + "express": "^5.0.0", + "path-to-regexp": "^8.1.0", + "raw-body": "^3", + "tsscmp": "^1.0.6" + }, + "engines": { + "node": ">=18", + "npm": ">=8.6.0" + } + }, + "node_modules/@slack/bolt/node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@slack/bolt/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@slack/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", + "license": "MIT", + "dependencies": { + "@types/node": ">=18.0.0" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/oauth": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-3.0.4.tgz", + "integrity": "sha512-+8H0g7mbrHndEUbYCP7uYyBCbwqmm3E6Mo3nfsDvZZW74zKk1ochfH/fWSvGInYNCVvaBUbg3RZBbTp0j8yJCg==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4", + "@slack/web-api": "^7.10.0", + "@types/jsonwebtoken": "^9", + "@types/node": ">=18", + "jsonwebtoken": "^9" + }, + "engines": { + "node": ">=18", + "npm": ">=8.6.0" + } + }, + "node_modules/@slack/socket-mode": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.5.tgz", + "integrity": "sha512-VaapvmrAifeFLAFaDPfGhEwwunTKsI6bQhYzxRXw7BSujZUae5sANO76WqlVsLXuhVtCVrBWPiS2snAQR2RHJQ==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4", + "@slack/web-api": "^7.10.0", + "@types/node": ">=18", + "@types/ws": "^8", + "eventemitter3": "^5", + "ws": "^8" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.20.0.tgz", + "integrity": "sha512-PVF6P6nxzDMrzPC8fSCsnwaI+kF8YfEpxf3MqXmdyjyWTYsZQURpkK7WWUWvP5QpH55pB7zyYL9Qem/xSgc5VA==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.14.1.tgz", + "integrity": "sha512-RoygyteJeFswxDPJjUMESn9dldWVMD2xUcHHd9DenVavSfVC6FeVnSdDerOO7m8LLvw4Q132nQM4hX8JiF7dng==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/types": "^2.20.0", + "@types/node": ">=18.0.0", + "@types/retry": "0.12.0", + "axios": "^1.13.5", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.4", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/web-api/node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@slack/web-api/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@so-ric/colorspace": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", @@ -447,25 +722,96 @@ "text-hex": "1.0.x" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "^1" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.8", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", + "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, "node_modules/@types/node": { @@ -477,6 +823,54 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "<1" + } + }, + "node_modules/@types/serve-static/node_modules/@types/send": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -515,12 +909,50 @@ "node": ">=4" } }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -532,7 +964,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -576,6 +1007,12 @@ "node": ">=0.2.6" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -640,7 +1077,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "20 || >=22" @@ -661,6 +1098,45 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "license": "MIT" }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -671,7 +1147,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -680,6 +1156,21 @@ "node": "20 || >=22" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -727,6 +1218,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -935,6 +1435,52 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -974,7 +1520,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1121,7 +1667,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1139,7 +1684,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/define-data-property": { @@ -1168,6 +1713,15 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/discord-api-types": { "version": "0.38.37", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", @@ -1288,12 +1842,36 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -1333,11 +1911,32 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -1350,8 +1949,9 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.1.tgz", "integrity": "sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==", - "dev": true, + "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -1422,7 +2022,7 @@ "version": "9.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "@types/esrecurse": "^4.3.1", @@ -1441,7 +2041,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -1454,7 +2054,7 @@ "version": "11.1.1", "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.16.0", @@ -1472,7 +2072,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -1485,7 +2085,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -1498,7 +2098,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -1508,12 +2108,110 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -1545,7 +2243,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fecha": { @@ -1581,7 +2279,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -1590,11 +2288,32 @@ "node": ">=16.0.0" } }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -1611,7 +2330,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -1625,7 +2344,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/fn.name": { @@ -1689,6 +2408,24 @@ "node": ">=12.20.0" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs": { "version": "0.0.1-security", "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", @@ -1766,7 +2503,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -1785,6 +2522,18 @@ "process": "^0.11.10" } }, + "node_modules/globals": { + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", + "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -1850,6 +2599,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1906,6 +2670,32 @@ "entities": "^2.0.0" } }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-errors/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", @@ -1931,11 +2721,26 @@ "npm": ">=5.3.0" } }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -1973,11 +2778,27 @@ } } }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -2002,11 +2823,26 @@ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "license": "ISC" }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2016,7 +2852,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -2025,6 +2861,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -2053,7 +2895,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/isstream": { @@ -2062,6 +2904,18 @@ "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -2072,7 +2926,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-schema": { @@ -2110,7 +2964,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-stringify-safe": { @@ -2128,6 +2982,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, "node_modules/jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -2143,11 +3019,32 @@ "node": ">=0.6.0" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -2163,7 +3060,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -2177,7 +3074,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -2231,6 +3128,42 @@ "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -2243,6 +3176,12 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lodash.pick": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", @@ -2327,6 +3266,27 @@ "node": ">= 0.4" } }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2361,7 +3321,7 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -2408,9 +3368,18 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -2490,6 +3459,18 @@ "node": "*" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -2499,6 +3480,27 @@ "node": ">= 0.4" } }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -2528,7 +3530,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -2542,11 +3544,20 @@ "node": ">= 0.8.0" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -2562,7 +3573,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -2574,6 +3585,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path": { "version": "0.12.7", "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", @@ -2588,7 +3667,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -2598,12 +3677,22 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -2614,7 +3703,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -2662,6 +3751,25 @@ "node": ">=12.0.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -2704,6 +3812,30 @@ "node": ">=14.18.0" } }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -2724,6 +3856,7 @@ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", "license": "Apache-2.0", + "peer": true, "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -2784,6 +3917,40 @@ "request": "^2.34" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2819,6 +3986,88 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -2836,11 +4085,17 @@ "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -2853,12 +4108,84 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/snoowrap": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/snoowrap/-/snoowrap-1.23.0.tgz", @@ -2927,6 +4254,15 @@ "node": "*" } }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", @@ -2957,6 +4293,18 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/sylvester": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/sylvester/-/sylvester-0.0.12.tgz", @@ -2971,6 +4319,15 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -3011,6 +4368,15 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "license": "MIT", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -3033,7 +4399,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -3042,6 +4408,45 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/ultron": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", @@ -3092,6 +4497,15 @@ "node": ">=0.8.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3137,6 +4551,15 @@ "uuid": "bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -3180,7 +4603,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -3250,7 +4673,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3292,6 +4715,12 @@ "node": ">=0.4.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.18.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", @@ -3326,7 +4755,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index fc51264..06f7ba7 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,13 @@ { "dependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^10.0.1", + "@slack/bolt": "^4.1.1", "8ball": "^1.0.6", "chrono-node": "^2.9.0", "dammit": "^0.5.1", "discord.js": "^14.25.1", + "globals": "^17.3.0", "html-entities": "^2.6.0", "image-search-engine": "^1.2.1", "lowdb": "^7.0.1", @@ -19,7 +23,7 @@ }, "name": "ava", "version": "1.0.0", - "description": "OSS Ava discord bot", + "description": "OSS Ava discord and slack bot", "main": "./index.js", "type": "module", "devDependencies": { @@ -30,6 +34,7 @@ "lint": "eslint .", "fix-lint": "eslint . --fix", "start": "node --no-deprecation ./index.js", + "start:slack": "node --no-deprecation ./slack.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -38,6 +43,7 @@ }, "keywords": [ "discord", + "slack", "bot" ], "author": "circa10a", diff --git a/slack.js b/slack.js new file mode 100644 index 0000000..47f0e75 --- /dev/null +++ b/slack.js @@ -0,0 +1,117 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import pkg from '@slack/bolt'; +const { App } = pkg; + +import logger from './lib/logger/logger.js'; +import { commandsDir, dbDir, avaPrefix } from './config/config.js'; +import { initDB } from './lib/db/db.js'; +import { SlackMessageAdapter } from './lib/adapters/slack.js'; +import { sleep } from './lib/utils/utils.js'; + +// Remindme is a special case where we need to pass in the db +import remindMe from './commands/remindme.js'; + +const { AVA_SLACK_BOT_TOKEN, AVA_SLACK_APP_TOKEN, AVA_SLACK_SIGNING_SECRET, AVA_ENABLE_REMINDERS } = process.env; + +logger.info(`Node version: ${process.version}`); + +if (!AVA_SLACK_BOT_TOKEN || !AVA_SLACK_APP_TOKEN) { + logger.error('Missing AVA_SLACK_BOT_TOKEN or AVA_SLACK_APP_TOKEN environment variables'); + process.exit(1); +} + +const app = new App({ + token: AVA_SLACK_BOT_TOKEN, + appToken: AVA_SLACK_APP_TOKEN, + signingSecret: AVA_SLACK_SIGNING_SECRET || '', + socketMode: true, +}); + +const startSlackReminderDaemon = async (opts = {}) => { + const { db } = opts; + const checkInterval = 300000; // 5m + for (;;) { + await sleep(checkInterval); + + await db.read(); + const reminders = db.data.reminders; + + for (let i = 0; i < reminders.length; i++) { + const reminder = reminders[i]; + const remindTime = Date.parse(reminder.remindTime); + const now = new Date(); + + if (remindTime < now) { + try { + logger.debug(`Sending reminder to ${reminder.user}`); + await app.client.chat.postMessage({ + channel: reminder.channelId, + thread_ts: reminder.messageId, + text: 'Here\'s your reminder', + }); + reminders.splice(i, 1); + await db.write(); + await sleep(100); + } catch (e) { + logger.error(`Reminder error: ${e}`); + if (e.data && e.data.error === 'channel_not_found') { + logger.error(`Unable to access channel for reminder from ${reminder.user}. Deleting...`); + reminders.splice(i, 1); + await db.write(); + } + } + } + } + } +}; + +(async () => { + // Dynamically load all command modules + const commands = []; + const eventFiles = fs.readdirSync(`./${commandsDir}`).filter(file => file.endsWith('.js') && !file.startsWith('remindme')); + + for (const file of eventFiles) { + const module = await import(`./${commandsDir}/${file}`); + commands.push(module.default); + } + + // Handle reminders if enabled + if (AVA_ENABLE_REMINDERS) { + const dbFile = path.join(dbDir, 'db.json'); + logger.info(`Reminders enabled. Initializing database at ${dbFile}`); + + try { + const db = await initDB({ file: dbFile }); + const remindMeCommand = remindMe({ db }); + commands.push(remindMeCommand); + + startSlackReminderDaemon({ db }).catch((e) => { + logger.error(e); + }); + } catch (e) { + logger.error(e); + process.exit(1); + } + } + + // Listen for messages starting with the ava prefix + app.message(new RegExp(`^${avaPrefix}`, 'i'), async ({ message, client }) => { + // Ignore bot messages to prevent loops + if (message.subtype === 'bot_message' || message.bot_id) { + return; + } + + const adapter = new SlackMessageAdapter({ event: message, client }); + for (const command of commands) { + try { + await command.execute(adapter); + } catch (e) { + logger.error(`Command execution error: ${e}`); + } + } + }); + + await app.start(); + logger.info('⚡️ Ava Slack bot is running!'); +})(); From 7134b1864fe5fb021f6887a6ea378c2bf318526f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:31:25 +0000 Subject: [PATCH 3/7] Move eslint peer deps to devDependencies Co-authored-by: circa10a <21261388+circa10a@users.noreply.github.com> --- package-lock.json | 142 ++++++++++++++++++++++++++-------------------- package.json | 8 +-- 2 files changed, 85 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f7c873..2994e0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,14 +9,11 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@eslint/eslintrc": "^3.3.3", - "@eslint/js": "^10.0.1", "@slack/bolt": "^4.1.1", "8ball": "^1.0.6", "chrono-node": "^2.9.0", "dammit": "^0.5.1", "discord.js": "^14.25.1", - "globals": "^17.3.0", "html-entities": "^2.6.0", "image-search-engine": "^1.2.1", "lowdb": "^7.0.1", @@ -31,8 +28,11 @@ "winston": "^3.19.0" }, "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^10.0.1", "eslint": "^10.0.1", - "eslint-plugin-unused-imports": "^4.4.1" + "eslint-plugin-unused-imports": "^4.4.1", + "globals": "^17.3.0" } }, "node_modules/@colors/colors": { @@ -189,7 +189,7 @@ "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -208,7 +208,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -221,7 +221,7 @@ "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -231,7 +231,7 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.2.tgz", "integrity": "sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^3.0.2", @@ -246,7 +246,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.1.0" @@ -259,7 +259,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -272,6 +272,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -295,12 +296,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -311,6 +314,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -323,6 +327,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -340,6 +345,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -352,6 +358,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -364,6 +371,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, "license": "MIT", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -384,7 +392,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.2.tgz", "integrity": "sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -394,7 +402,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.1.0", @@ -408,7 +416,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -418,7 +426,7 @@ "version": "0.16.7", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -432,7 +440,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -446,7 +454,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -745,14 +753,14 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -789,7 +797,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/jsonwebtoken": { @@ -951,6 +959,7 @@ "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, "license": "MIT", "peer": true, "bin": { @@ -964,6 +973,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -1011,6 +1021,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/asn1": { @@ -1077,7 +1088,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": "20 || >=22" @@ -1147,7 +1158,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -1222,6 +1233,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1439,6 +1451,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/content-disposition": { @@ -1520,7 +1533,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1684,7 +1697,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/define-data-property": { @@ -1936,7 +1949,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1949,7 +1962,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.1.tgz", "integrity": "sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==", - "devOptional": true, + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -2022,7 +2035,7 @@ "version": "9.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.1.tgz", "integrity": "sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "@types/esrecurse": "^4.3.1", @@ -2041,7 +2054,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -2054,7 +2067,7 @@ "version": "11.1.1", "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.16.0", @@ -2072,7 +2085,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -2085,7 +2098,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -2098,7 +2111,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -2108,7 +2121,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -2243,7 +2256,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/fecha": { @@ -2279,7 +2292,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -2313,7 +2326,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -2330,7 +2343,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -2344,7 +2357,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/fn.name": { @@ -2503,7 +2516,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -2526,6 +2539,7 @@ "version": "17.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2741,6 +2755,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -2782,6 +2797,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -2798,7 +2814,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -2842,7 +2858,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2852,7 +2868,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -2895,7 +2911,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/isstream": { @@ -2908,6 +2924,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -2926,7 +2943,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-schema": { @@ -2964,7 +2981,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-stringify-safe": { @@ -3044,7 +3061,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -3060,7 +3077,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -3074,7 +3091,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -3321,7 +3338,7 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", - "devOptional": true, + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -3368,7 +3385,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -3530,7 +3547,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -3557,7 +3574,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -3573,7 +3590,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -3636,6 +3653,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -3667,7 +3685,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3677,7 +3695,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3703,7 +3721,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -3921,6 +3939,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -4095,7 +4114,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -4108,7 +4127,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4297,6 +4316,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4399,7 +4419,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -4603,7 +4623,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -4673,7 +4693,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4755,7 +4775,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 06f7ba7..065fb60 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,10 @@ { "dependencies": { - "@eslint/eslintrc": "^3.3.3", - "@eslint/js": "^10.0.1", "@slack/bolt": "^4.1.1", "8ball": "^1.0.6", "chrono-node": "^2.9.0", "dammit": "^0.5.1", "discord.js": "^14.25.1", - "globals": "^17.3.0", "html-entities": "^2.6.0", "image-search-engine": "^1.2.1", "lowdb": "^7.0.1", @@ -27,8 +24,11 @@ "main": "./index.js", "type": "module", "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^10.0.1", "eslint": "^10.0.1", - "eslint-plugin-unused-imports": "^4.4.1" + "eslint-plugin-unused-imports": "^4.4.1", + "globals": "^17.3.0" }, "scripts": { "lint": "eslint .", From 61de5ec28abee06c3bb4254d71294e5b8743d918 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 17:16:44 +0000 Subject: [PATCH 4/7] Add Discord adapter, refactor to symmetric adapter pattern, pre-compile regex, fix lint errors - Create lib/adapters/discord.js with DiscordMessageAdapter - Refactor index.js to use adapter pattern matching slack.js architecture - Pre-compile regex in both slack.js and index.js (no per-message compilation) - Remove Events import from all 25 command files (decouple from discord.js) - Fix 6 pre-existing no-useless-assignment lint errors in commands and lib files Co-authored-by: circa10a <21261388+circa10a@users.noreply.github.com> --- commands/8ball.js | 3 -- commands/bored.js | 3 -- commands/brewery.js | 4 +-- commands/catfact.js | 3 -- commands/coffee.js | 4 +-- commands/compliment.js | 3 -- commands/contribute.js | 3 -- commands/emoji.js | 3 -- commands/floridaman.js | 5 +-- commands/fuck.js | 3 -- commands/gcp.js | 3 -- commands/help.js | 4 +-- commands/hp.js | 6 ++-- commands/insult.js | 3 -- commands/java.js | 3 -- commands/karen.js | 6 ++-- commands/meme.js | 6 ++-- commands/mock.js | 3 -- commands/recipe.js | 3 -- commands/remindme.js | 3 -- commands/takemehome.js | 3 -- commands/tldr.js | 3 -- commands/video.js | 3 -- commands/whiteclaw.js | 3 -- commands/yeet.js | 3 -- index.js | 68 +++++++++++++++++++++------------------ lib/adapters/discord.js | 23 +++++++++++++ lib/db/db.js | 4 +-- lib/reddit/submissions.js | 2 +- slack.js | 5 ++- 30 files changed, 77 insertions(+), 114 deletions(-) create mode 100644 lib/adapters/discord.js diff --git a/commands/8ball.js b/commands/8ball.js index c768f92..cbf19a3 100644 --- a/commands/8ball.js +++ b/commands/8ball.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import eightball from '8ball'; import { messageForAva , splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; import dammit from 'dammit'; @@ -7,8 +6,6 @@ const command = getFileName(import.meta.url); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/bored.js b/commands/bored.js index 77f8d19..861fe33 100644 --- a/commands/bored.js +++ b/commands/bored.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import fetch from 'node-fetch'; import { messageForAva, splitArgs, getFileName} from '../lib/utils/utils.js'; @@ -8,8 +7,6 @@ const boredEndpoint = 'https://boredapi.com/api/activity'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/brewery.js b/commands/brewery.js index 7ea43e8..c856422 100644 --- a/commands/brewery.js +++ b/commands/brewery.js @@ -1,4 +1,4 @@ -import { EmbedBuilder, Events } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; import fetch from 'node-fetch'; import { embedColor } from '../config/config.js'; import { randomItemFromArray, messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; @@ -10,8 +10,6 @@ const beerThumbnail = 'https://i.imgur.com/6ZKnMaw.jpg'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/catfact.js b/commands/catfact.js index aff752d..bc8b631 100644 --- a/commands/catfact.js +++ b/commands/catfact.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import fetch from 'node-fetch'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; @@ -8,8 +7,6 @@ const catFactEndpoint = 'https://catfact.ninja/fact'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/coffee.js b/commands/coffee.js index 79a735d..361b026 100644 --- a/commands/coffee.js +++ b/commands/coffee.js @@ -1,5 +1,5 @@ import { default as wiki } from 'wikijs'; -import { EmbedBuilder, Events } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; import { randomItemFromArray, messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; import { embedColor } from '../config/config.js'; @@ -7,8 +7,6 @@ const command = getFileName(import.meta.url); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/compliment.js b/commands/compliment.js index f214f9d..ca701b9 100644 --- a/commands/compliment.js +++ b/commands/compliment.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import fetch from 'node-fetch'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; @@ -8,8 +7,6 @@ const complimentEndpoint = 'https://complimentr.com/api'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/contribute.js b/commands/contribute.js index 281196f..ffb2ada 100644 --- a/commands/contribute.js +++ b/commands/contribute.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,8 +6,6 @@ const projectURL = 'https://github.com/circa10a/ava'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute(message) { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/emoji.js b/commands/emoji.js index 178a4b4..86539cc 100644 --- a/commands/emoji.js +++ b/commands/emoji.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import imageFinder from 'image-search-engine'; import { messageForAva, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; @@ -6,8 +5,6 @@ const command = getFileName(import.meta.url); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/floridaman.js b/commands/floridaman.js index 8c085a2..c604590 100644 --- a/commands/floridaman.js +++ b/commands/floridaman.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; import { getRandomSubmission } from '../lib/reddit/submissions.js'; @@ -8,8 +7,6 @@ const subreddit = 'FloridaMan'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { @@ -19,7 +16,7 @@ export default { const userCmd = args[1]; if (userCmd === command) { - let randomSubmission = {}; + let randomSubmission; try{ randomSubmission = await getRandomSubmission({subreddit}); } catch(e) { diff --git a/commands/fuck.js b/commands/fuck.js index 6df3ad1..79084f3 100644 --- a/commands/fuck.js +++ b/commands/fuck.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import fetch from 'node-fetch'; import { randomItemFromArray, messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; import { avaPrefix } from '../config/config.js'; @@ -21,8 +20,6 @@ const avaInsults = [ export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async (message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/gcp.js b/commands/gcp.js index 54ef88d..30f741c 100644 --- a/commands/gcp.js +++ b/commands/gcp.js @@ -1,12 +1,9 @@ -import { Events } from 'discord.js'; import { messageForAva, splitArgs, getFileName} from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute(message) { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/help.js b/commands/help.js index db4059f..a6ac3b4 100644 --- a/commands/help.js +++ b/commands/help.js @@ -1,4 +1,4 @@ -import { EmbedBuilder, Events } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; import * as fs from 'fs'; import { commandsDir, embedColor } from '../config/config.js'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; @@ -10,8 +10,6 @@ const availableCommands = eventFiles.map(event => event.replace('.js', '')); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute(message) { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/hp.js b/commands/hp.js index df570a0..832b4f8 100644 --- a/commands/hp.js +++ b/commands/hp.js @@ -1,4 +1,4 @@ -import { EmbedBuilder, Events } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; import { embedColor } from '../config/config.js'; import { getRandomSubmissionWithImage } from '../lib/reddit/submissions.js'; @@ -10,8 +10,6 @@ const subreddit = 'HarryPotterMemes'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async (message) => { // Ensure message is intended for ava if (!messageForAva(message)) { @@ -21,7 +19,7 @@ export default { const userCmd = args[1]; if (userCmd === command) { - let randomSubmission = {}; + let randomSubmission; try { randomSubmission = await getRandomSubmissionWithImage({subreddit}); } catch(e) { diff --git a/commands/insult.js b/commands/insult.js index fa03abc..3208f2b 100644 --- a/commands/insult.js +++ b/commands/insult.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { decode } from 'html-entities'; import fetch from 'node-fetch'; import { messageForAva, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; @@ -9,8 +8,6 @@ const insultEndpoint = 'https://evilinsult.com/generate_insult.php?lang=en&type= export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/java.js b/commands/java.js index 2f46938..1b23c67 100644 --- a/commands/java.js +++ b/commands/java.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,8 +6,6 @@ const javaImage = 'https://i.redd.it/o4w97sa7iidz.jpg'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute(message) { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/karen.js b/commands/karen.js index b93c827..c06ac07 100644 --- a/commands/karen.js +++ b/commands/karen.js @@ -1,4 +1,4 @@ -import { EmbedBuilder, Events } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; import { embedColor } from '../config/config.js'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; @@ -10,8 +10,6 @@ const subreddit = 'FuckYouKaren'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async (message) => { // Ensure message is intended for ava if (!messageForAva(message)) { @@ -21,7 +19,7 @@ export default { const userCmd = args[1]; if (userCmd === command) { - let randomSubmission = {}; + let randomSubmission; try { randomSubmission = await getRandomSubmissionWithImage({subreddit}); } catch(e) { diff --git a/commands/meme.js b/commands/meme.js index 89f5314..5a81dc8 100644 --- a/commands/meme.js +++ b/commands/meme.js @@ -1,4 +1,4 @@ -import { EmbedBuilder, Events } from 'discord.js'; +import { EmbedBuilder } from 'discord.js'; import { embedColor } from '../config/config.js'; import { getRandomSubmissionWithImage } from '../lib/reddit/submissions.js'; @@ -10,8 +10,6 @@ const subreddit = 'dankmemes'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async (message) => { // Ensure message is intended for ava if (!messageForAva(message)) { @@ -21,7 +19,7 @@ export default { const userCmd = args[1]; if (userCmd === command) { - let randomSubmission = {}; + let randomSubmission; try { randomSubmission = await getRandomSubmissionWithImage({subreddit}); } catch(e) { diff --git a/commands/mock.js b/commands/mock.js index 2700825..971c24b 100644 --- a/commands/mock.js +++ b/commands/mock.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { messageForAva, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -13,8 +12,6 @@ const mockText = (str) => { export default { commandName: command, - name: Events.MessageCreate, - once: false, execute(message) { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/recipe.js b/commands/recipe.js index 8d5d5f7..2137475 100644 --- a/commands/recipe.js +++ b/commands/recipe.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { parse } from 'node-html-parser'; import { randomItemFromArray, messageForAva, getBody, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; @@ -9,8 +8,6 @@ const itemsHTMLClass ='#schema-lifestyle_1-0'; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/remindme.js b/commands/remindme.js index 9593d9d..af7b348 100644 --- a/commands/remindme.js +++ b/commands/remindme.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import * as chrono from 'chrono-node'; import { messageForAva, splitArgs, getAllArgsAsStr } from '../lib/utils/utils.js'; @@ -7,8 +6,6 @@ const command = 'remindme'; const remindMe = (opts = {}) => { return { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/takemehome.js b/commands/takemehome.js index 57de00b..a19d2f6 100644 --- a/commands/takemehome.js +++ b/commands/takemehome.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { sleep , messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,8 +6,6 @@ const wait = 2000; export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/tldr.js b/commands/tldr.js index 9337bf1..59e26b1 100644 --- a/commands/tldr.js +++ b/commands/tldr.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import {SummarizerManager as summarizerManager} from 'node-summarizer'; import extractor from 'unfluff'; import { messageForAva, splitArgs, getBody, stringIsAValidUrl, getFileName } from '../lib/utils/utils.js'; @@ -9,8 +8,6 @@ const command = getFileName(import.meta.url); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/video.js b/commands/video.js index d7b3e1b..cb856b4 100644 --- a/commands/video.js +++ b/commands/video.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import usetube from 'usetube'; import { randomItemFromArray, messageForAva, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; @@ -6,8 +5,6 @@ const command = getFileName(import.meta.url); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute: async(message) => { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/whiteclaw.js b/commands/whiteclaw.js index 86982f9..e39f5c7 100644 --- a/commands/whiteclaw.js +++ b/commands/whiteclaw.js @@ -1,12 +1,9 @@ -import { Events } from 'discord.js'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); export default { commandName: command, - name: Events.MessageCreate, - once: false, execute(message) { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/commands/yeet.js b/commands/yeet.js index 6491098..cb879bb 100644 --- a/commands/yeet.js +++ b/commands/yeet.js @@ -1,4 +1,3 @@ -import { Events } from 'discord.js'; import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,8 +6,6 @@ const yeetImage = 'https://i.kym-cdn.com/entries/icons/original/000/031/544/cove export default { commandName: command, - name: Events.MessageCreate, - once: false, execute(message) { // Ensure message is intended for ava if (!messageForAva(message)) { diff --git a/index.js b/index.js index 474fdea..753d252 100644 --- a/index.js +++ b/index.js @@ -3,8 +3,9 @@ import * as path from 'path'; import { Client, Events, GatewayIntentBits } from 'discord.js'; import logger from './lib/logger/logger.js'; -import { commandsDir, dbDir } from './config/config.js'; +import { commandsDir, dbDir, avaPrefix } from './config/config.js'; import { initDB, startReminderDaemon } from './lib/db/db.js'; +import { DiscordMessageAdapter } from './lib/adapters/discord.js'; const { AVA_DISCORD_TOKEN, AVA_ENABLE_REMINDERS } = process.env; // Remindme is a special case where we need to pass in the db @@ -29,36 +30,28 @@ client.once(Events.ClientReady, c => { logger.info(`Ready! Logged in as ${c.user.tag}`); }); -// Dynamic imports of modules/commands so that we don't need to statically define them. -const eventFiles = fs.readdirSync(`./${commandsDir}`).filter(file => file.endsWith('.js') && !file.startsWith('remindme')); -for (const file of eventFiles) { - import(`./${commandsDir}/${file}`).then((module) => { - const event = module.default; - if (event.once) { - client.once(event.name, (...args) => event.execute(...args)); - } else { - client.on(event.name, (...args) => event.execute(...args)); - } - }); -} +// Pre-compile regex once +const avaPrefixRegex = new RegExp(`^${avaPrefix}`, 'i'); + +(async () => { + // Dynamic imports of modules/commands so that we don't need to statically define them. + const commands = []; + const eventFiles = fs.readdirSync(`./${commandsDir}`).filter(file => file.endsWith('.js') && !file.startsWith('remindme')); + for (const file of eventFiles) { + const module = await import(`./${commandsDir}/${file}`); + commands.push(module.default); + } -if (AVA_ENABLE_REMINDERS) { - const dbFile = path.join(dbDir, 'db.json'); + if (AVA_ENABLE_REMINDERS) { + const dbFile = path.join(dbDir, 'db.json'); - logger.info(`Reminders enabled. Initializing database at ${dbFile}`); - (async () => { + logger.info(`Reminders enabled. Initializing database at ${dbFile}`); const db = await initDB({ file: dbFile, }); - const remindMeEvent = remindMe({db}); - - // Start discord listener - if (remindMeEvent.once) { - client.once(remindMeEvent.name, (...args) => remindMeEvent.execute(...args)); - } else { - client.on(remindMeEvent.name, (...args) => remindMeEvent.execute(...args)); - } + const remindMeCommand = remindMe({db}); + commands.push(remindMeCommand); // Start reminder daemon startReminderDaemon({ @@ -66,11 +59,24 @@ if (AVA_ENABLE_REMINDERS) { checkForRemindersInterval: 300000, // 5m db: db }); - })().catch((e) => { - logger.error(e); - process.exit(1); - }); + } -} + // Listen for all messages and dispatch to commands via adapter + client.on(Events.MessageCreate, async (message) => { + if (!avaPrefixRegex.test(message.content)) return; + + const adapter = new DiscordMessageAdapter(message); + for (const command of commands) { + try { + await command.execute(adapter); + } catch (e) { + logger.error(`Command execution error: ${e}`); + } + } + }); -client.login(AVA_DISCORD_TOKEN); + client.login(AVA_DISCORD_TOKEN); +})().catch((e) => { + logger.error(e); + process.exit(1); +}); diff --git a/lib/adapters/discord.js b/lib/adapters/discord.js new file mode 100644 index 0000000..3cc7416 --- /dev/null +++ b/lib/adapters/discord.js @@ -0,0 +1,23 @@ +/** + * Adapts a Discord message to a common interface shared with the Slack adapter. + * This is a thin wrapper for architectural consistency — both platforms + * now use the same adapter pattern for dispatching commands. + */ +class DiscordMessageAdapter { + constructor(message) { + this._message = message; + this.content = message.content; + this.channelId = message.channelId; + this.guildId = message.guildId; + this.id = message.id; + this.createdTimestamp = message.createdTimestamp; + this.author = message.author; + this.channel = message.channel; + } + + async reply(content) { + return this._message.reply(content); + } +} + +export { DiscordMessageAdapter }; diff --git a/lib/db/db.js b/lib/db/db.js index 123d3ff..b965b71 100644 --- a/lib/db/db.js +++ b/lib/db/db.js @@ -46,8 +46,8 @@ const startReminderDaemon = async(opts = { const reminder = reminders[i]; const remindTime = Date.parse(reminder.remindTime); const now = new Date; - let channel = {}; - let message = {}; + let channel; + let message; // If remindtime has passed if (remindTime < now) { diff --git a/lib/reddit/submissions.js b/lib/reddit/submissions.js index 0d50345..290984a 100644 --- a/lib/reddit/submissions.js +++ b/lib/reddit/submissions.js @@ -13,7 +13,7 @@ const getRandomSubmission = async(opts = {}) => { return e; } - let randomSubmission = []; + let randomSubmission; try { const r = new Snoowrap(reddit); randomSubmission = await r.getSubreddit(subreddit).getHot(); diff --git a/slack.js b/slack.js index 47f0e75..69a77a5 100644 --- a/slack.js +++ b/slack.js @@ -28,6 +28,9 @@ const app = new App({ socketMode: true, }); +// Pre-compile regex once +const avaPrefixRegex = new RegExp(`^${avaPrefix}`, 'i'); + const startSlackReminderDaemon = async (opts = {}) => { const { db } = opts; const checkInterval = 300000; // 5m @@ -96,7 +99,7 @@ const startSlackReminderDaemon = async (opts = {}) => { } // Listen for messages starting with the ava prefix - app.message(new RegExp(`^${avaPrefix}`, 'i'), async ({ message, client }) => { + app.message(avaPrefixRegex, async ({ message, client }) => { // Ignore bot messages to prevent loops if (message.subtype === 'bot_message' || message.bot_id) { return; From 0672c98ce4f002b4bf0e6b2ec0b6ade9f49f4911 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:47:28 +0000 Subject: [PATCH 5/7] Unify entry point: single `npm start` runs Discord, Slack, or both simultaneously - Refactor index.js to auto-detect platforms based on which env vars are set - Extract platform logic into lib/platforms/discord.js and lib/platforms/slack.js - Remove separate slack.js entry point and start:slack script - Docker image works for all configs via env vars alone (no CMD override) - Update README and docs to show unified usage Co-authored-by: circa10a <21261388+circa10a@users.noreply.github.com> --- README.md | 27 ++++----- docs/getting-started.md | 28 ++++++++- index.js | 74 ++++++++---------------- lib/platforms/discord.js | 53 +++++++++++++++++ lib/platforms/slack.js | 88 ++++++++++++++++++++++++++++ package-lock.json | 3 - package.json | 1 - slack.js | 120 --------------------------------------- 8 files changed, 205 insertions(+), 189 deletions(-) create mode 100644 lib/platforms/discord.js create mode 100644 lib/platforms/slack.js delete mode 100644 slack.js diff --git a/README.md b/README.md index e83888b..aa617aa 100644 --- a/README.md +++ b/README.md @@ -13,21 +13,23 @@ A discord and slack implementation of the famous ava bot ## Usage -### Discord +Ava starts whichever platforms have tokens configured. Set Discord tokens, Slack tokens, or both to run them simultaneously. ```bash +# Discord only export AVA_DISCORD_TOKEN= -npm i -npm start -``` -### Slack +# Slack only +export AVA_SLACK_BOT_TOKEN= +export AVA_SLACK_APP_TOKEN= -```bash +# Both platforms simultaneously +export AVA_DISCORD_TOKEN= export AVA_SLACK_BOT_TOKEN= export AVA_SLACK_APP_TOKEN= + npm i -npm run start:slack +npm start ``` ## Config @@ -79,16 +81,15 @@ npm run start:slack ## Docker -### Discord - ```bash +# Discord only docker run -e AVA_DISCORD_TOKEN="" circa10a/ava -``` -### Slack +# Slack only +docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava -```bash -docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava npm run start:slack +# Both platforms simultaneously +docker run -e AVA_DISCORD_TOKEN="" -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava ``` ## Contribution diff --git a/docs/getting-started.md b/docs/getting-started.md index ba40910..70b88de 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -56,12 +56,14 @@ docker run -e AVA_DISCORD_TOKEN="" circa10a/ava #### Non-Docker ```shell +export AVA_DISCORD_TOKEN= npm run start ``` You should see a message when the server is up and running. ``` -[INFO] Ready! Logged in as ... +[INFO] Discord platform enabled +[INFO] Discord ready! Logged in as ... ``` ### Verify Ava (Discord) @@ -118,7 +120,7 @@ Invite the bot to a channel by typing `/invite @Ava` in the channel. #### Docker ```shell -docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava npm run start:slack +docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava ``` #### Non-Docker @@ -126,11 +128,12 @@ docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN=" export AVA_SLACK_APP_TOKEN= -npm run start:slack +npm run start ``` You should see a message when the server is up and running. ``` +[INFO] Slack platform enabled [INFO] ⚡️ Ava Slack bot is running! ``` @@ -140,6 +143,25 @@ Go to the Slack channel where you invited the bot and type `ava help`. If everyt --- +## Running Both Platforms Simultaneously + +Ava can run Discord and Slack at the same time. Simply provide tokens for both platforms: + +```shell +export AVA_DISCORD_TOKEN= +export AVA_SLACK_BOT_TOKEN= +export AVA_SLACK_APP_TOKEN= +npm run start +``` + +Or with Docker: + +```shell +docker run -e AVA_DISCORD_TOKEN="" -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava +``` + +--- + ## Closing If Ava returned all the commands then congratulations. You now have a local test server and an Ava test bot to verify new commands. diff --git a/index.js b/index.js index 753d252..9abce89 100644 --- a/index.js +++ b/index.js @@ -1,38 +1,26 @@ import * as fs from 'fs'; import * as path from 'path'; -import { Client, Events, GatewayIntentBits } from 'discord.js'; import logger from './lib/logger/logger.js'; -import { commandsDir, dbDir, avaPrefix } from './config/config.js'; -import { initDB, startReminderDaemon } from './lib/db/db.js'; -import { DiscordMessageAdapter } from './lib/adapters/discord.js'; -const { AVA_DISCORD_TOKEN, AVA_ENABLE_REMINDERS } = process.env; +import { commandsDir, dbDir } from './config/config.js'; +import { initDB } from './lib/db/db.js'; +import { startDiscord } from './lib/platforms/discord.js'; +import { startSlack } from './lib/platforms/slack.js'; -// Remindme is a special case where we need to pass in the db import remindMe from './commands/remindme.js'; +const { AVA_DISCORD_TOKEN, AVA_SLACK_BOT_TOKEN, AVA_SLACK_APP_TOKEN, AVA_ENABLE_REMINDERS } = process.env; + logger.info(`Node version: ${process.version}`); -if (!AVA_DISCORD_TOKEN) { - logger.error('Missing AVA_DISCORD_TOKEN environment variable'); +const discordEnabled = !!AVA_DISCORD_TOKEN; +const slackEnabled = !!(AVA_SLACK_BOT_TOKEN && AVA_SLACK_APP_TOKEN); + +if (!discordEnabled && !slackEnabled) { + logger.error('No platform configured. Set AVA_DISCORD_TOKEN for Discord and/or AVA_SLACK_BOT_TOKEN + AVA_SLACK_APP_TOKEN for Slack.'); process.exit(1); } -// Setup client -const client = new Client({ intents: [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.MessageContent, -]}); - -client.setMaxListeners(0); -client.once(Events.ClientReady, c => { - logger.info(`Ready! Logged in as ${c.user.tag}`); -}); - -// Pre-compile regex once -const avaPrefixRegex = new RegExp(`^${avaPrefix}`, 'i'); - (async () => { // Dynamic imports of modules/commands so that we don't need to statically define them. const commands = []; @@ -42,40 +30,28 @@ const avaPrefixRegex = new RegExp(`^${avaPrefix}`, 'i'); commands.push(module.default); } + let db = null; if (AVA_ENABLE_REMINDERS) { const dbFile = path.join(dbDir, 'db.json'); - logger.info(`Reminders enabled. Initializing database at ${dbFile}`); - const db = await initDB({ - file: dbFile, - }); - - const remindMeCommand = remindMe({db}); + db = await initDB({ file: dbFile }); + const remindMeCommand = remindMe({ db }); commands.push(remindMeCommand); - - // Start reminder daemon - startReminderDaemon({ - client: client, - checkForRemindersInterval: 300000, // 5m - db: db - }); } - // Listen for all messages and dispatch to commands via adapter - client.on(Events.MessageCreate, async (message) => { - if (!avaPrefixRegex.test(message.content)) return; + const startups = []; - const adapter = new DiscordMessageAdapter(message); - for (const command of commands) { - try { - await command.execute(adapter); - } catch (e) { - logger.error(`Command execution error: ${e}`); - } - } - }); + if (discordEnabled) { + logger.info('Discord platform enabled'); + startups.push(startDiscord({ commands, db })); + } + + if (slackEnabled) { + logger.info('Slack platform enabled'); + startups.push(startSlack({ commands, db })); + } - client.login(AVA_DISCORD_TOKEN); + await Promise.all(startups); })().catch((e) => { logger.error(e); process.exit(1); diff --git a/lib/platforms/discord.js b/lib/platforms/discord.js new file mode 100644 index 0000000..0f3851d --- /dev/null +++ b/lib/platforms/discord.js @@ -0,0 +1,53 @@ +import { Client, Events, GatewayIntentBits } from 'discord.js'; + +import logger from '../logger/logger.js'; +import { avaPrefix } from '../../config/config.js'; +import { startReminderDaemon } from '../db/db.js'; +import { DiscordMessageAdapter } from '../adapters/discord.js'; + +// Pre-compile regex once +const avaPrefixRegex = new RegExp(`^${avaPrefix}`, 'i'); + +const startDiscord = async ({ commands, db }) => { + const { AVA_DISCORD_TOKEN } = process.env; + + if (!AVA_DISCORD_TOKEN) { + throw new Error('Missing AVA_DISCORD_TOKEN environment variable'); + } + + const client = new Client({ intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ]}); + + client.setMaxListeners(0); + client.once(Events.ClientReady, c => { + logger.info(`Discord ready! Logged in as ${c.user.tag}`); + }); + + if (db) { + startReminderDaemon({ + client: client, + checkForRemindersInterval: 300000, + db: db + }); + } + + client.on(Events.MessageCreate, async (message) => { + if (!avaPrefixRegex.test(message.content)) return; + + const adapter = new DiscordMessageAdapter(message); + for (const command of commands) { + try { + await command.execute(adapter); + } catch (e) { + logger.error(`Command execution error: ${e}`); + } + } + }); + + await client.login(AVA_DISCORD_TOKEN); +}; + +export { startDiscord }; diff --git a/lib/platforms/slack.js b/lib/platforms/slack.js new file mode 100644 index 0000000..5b14fa6 --- /dev/null +++ b/lib/platforms/slack.js @@ -0,0 +1,88 @@ +import pkg from '@slack/bolt'; +const { App } = pkg; + +import logger from '../logger/logger.js'; +import { avaPrefix } from '../../config/config.js'; +import { SlackMessageAdapter } from '../adapters/slack.js'; +import { sleep } from '../utils/utils.js'; + +// Pre-compile regex once +const avaPrefixRegex = new RegExp(`^${avaPrefix}`, 'i'); + +const startSlackReminderDaemon = async ({ app, db }) => { + const checkInterval = 300000; // 5m + for (;;) { + await sleep(checkInterval); + + await db.read(); + const reminders = db.data.reminders; + + for (let i = 0; i < reminders.length; i++) { + const reminder = reminders[i]; + const remindTime = Date.parse(reminder.remindTime); + const now = new Date(); + + if (remindTime < now) { + try { + logger.debug(`Sending reminder to ${reminder.user}`); + await app.client.chat.postMessage({ + channel: reminder.channelId, + thread_ts: reminder.messageId, + text: 'Here\'s your reminder', + }); + reminders.splice(i, 1); + await db.write(); + await sleep(100); + } catch (e) { + logger.error(`Reminder error: ${e}`); + if (e.data && e.data.error === 'channel_not_found') { + logger.error(`Unable to access channel for reminder from ${reminder.user}. Deleting...`); + reminders.splice(i, 1); + await db.write(); + } + } + } + } + } +}; + +const startSlack = async ({ commands, db }) => { + const { AVA_SLACK_BOT_TOKEN, AVA_SLACK_APP_TOKEN, AVA_SLACK_SIGNING_SECRET } = process.env; + + if (!AVA_SLACK_BOT_TOKEN || !AVA_SLACK_APP_TOKEN) { + throw new Error('Missing AVA_SLACK_BOT_TOKEN or AVA_SLACK_APP_TOKEN environment variables'); + } + + const app = new App({ + token: AVA_SLACK_BOT_TOKEN, + appToken: AVA_SLACK_APP_TOKEN, + signingSecret: AVA_SLACK_SIGNING_SECRET || '', + socketMode: true, + }); + + if (db) { + startSlackReminderDaemon({ app, db }).catch((e) => { + logger.error(e); + }); + } + + app.message(avaPrefixRegex, async ({ message, client }) => { + if (message.subtype === 'bot_message' || message.bot_id) { + return; + } + + const adapter = new SlackMessageAdapter({ event: message, client }); + for (const command of commands) { + try { + await command.execute(adapter); + } catch (e) { + logger.error(`Command execution error: ${e}`); + } + } + }); + + await app.start(); + logger.info('⚡️ Ava Slack bot is running!'); +}; + +export { startSlack }; diff --git a/package-lock.json b/package-lock.json index 2994e0a..33777f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -961,7 +961,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1964,7 +1963,6 @@ "integrity": "sha512-20MV9SUdeN6Jd84xESsKhRly+/vxI+hwvpBMA93s+9dAcjdCuCojn4IqUGS3lvVaqjVYGYHSRMCpeFtF2rQYxQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -3874,7 +3872,6 @@ "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", "license": "Apache-2.0", - "peer": true, "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", diff --git a/package.json b/package.json index 065fb60..d510379 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "lint": "eslint .", "fix-lint": "eslint . --fix", "start": "node --no-deprecation ./index.js", - "start:slack": "node --no-deprecation ./slack.js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { diff --git a/slack.js b/slack.js deleted file mode 100644 index 69a77a5..0000000 --- a/slack.js +++ /dev/null @@ -1,120 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import pkg from '@slack/bolt'; -const { App } = pkg; - -import logger from './lib/logger/logger.js'; -import { commandsDir, dbDir, avaPrefix } from './config/config.js'; -import { initDB } from './lib/db/db.js'; -import { SlackMessageAdapter } from './lib/adapters/slack.js'; -import { sleep } from './lib/utils/utils.js'; - -// Remindme is a special case where we need to pass in the db -import remindMe from './commands/remindme.js'; - -const { AVA_SLACK_BOT_TOKEN, AVA_SLACK_APP_TOKEN, AVA_SLACK_SIGNING_SECRET, AVA_ENABLE_REMINDERS } = process.env; - -logger.info(`Node version: ${process.version}`); - -if (!AVA_SLACK_BOT_TOKEN || !AVA_SLACK_APP_TOKEN) { - logger.error('Missing AVA_SLACK_BOT_TOKEN or AVA_SLACK_APP_TOKEN environment variables'); - process.exit(1); -} - -const app = new App({ - token: AVA_SLACK_BOT_TOKEN, - appToken: AVA_SLACK_APP_TOKEN, - signingSecret: AVA_SLACK_SIGNING_SECRET || '', - socketMode: true, -}); - -// Pre-compile regex once -const avaPrefixRegex = new RegExp(`^${avaPrefix}`, 'i'); - -const startSlackReminderDaemon = async (opts = {}) => { - const { db } = opts; - const checkInterval = 300000; // 5m - for (;;) { - await sleep(checkInterval); - - await db.read(); - const reminders = db.data.reminders; - - for (let i = 0; i < reminders.length; i++) { - const reminder = reminders[i]; - const remindTime = Date.parse(reminder.remindTime); - const now = new Date(); - - if (remindTime < now) { - try { - logger.debug(`Sending reminder to ${reminder.user}`); - await app.client.chat.postMessage({ - channel: reminder.channelId, - thread_ts: reminder.messageId, - text: 'Here\'s your reminder', - }); - reminders.splice(i, 1); - await db.write(); - await sleep(100); - } catch (e) { - logger.error(`Reminder error: ${e}`); - if (e.data && e.data.error === 'channel_not_found') { - logger.error(`Unable to access channel for reminder from ${reminder.user}. Deleting...`); - reminders.splice(i, 1); - await db.write(); - } - } - } - } - } -}; - -(async () => { - // Dynamically load all command modules - const commands = []; - const eventFiles = fs.readdirSync(`./${commandsDir}`).filter(file => file.endsWith('.js') && !file.startsWith('remindme')); - - for (const file of eventFiles) { - const module = await import(`./${commandsDir}/${file}`); - commands.push(module.default); - } - - // Handle reminders if enabled - if (AVA_ENABLE_REMINDERS) { - const dbFile = path.join(dbDir, 'db.json'); - logger.info(`Reminders enabled. Initializing database at ${dbFile}`); - - try { - const db = await initDB({ file: dbFile }); - const remindMeCommand = remindMe({ db }); - commands.push(remindMeCommand); - - startSlackReminderDaemon({ db }).catch((e) => { - logger.error(e); - }); - } catch (e) { - logger.error(e); - process.exit(1); - } - } - - // Listen for messages starting with the ava prefix - app.message(avaPrefixRegex, async ({ message, client }) => { - // Ignore bot messages to prevent loops - if (message.subtype === 'bot_message' || message.bot_id) { - return; - } - - const adapter = new SlackMessageAdapter({ event: message, client }); - for (const command of commands) { - try { - await command.execute(adapter); - } catch (e) { - logger.error(`Command execution error: ${e}`); - } - } - }); - - await app.start(); - logger.info('⚡️ Ava Slack bot is running!'); -})(); From f6acd5d1e0db695d46094af57b37f160aecb0efb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:48:49 +0000 Subject: [PATCH 6/7] Fix splice-while-iterating: iterate backwards in Slack reminder daemon Co-authored-by: circa10a <21261388+circa10a@users.noreply.github.com> --- lib/platforms/slack.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/platforms/slack.js b/lib/platforms/slack.js index 5b14fa6..05cf0bd 100644 --- a/lib/platforms/slack.js +++ b/lib/platforms/slack.js @@ -17,7 +17,7 @@ const startSlackReminderDaemon = async ({ app, db }) => { await db.read(); const reminders = db.data.reminders; - for (let i = 0; i < reminders.length; i++) { + for (let i = reminders.length - 1; i >= 0; i--) { const reminder = reminders[i]; const remindTime = Date.parse(reminder.remindTime); const now = new Date(); From ac60d33ead28d80e319e3e02404cecd6c472c166 Mon Sep 17 00:00:00 2001 From: circa10a Date: Tue, 3 Mar 2026 20:03:21 -0800 Subject: [PATCH 7/7] rewrite the whole fuckin thing --- .dockerignore | 3 + .github/workflows/docker.yaml | 10 +- .github/workflows/test.yaml | 7 +- Dockerfile | 7 +- commands/8ball.js | 46 +- commands/bored.js | 36 +- commands/brewery.js | 71 +- commands/catfact.js | 36 +- commands/coffee.js | 45 +- commands/compliment.js | 49 +- commands/contribute.js | 13 +- commands/emoji.js | 22 +- commands/floridaman.js | 31 +- commands/fuck.js | 82 +-- commands/gcp.js | 13 +- commands/help.js | 31 +- commands/hp.js | 48 +- commands/insult.js | 50 +- commands/java.js | 13 +- commands/karen.js | 48 +- commands/meme.js | 48 +- commands/mock.js | 28 +- commands/recipe.js | 71 +- commands/remindme.js | 59 +- commands/takemehome.js | 33 +- commands/tldr.js | 83 ++- commands/video.js | 36 +- commands/whiteclaw.js | 13 +- commands/yeet.js | 13 +- config/config.js | 17 +- docs/CONTRIBUTION.md | 24 +- docs/getting-started.md | 13 +- index.js | 9 +- lib/commands/redditEmbed.js | 35 + lib/db/db.js | 20 +- lib/platforms/discord.js | 11 +- lib/platforms/slack.js | 11 +- lib/reddit/submissions.js | 51 +- lib/utils/utils.js | 3 - lib/validations/reddit/creds.js | 11 - package-lock.json | 1223 ++----------------------------- package.json | 19 +- test/adapters/discord.test.js | 95 +++ test/adapters/slack.test.js | 235 ++++++ test/platforms/discord.test.js | 15 + test/platforms/slack.test.js | 18 + 46 files changed, 949 insertions(+), 1906 deletions(-) create mode 100644 lib/commands/redditEmbed.js delete mode 100644 lib/validations/reddit/creds.js create mode 100644 test/adapters/discord.test.js create mode 100644 test/adapters/slack.test.js create mode 100644 test/platforms/discord.test.js create mode 100644 test/platforms/slack.test.js diff --git a/.dockerignore b/.dockerignore index 1d84211..e02b65f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,9 @@ node_modules .eslintrc.json +eslint.config.mjs .gitignore *.md .github .do +test +docs diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index dbe9884..cfc582b 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -13,18 +13,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ github.repository_owner }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push ava - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: platforms: ${{ env.PLATFORMS }} push: true diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index ff9e38a..47510e9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,10 +9,13 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' - run: npm ci - run: npm run lint env: CI: true + - run: npm test diff --git a/Dockerfile b/Dockerfile index 5fbf4d3..f86b57d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ -FROM node +FROM node:22-alpine ENV NODE_ENV=production WORKDIR /usr/src/app +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev COPY . . -RUN npm install -CMD ["npm", "start"] \ No newline at end of file +CMD ["node", "--no-deprecation", "./index.js"] \ No newline at end of file diff --git a/commands/8ball.js b/commands/8ball.js index cbf19a3..cf5ab2b 100644 --- a/commands/8ball.js +++ b/commands/8ball.js @@ -1,30 +1,36 @@ -import eightball from '8ball'; -import { messageForAva , splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; -import dammit from 'dammit'; +import { randomItemFromArray, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); +const eightBallResponses = [ + 'It is certain', 'It is decidedly so', 'Without a doubt', + 'Yes definitely', 'You may rely on it', 'As I see it, yes', + 'Most likely', 'Outlook good', 'Yes', 'Signs point to yes', + 'Reply hazy try again', 'Ask again later', 'Better not tell you now', + 'Cannot predict now', 'Concentrate and ask again', + 'Don\'t count on it', 'My reply is no', 'My sources say no', + 'Outlook not so good', 'Very doubtful', +]; + +const dammitPhrases = [ + 'Dammit', 'Damn', 'Damnit', 'Well damn', 'Oh damn', + 'Son of a bitch', 'Hell', 'Crap', 'Shit', 'Bloody hell', + 'For fuck\'s sake', 'What the hell', 'God dammit', +]; + export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; + execute: async (message, args) => { const question = getAllArgsAsStr(args); - if (userCmd === command) { - if (!question) { - message.reply('No question provided'); - return; - } - try { - message.reply(`${dammit({NSFW: true})}... ${eightball()}`); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + if (!question) { + message.reply('No question provided'); + return; + } + try { + message.reply(`${randomItemFromArray(dammitPhrases)}... ${randomItemFromArray(eightBallResponses)}`); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/bored.js b/commands/bored.js index 861fe33..ea3c385 100644 --- a/commands/bored.js +++ b/commands/bored.js @@ -1,5 +1,4 @@ -import fetch from 'node-fetch'; -import { messageForAva, splitArgs, getFileName} from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,27 +6,18 @@ const boredEndpoint = 'https://boredapi.com/api/activity'; export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - try { - const response = await fetch(boredEndpoint, { - method: 'get', - headers: { - 'Accept': 'application/json', - } - }); - const jsonResponse = await response.json(); - message.reply(jsonResponse.activity); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + execute: async (message) => { + try { + const response = await fetch(boredEndpoint, { + method: 'get', + headers: { + 'Accept': 'application/json', + } + }); + const jsonResponse = await response.json(); + message.reply(jsonResponse.activity); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/brewery.js b/commands/brewery.js index c856422..3a158dc 100644 --- a/commands/brewery.js +++ b/commands/brewery.js @@ -1,7 +1,6 @@ import { EmbedBuilder } from 'discord.js'; -import fetch from 'node-fetch'; import { embedColor } from '../config/config.js'; -import { randomItemFromArray, messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { randomItemFromArray, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -10,48 +9,40 @@ const beerThumbnail = 'https://i.imgur.com/6ZKnMaw.jpg'; export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; + execute: async (message, args) => { const city = args[2]; - if (userCmd === command) { - if (!city) { - message.reply('No city provided'); - return; - } - try { - const response = await fetch(`${breweryEndpoint}?by_city=${city}`, { - method: 'get', - headers: { - 'Accept': 'application/json', - } - }); - const breweries = await response.json(); - const randomBrewery = randomItemFromArray(breweries); - if (!randomBrewery) { - message.reply('City not found'); - return; + if (!city) { + message.reply('No city provided'); + return; + } + try { + const response = await fetch(`${breweryEndpoint}?by_city=${city}`, { + method: 'get', + headers: { + 'Accept': 'application/json', } - const embed = new EmbedBuilder() - .setColor(embedColor) - .setTitle(randomBrewery.name) - .setURL(randomBrewery.website_url) - .setDescription(`${randomBrewery.street}\n${randomBrewery.city}, ${randomBrewery.state}`) - .setThumbnail(beerThumbnail) - .addFields( - { name: 'Google Maps Link', value: `https://www.google.com/maps?q=${randomBrewery.latitude},${randomBrewery.longitude}`}, - { name: 'Phone', value: randomBrewery.phone ?? 'Not listed' }, - { name: 'Brewery Type', value: randomBrewery.brewery_type ?? 'Not listed' }, - ); - message.channel.send({ embeds: [embed] }); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); + }); + const breweries = await response.json(); + const randomBrewery = randomItemFromArray(breweries); + if (!randomBrewery) { + message.reply('City not found'); + return; } + const embed = new EmbedBuilder() + .setColor(embedColor) + .setTitle(randomBrewery.name) + .setURL(randomBrewery.website_url) + .setDescription(`${randomBrewery.street}\n${randomBrewery.city}, ${randomBrewery.state}`) + .setThumbnail(beerThumbnail) + .addFields( + { name: 'Google Maps Link', value: `https://www.google.com/maps?q=${randomBrewery.latitude},${randomBrewery.longitude}`}, + { name: 'Phone', value: randomBrewery.phone ?? 'Not listed' }, + { name: 'Brewery Type', value: randomBrewery.brewery_type ?? 'Not listed' }, + ); + message.channel.send({ embeds: [embed] }); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/catfact.js b/commands/catfact.js index bc8b631..e530148 100644 --- a/commands/catfact.js +++ b/commands/catfact.js @@ -1,5 +1,4 @@ -import fetch from 'node-fetch'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,27 +6,18 @@ const catFactEndpoint = 'https://catfact.ninja/fact'; export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - try { - const response = await fetch(catFactEndpoint, { - method: 'get', - headers: { - 'Accept': 'application/json', - } - }); - const jsonResponse = await response.json(); - message.reply(jsonResponse.fact); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + execute: async (message) => { + try { + const response = await fetch(catFactEndpoint, { + method: 'get', + headers: { + 'Accept': 'application/json', + } + }); + const jsonResponse = await response.json(); + message.reply(jsonResponse.fact); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/coffee.js b/commands/coffee.js index 361b026..941a2d5 100644 --- a/commands/coffee.js +++ b/commands/coffee.js @@ -1,39 +1,30 @@ import { default as wiki } from 'wikijs'; import { EmbedBuilder } from 'discord.js'; -import { randomItemFromArray, messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { randomItemFromArray, getFileName } from '../lib/utils/utils.js'; import { embedColor } from '../config/config.js'; const command = getFileName(import.meta.url); export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - try { - const coffeeBrands = await wiki().pagesInCategory('Category:Coffee_brands'); - const randomBrand = randomItemFromArray(coffeeBrands); - const wikiPage = await wiki().page(randomBrand); - const wikiPageUrl = wikiPage.fullurl; - const brandTitle = wikiPage.title; - const [pageImage, summary] = await Promise.all([wikiPage.pageImage(), wikiPage.summary()]); + execute: async (message) => { + try { + const coffeeBrands = await wiki().pagesInCategory('Category:Coffee_brands'); + const randomBrand = randomItemFromArray(coffeeBrands); + const wikiPage = await wiki().page(randomBrand); + const wikiPageUrl = wikiPage.fullurl; + const brandTitle = wikiPage.title; + const [pageImage, summary] = await Promise.all([wikiPage.pageImage(), wikiPage.summary()]); - const embed = new EmbedBuilder() - .setColor(embedColor) - .setTitle(brandTitle) - .setURL(wikiPageUrl) - .setDescription(summary) - .setThumbnail(pageImage); - message.channel.send({ embeds: [embed] }); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + const embed = new EmbedBuilder() + .setColor(embedColor) + .setTitle(brandTitle) + .setURL(wikiPageUrl) + .setDescription(summary) + .setThumbnail(pageImage); + message.channel.send({ embeds: [embed] }); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/compliment.js b/commands/compliment.js index ca701b9..e3f59d5 100644 --- a/commands/compliment.js +++ b/commands/compliment.js @@ -1,5 +1,4 @@ -import fetch from 'node-fetch'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,37 +6,29 @@ const complimentEndpoint = 'https://complimentr.com/api'; export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; + execute: async (message, args) => { let userToCompliment = args[2]; - if (userCmd === command) { - if (!userToCompliment) { - message.reply('No user to compliment provided'); - return; - } + if (!userToCompliment) { + message.reply('No user to compliment provided'); + return; + } - if (userToCompliment === 'me') { - userToCompliment = message.author.toString(); - } + if (userToCompliment === 'me') { + userToCompliment = message.author.toString(); + } - try { - const response = await fetch(complimentEndpoint, { - method: 'get', - headers: { - 'Accept': 'application/json', - } - }); - const jsonResponse = await response.json(); - message.channel.send(`${userToCompliment}, ${jsonResponse.compliment}`); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + try { + const response = await fetch(complimentEndpoint, { + method: 'get', + headers: { + 'Accept': 'application/json', + } + }); + const jsonResponse = await response.json(); + message.channel.send(`${userToCompliment}, ${jsonResponse.compliment}`); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/contribute.js b/commands/contribute.js index ffb2ada..426852c 100644 --- a/commands/contribute.js +++ b/commands/contribute.js @@ -1,4 +1,4 @@ -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,15 +7,6 @@ const projectURL = 'https://github.com/circa10a/ava'; export default { commandName: command, execute(message) { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd == command) { - message.reply(projectURL); - } + message.reply(projectURL); }, }; diff --git a/commands/emoji.js b/commands/emoji.js index 86539cc..db22e1e 100644 --- a/commands/emoji.js +++ b/commands/emoji.js @@ -1,26 +1,18 @@ import imageFinder from 'image-search-engine'; -import { messageForAva, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; +import { getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; + execute: async (message, args) => { const searchTerms = getAllArgsAsStr(args); - if (userCmd === command) { - try { - const img = await imageFinder.find(searchTerms, { size: 'small', color: 'transparent' }); - message.reply(img); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + try { + const img = await imageFinder.find(searchTerms, { size: 'small', color: 'transparent' }); + message.reply(img); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/floridaman.js b/commands/floridaman.js index c604590..37ed02e 100644 --- a/commands/floridaman.js +++ b/commands/floridaman.js @@ -1,4 +1,4 @@ -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; import { getRandomSubmission } from '../lib/reddit/submissions.js'; const command = getFileName(import.meta.url); @@ -7,27 +7,18 @@ const subreddit = 'FloridaMan'; export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { + execute: async (message) => { + let randomSubmission; + try { + randomSubmission = await getRandomSubmission({ subreddit }); + } catch(e) { + message.reply(e.toString()); return; } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - let randomSubmission; - try{ - randomSubmission = await getRandomSubmission({subreddit}); - } catch(e) { - message.reply(e.toString()); - return; - } - try { - message.reply(`${randomSubmission.title}\n${randomSubmission.url_overridden_by_dest}`); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + try { + message.reply(`${randomSubmission.title}\n${randomSubmission.url_overridden_by_dest}`); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } }, }; diff --git a/commands/fuck.js b/commands/fuck.js index 79084f3..ab94b0b 100644 --- a/commands/fuck.js +++ b/commands/fuck.js @@ -1,5 +1,4 @@ -import fetch from 'node-fetch'; -import { randomItemFromArray, messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { randomItemFromArray, getFileName } from '../lib/utils/utils.js'; import { avaPrefix } from '../config/config.js'; const foaasRootEndpoint = 'https://foaas.com'; @@ -18,56 +17,51 @@ const avaInsults = [ 'yourself', ]; +// Cache filtered FOAAS operations (static list, fetched once) +let cachedOps = null; + +const getFilteredOps = async () => { + if (cachedOps) return cachedOps; + const response = await fetch('https://foaas.com/operations'); + const data = await response.json(); + cachedOps = data.filter(item => item.fields.length === 2 && item.url.includes(':name') && !notFucks.includes(item.name)); + return cachedOps; +}; + export default { commandName: command, - execute: async (message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - - const args = splitArgs(message); - const cmd = args[1]; + execute: async (message, args) => { const thingToInsult = args[2]; - if (cmd === command) { - // Ava fights back if people diss her - if (avaInsults.includes(thingToInsult.toLowerCase())) { - message.reply('No, fuck you!'); - return; - } - - // Someone said 'ava fuck' - if (args.length < 3) { - message.reply('Fuck you'); - return; - } - - try { - // Get the list of operations (filtered) - const ops = await fetch('https://foaas.com/operations'). - then(response => response.json()). - then(data => data.filter(item => item.fields.length === 2 && item.url.includes(':name') && !notFucks.includes(item.name))); + // Ava fights back if people diss her + if (avaInsults.includes(thingToInsult?.toLowerCase())) { + message.reply('No, fuck you!'); + return; + } - // Pick a random one - const selection = randomItemFromArray(ops); - const userArg = encodeURIComponent(args.slice(2).join(' ')); + // Someone said 'ava fuck' + if (args.length < 3) { + message.reply('Fuck you'); + return; + } - // Do the needful - const selectedUrl = selection.url.replace(':name', userArg).replace(':from', message.author.username); + try { + const ops = await getFilteredOps(); + const selection = randomItemFromArray(ops); + const userArg = encodeURIComponent(args.slice(2).join(' ')); + const selectedUrl = selection.url.replace(':name', userArg).replace(':from', message.author.username); - const resp = await fetch(foaasRootEndpoint + selectedUrl, { - method: 'get', - headers: { - 'Accept': 'text/plain' - } - }); + const resp = await fetch(foaasRootEndpoint + selectedUrl, { + method: 'get', + headers: { + 'Accept': 'text/plain' + } + }); - const msg = await resp.text(); - message.channel.send(msg); - } catch (e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + const msg = await resp.text(); + message.channel.send(msg); + } catch (e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/gcp.js b/commands/gcp.js index 30f741c..eb1e1f8 100644 --- a/commands/gcp.js +++ b/commands/gcp.js @@ -1,19 +1,10 @@ -import { messageForAva, splitArgs, getFileName} from '../lib/utils/utils.js'; +import { getFileName} from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); export default { commandName: command, execute(message) { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - message.reply('GCP is trash and Google should be embarrassed'); - } + message.reply('GCP is trash and Google should be embarrassed'); }, }; diff --git a/commands/help.js b/commands/help.js index a6ac3b4..47c1a1f 100644 --- a/commands/help.js +++ b/commands/help.js @@ -1,24 +1,13 @@ import { EmbedBuilder } from 'discord.js'; -import * as fs from 'fs'; -import { commandsDir, embedColor } from '../config/config.js'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { embedColor } from '../config/config.js'; -const command = getFileName(import.meta.url); +const command = 'help'; -const eventFiles = fs.readdirSync(`./${commandsDir}`).filter(file => file.endsWith('.js')); -const availableCommands = eventFiles.map(event => event.replace('.js', '')); - -export default { - commandName: command, - execute(message) { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { +const help = (opts = {}) => { + return { + commandName: command, + execute(message) { + const { availableCommands = [] } = opts; const embed = new EmbedBuilder() .setColor(embedColor) .setTitle('Available Commands') @@ -27,6 +16,8 @@ export default { .setThumbnail('https://i.imgur.com/XbO6CSl.jpg'); message.channel.send({ embeds: [embed] }); - } - }, + }, + }; }; + +export default help; diff --git a/commands/hp.js b/commands/hp.js index 832b4f8..09dbca4 100644 --- a/commands/hp.js +++ b/commands/hp.js @@ -1,43 +1,7 @@ -import { EmbedBuilder } from 'discord.js'; +import { createRedditEmbedCommand } from '../lib/commands/redditEmbed.js'; +import { getFileName } from '../lib/utils/utils.js'; -import { embedColor } from '../config/config.js'; -import { getRandomSubmissionWithImage } from '../lib/reddit/submissions.js'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; - -const command = getFileName(import.meta.url); - -const subreddit = 'HarryPotterMemes'; - -export default { - commandName: command, - execute: async (message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - let randomSubmission; - try { - randomSubmission = await getRandomSubmissionWithImage({subreddit}); - } catch(e) { - message.reply(e.toString()); - return; - } - try { - let description = randomSubmission.selftext || subreddit; - const embed = new EmbedBuilder() - .setColor(embedColor) - .setTitle(randomSubmission.title) - .setURL(`https://reddit.com${randomSubmission.permalink}`) - .setDescription(description) - .setImage(randomSubmission.url_overridden_by_dest); - message.channel.send({ embeds: [embed] }); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } - } - }, -}; +export default createRedditEmbedCommand({ + subreddit: 'HarryPotterMemes', + commandName: getFileName(import.meta.url), +}); diff --git a/commands/insult.js b/commands/insult.js index 3208f2b..6b32ecf 100644 --- a/commands/insult.js +++ b/commands/insult.js @@ -1,6 +1,5 @@ import { decode } from 'html-entities'; -import fetch from 'node-fetch'; -import { messageForAva, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; +import { getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -8,38 +7,29 @@ const insultEndpoint = 'https://evilinsult.com/generate_insult.php?lang=en&type= export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - - const args = splitArgs(message); - const userCmd = args[1]; + execute: async (message, args) => { let thingToInsult = getAllArgsAsStr(args); - if (userCmd === command) { - if (!thingToInsult) { - message.reply('Nothing to insult'); - return; - } + if (!thingToInsult) { + message.reply('Nothing to insult'); + return; + } - if (thingToInsult === 'me') { - thingToInsult = message.author.toString(); - } + if (thingToInsult === 'me') { + thingToInsult = message.author.toString(); + } - try { - const response = await fetch(insultEndpoint, { - method: 'get', - headers: { - 'Accept': 'application/json', - } - }); - const jsonResponse = await response.json(); - message.channel.send(`${thingToInsult}, ${decode(jsonResponse.insult)}`); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } + try { + const response = await fetch(insultEndpoint, { + method: 'get', + headers: { + 'Accept': 'application/json', + } + }); + const jsonResponse = await response.json(); + message.channel.send(`${thingToInsult}, ${decode(jsonResponse.insult)}`); + } catch(e) { + message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); } } }; diff --git a/commands/java.js b/commands/java.js index 1b23c67..5f8e01d 100644 --- a/commands/java.js +++ b/commands/java.js @@ -1,4 +1,4 @@ -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,15 +7,6 @@ const javaImage = 'https://i.redd.it/o4w97sa7iidz.jpg'; export default { commandName: command, execute(message) { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd == command) { - message.reply(javaImage); - } + message.reply(javaImage); }, }; diff --git a/commands/karen.js b/commands/karen.js index c06ac07..8494a97 100644 --- a/commands/karen.js +++ b/commands/karen.js @@ -1,43 +1,7 @@ -import { EmbedBuilder } from 'discord.js'; +import { createRedditEmbedCommand } from '../lib/commands/redditEmbed.js'; +import { getFileName } from '../lib/utils/utils.js'; -import { embedColor } from '../config/config.js'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; -import { getRandomSubmissionWithImage } from '../lib/reddit/submissions.js'; - -const command = getFileName(import.meta.url); - -const subreddit = 'FuckYouKaren'; - -export default { - commandName: command, - execute: async (message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - let randomSubmission; - try { - randomSubmission = await getRandomSubmissionWithImage({subreddit}); - } catch(e) { - message.reply(e.toString()); - return; - } - try{ - let description = randomSubmission.selftext || subreddit; - const embed = new EmbedBuilder() - .setColor(embedColor) - .setTitle(randomSubmission.title) - .setURL(`https://reddit.com${randomSubmission.permalink}`) - .setDescription(description) - .setImage(randomSubmission.url_overridden_by_dest); - message.channel.send({ embeds: [embed] }); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } - } - }, -}; +export default createRedditEmbedCommand({ + subreddit: 'FuckYouKaren', + commandName: getFileName(import.meta.url), +}); diff --git a/commands/meme.js b/commands/meme.js index 5a81dc8..2b27491 100644 --- a/commands/meme.js +++ b/commands/meme.js @@ -1,43 +1,7 @@ -import { EmbedBuilder } from 'discord.js'; +import { createRedditEmbedCommand } from '../lib/commands/redditEmbed.js'; +import { getFileName } from '../lib/utils/utils.js'; -import { embedColor } from '../config/config.js'; -import { getRandomSubmissionWithImage } from '../lib/reddit/submissions.js'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; - -const command = getFileName(import.meta.url); - -const subreddit = 'dankmemes'; - -export default { - commandName: command, - execute: async (message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; - - if (userCmd === command) { - let randomSubmission; - try { - randomSubmission = await getRandomSubmissionWithImage({subreddit}); - } catch(e) { - message.reply(e.toString()); - return; - } - try { - let description = randomSubmission.selftext || subreddit; - const embed = new EmbedBuilder() - .setColor(embedColor) - .setTitle(randomSubmission.title) - .setURL(`https://reddit.com${randomSubmission.permalink}`) - .setDescription(description) - .setImage(randomSubmission.url_overridden_by_dest); - message.channel.send({ embeds: [embed] }); - } catch(e) { - message.channel.send(`\`\`\`log\n${e.toString()}\`\`\``); - } - } - }, -}; +export default createRedditEmbedCommand({ + subreddit: 'dankmemes', + commandName: getFileName(import.meta.url), +}); diff --git a/commands/mock.js b/commands/mock.js index 971c24b..2e0faa4 100644 --- a/commands/mock.js +++ b/commands/mock.js @@ -1,32 +1,18 @@ -import { messageForAva, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; +import { getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); -const mockText = (str) => { - let newStr = ''; - str.split('').forEach((el, idx) => { - newStr += idx % 2 === 0 ? el.toLowerCase() : el.toUpperCase(); - }); - return newStr; -}; +const mockText = (str) => str.split('').map((ch, i) => i % 2 === 0 ? ch.toLowerCase() : ch.toUpperCase()).join(''); export default { commandName: command, - execute(message) { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - const args = splitArgs(message); - const userCmd = args[1]; + execute(message, args) { const userArg = getAllArgsAsStr(args); - if (userCmd === command) { - if (!userArg) { - message.reply('Nothing to mock'); - return; - } - message.reply(mockText(userArg)); + if (!userArg) { + message.reply('Nothing to mock'); + return; } + message.reply(mockText(userArg)); }, }; diff --git a/commands/recipe.js b/commands/recipe.js index 2137475..8c153d2 100644 --- a/commands/recipe.js +++ b/commands/recipe.js @@ -1,5 +1,5 @@ import { parse } from 'node-html-parser'; -import { randomItemFromArray, messageForAva, getBody, splitArgs, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; +import { randomItemFromArray, getBody, getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); const seriousEatsBaseURL = 'https://www.seriouseats.com'; @@ -8,51 +8,42 @@ const itemsHTMLClass ='#schema-lifestyle_1-0'; export default { commandName: command, - execute: async(message) => { - // Ensure message is intended for ava - if (!messageForAva(message)) { - return; - } - - const args = splitArgs(message); - const userCmd = args[1]; + execute: async (message, args) => { const searchTerms = getAllArgsAsStr(args); - if (userCmd === command) { - try { - // If search terms specified - if (searchTerms) { - const body = await getBody(`${seriousEatsBaseURL}/search?q=${encodeURIComponent(searchTerms)}`); - const root = parse(body); - const results = root.querySelectorAll('.card'); - if (results.length >= 1 ) { - const firstSearchResult = results[0]._attrs.href; - message.reply(firstSearchResult); - return; - } else { - message.reply('No recipes found'); - return; - } + try { + // If search terms specified + if (searchTerms) { + const body = await getBody(`${seriousEatsBaseURL}/search?q=${encodeURIComponent(searchTerms)}`); + const root = parse(body); + const results = root.querySelectorAll('.card'); + if (results.length >= 1 ) { + const firstSearchResult = results[0]._attrs.href; + message.reply(firstSearchResult); + return; + } else { + message.reply('No recipes found'); + return; } + } - // Random (if no search terms) - if (!searchTerms) { - const body = await getBody(allRecipesEndpoint); - const root = parse(body); - // JSON in