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/README.md b/README.md index e5241fd..aa617aa 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,8 +13,21 @@ A discord implementation of the famous ava bot ## Usage +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= + +# Slack only +export AVA_SLACK_BOT_TOKEN= +export AVA_SLACK_APP_TOKEN= + +# Both platforms simultaneously export AVA_DISCORD_TOKEN= +export AVA_SLACK_BOT_TOKEN= +export AVA_SLACK_APP_TOKEN= + npm i npm start ``` @@ -24,7 +37,10 @@ npm start | | | | |----------------------------|----------|---------| | 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 | `""` | @@ -66,7 +82,14 @@ npm start ## Docker ```bash +# Discord only docker run -e AVA_DISCORD_TOKEN="" circa10a/ava + +# Slack only +docker run -e AVA_SLACK_BOT_TOKEN="" -e AVA_SLACK_APP_TOKEN="" circa10a/ava + +# 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/commands/8ball.js b/commands/8ball.js index c768f92..cf5ab2b 100644 --- a/commands/8ball.js +++ b/commands/8ball.js @@ -1,33 +1,36 @@ -import { Events } from 'discord.js'; -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, - name: Events.MessageCreate, - once: false, - 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 77f8d19..ea3c385 100644 --- a/commands/bored.js +++ b/commands/bored.js @@ -1,6 +1,4 @@ -import { Events } from 'discord.js'; -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); @@ -8,29 +6,18 @@ 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)) { - 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 7ea43e8..3a158dc 100644 --- a/commands/brewery.js +++ b/commands/brewery.js @@ -1,7 +1,6 @@ -import { EmbedBuilder, Events } from 'discord.js'; -import fetch from 'node-fetch'; +import { EmbedBuilder } from 'discord.js'; 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,50 +9,40 @@ 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)) { - 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 aff752d..e530148 100644 --- a/commands/catfact.js +++ b/commands/catfact.js @@ -1,6 +1,4 @@ -import { Events } from 'discord.js'; -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); @@ -8,29 +6,18 @@ 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)) { - 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 79a735d..941a2d5 100644 --- a/commands/coffee.js +++ b/commands/coffee.js @@ -1,41 +1,30 @@ import { default as wiki } from 'wikijs'; -import { EmbedBuilder, Events } from 'discord.js'; -import { randomItemFromArray, messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { EmbedBuilder } from 'discord.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, - name: Events.MessageCreate, - once: false, - 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 f214f9d..e3f59d5 100644 --- a/commands/compliment.js +++ b/commands/compliment.js @@ -1,6 +1,4 @@ -import { Events } from 'discord.js'; -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); @@ -8,39 +6,29 @@ 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)) { - 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 281196f..426852c 100644 --- a/commands/contribute.js +++ b/commands/contribute.js @@ -1,5 +1,4 @@ -import { Events } from 'discord.js'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,18 +6,7 @@ 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)) { - 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 178a4b4..db22e1e 100644 --- a/commands/emoji.js +++ b/commands/emoji.js @@ -1,29 +1,18 @@ -import { Events } from 'discord.js'; 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, - name: Events.MessageCreate, - once: false, - 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 8c085a2..37ed02e 100644 --- a/commands/floridaman.js +++ b/commands/floridaman.js @@ -1,5 +1,4 @@ -import { Events } from 'discord.js'; -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); @@ -8,29 +7,18 @@ const subreddit = 'FloridaMan'; export default { commandName: command, - name: Events.MessageCreate, - once: false, - 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 6df3ad1..ab94b0b 100644 --- a/commands/fuck.js +++ b/commands/fuck.js @@ -1,6 +1,4 @@ -import { Events } from 'discord.js'; -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'; @@ -19,58 +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, - name: Events.MessageCreate, - once: false, - 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 54ef88d..eb1e1f8 100644 --- a/commands/gcp.js +++ b/commands/gcp.js @@ -1,22 +1,10 @@ -import { Events } from 'discord.js'; -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, - name: Events.MessageCreate, - once: false, 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 db4059f..47c1a1f 100644 --- a/commands/help.js +++ b/commands/help.js @@ -1,26 +1,13 @@ -import { EmbedBuilder, Events } 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 { EmbedBuilder } from 'discord.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, - name: Events.MessageCreate, - once: false, - 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') @@ -29,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 df570a0..09dbca4 100644 --- a/commands/hp.js +++ b/commands/hp.js @@ -1,45 +1,7 @@ -import { EmbedBuilder, Events } 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, - name: Events.MessageCreate, - once: false, - 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 fa03abc..6b32ecf 100644 --- a/commands/insult.js +++ b/commands/insult.js @@ -1,7 +1,5 @@ -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'; +import { getAllArgsAsStr, getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -9,40 +7,29 @@ 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)) { - 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 2f46938..5f8e01d 100644 --- a/commands/java.js +++ b/commands/java.js @@ -1,5 +1,4 @@ -import { Events } from 'discord.js'; -import { messageForAva, splitArgs, getFileName } from '../lib/utils/utils.js'; +import { getFileName } from '../lib/utils/utils.js'; const command = getFileName(import.meta.url); @@ -7,18 +6,7 @@ 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)) { - 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 b93c827..8494a97 100644 --- a/commands/karen.js +++ b/commands/karen.js @@ -1,45 +1,7 @@ -import { EmbedBuilder, Events } 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, - name: Events.MessageCreate, - once: false, - 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 89f5314..2b27491 100644 --- a/commands/meme.js +++ b/commands/meme.js @@ -1,45 +1,7 @@ -import { EmbedBuilder, Events } 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, - name: Events.MessageCreate, - once: false, - 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 2700825..2e0faa4 100644 --- a/commands/mock.js +++ b/commands/mock.js @@ -1,35 +1,18 @@ -import { Events } from 'discord.js'; -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, - name: Events.MessageCreate, - once: false, - 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 8d5d5f7..8c153d2 100644 --- a/commands/recipe.js +++ b/commands/recipe.js @@ -1,6 +1,5 @@ -import { Events } from 'discord.js'; 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'; @@ -9,53 +8,42 @@ 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)) { - 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