Skip to content

Commit

Permalink
Refactoring and progress
Browse files Browse the repository at this point in the history
- Moved from ESLint flat config to .eslintrc
- Corrected lint script
- Updated NPM packages
- Added domain whitelist
- Capitalized variables in config.json
- Implemented autocomplete support
- Reformatted database
- Fixed badge delete function
- Async with misused promises (in button.ts and mongo.ts)
- Updated types
  • Loading branch information
SomeAspy committed Jul 6, 2023
1 parent da39607 commit 8cfca0d
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 101 deletions.
43 changes: 43 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"plugins": [
"json",
"@typescript-eslint"
],
"env": {
"node": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:json/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"ignorePatterns": [
"dist",
"node_modules",
"docs"
],
"parser": "@typescript-eslint/parser",
"overrides": [],
"parserOptions": {
"project": true,
"tsconfigRootDir": "__dirname",
"ecmaVersion": "latest",
"sourceType": "module",
"ecmaFeatures": {},
"extraFileExtensions": [
".json"
]
},
"rules": {
"semi": [
"error",
"always"
],
"prefer-const": [
"error"
],
"@typescript-eslint/no-non-null-assertion": "off"
}
}
7 changes: 6 additions & 1 deletion config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
"DatabaseName": "GlobalBadges",
"CollectionName": "database",
"MaxBadges": 5,
"promptChannel": "ChannelID"
"PromptChannel": "ChannelID",
"Domains": [
"media.discordapp.net",
"cdn.discordapp.com",
"tenor.com"
]
}
30 changes: 0 additions & 30 deletions eslint.config.js

This file was deleted.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "tsx src/index.ts",
"start": "node dist/index.js",
"build": "tsc",
"lint": "eslint"
"lint": "eslint src/**/* config/* -c .eslintrc"
},
"devDependencies": {
"@types/node": "^20.4.0",
Expand All @@ -23,6 +23,6 @@
"@discordjs/rest": "^1.7.1",
"discord-api-types": "^0.37.47",
"discord.js": "^14.11.0",
"mongodb": "^5.6.0"
"mongodb": "^5.7.0"
}
}
14 changes: 10 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 5 additions & 10 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
EmbedBuilder,
SlashCommandBuilder,
} from 'discord.js';
import { getBadges, getPending } from '../mongo.js';
import { getBadges } from '../mongo.js';
export const data = new SlashCommandBuilder()
.setName('list')
.setDescription("List all a user's badges")
Expand All @@ -16,21 +16,16 @@ export const data = new SlashCommandBuilder()

export async function execute(interaction: ChatInputCommandInteraction) {
const user = interaction.user;
const badges = await getBadges(user.id);
const pending = await getPending(user.id);
const badges = await getBadges(user.id, 'all');
const returnEmbed = new EmbedBuilder()
.setTitle(`${user.username}'s Badges`)
.setDescription(`${user.username} has ${badges.length} badges`)
.setColor('#FF0000');
for (const badge of badges) {
returnEmbed.addFields({
name: badge.name,
value: badge.badge,
});
}
for (const badge of pending) {
returnEmbed.addFields({
name: `${badge.name} (Pending)`,
name: `${badge.name}${
badge.pending ? ' `(Pending Approval)`' : ''
}`,
value: badge.badge,
});
}
Expand Down
45 changes: 31 additions & 14 deletions src/commands/manage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,25 @@ import {
SlashCommandBuilder,
type ChatInputCommandInteraction,
type ButtonInteraction,
AutocompleteInteraction,
} from 'discord.js';
import {
canMakeNewBadge,
pendBadge,
isBlocked,
deleteBadge,
deletePending,
badgeExists,
getBadges,
} from '../mongo.js';

import settings from '../../config/config.json' assert { type: 'json' };
import untypedConfig from '../../config/config.json' assert { type: 'json' };
import { fireVerification } from '../handler/verification.js';
import { isAllowedDomain } from '../lib/checkDomain.js';
import { Badge } from '../types/badge.js';

import { Config } from '../types/config.js';

const settings = untypedConfig as Config;

export const data = new SlashCommandBuilder()
.setName('manage')
Expand All @@ -26,8 +33,7 @@ export const data = new SlashCommandBuilder()
option
.setName('name')
.setDescription('The name of the badge')
.setRequired(true)
.setAutocomplete(true),
.setRequired(true),
)
.addStringOption((option) =>
option
Expand Down Expand Up @@ -83,14 +89,22 @@ export async function execute(interaction: ChatInputCommandInteraction) {
}
if (!(await canMakeNewBadge(interaction.user.id))) {
await interaction.reply({
content: `You already have ${settings.maxBadges} or more badges! (This includes pending badges!)`,
content: `You already have ${settings.MaxBadges} or more badges! (This includes pending badges!)`,
ephemeral: true,
});
return;
}
// These WILL exist because they are required by the slash command
const name = interaction.options.getString('name')!;
const url = interaction.options.getString('url')!;

if (!isAllowedDomain(url)) {
await interaction.reply({
content: 'This is not a whitelisted domain',
ephemeral: true,
});
return;
}
if (await badgeExists(id, name)) {
await interaction.reply({
content: 'You already have a badge with that name!',
Expand Down Expand Up @@ -124,15 +138,6 @@ export async function execute(interaction: ChatInputCommandInteraction) {
});
return;
}
if (await badgeExists(id, name)) {
await deletePending(id, name).then(async () => {
await interaction.reply({
content: 'Badge deleted!',
ephemeral: true,
});
});
return;
}
await interaction.reply({
content: 'You do not have a badge with that name!',
ephemeral: true,
Expand Down Expand Up @@ -184,3 +189,15 @@ export const buttons = [
},
},
];

export async function autocomplete(interaction: AutocompleteInteraction) {
const options: string[] = [];
const focus = interaction.options.getFocused();
(await getBadges(interaction.user.id, 'all')).forEach((badge: Badge) => {
options.push(badge.name);
});
const filtered = options.filter((option) => option.startsWith(focus));
await interaction.respond(
filtered.map((option) => ({ name: option, value: option })),
);
}
12 changes: 5 additions & 7 deletions src/handler/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ export function handleButton(
for (const command of commands) {
const buttons = command[1]?.buttons;
if (!buttons) continue;
// This might not work due to not being fully async
buttons.forEach((button: Button) => {
async () => {
if (button.id === interaction.customId) {
await button.execute(interaction, client);
}
};
// eslint-disable-next-line @typescript-eslint/no-misused-promises
buttons.forEach(async (button: Button) => {
if (button.id === interaction.customId) {
await button.execute(interaction, client);
}
});
}
}
7 changes: 5 additions & 2 deletions src/handler/verification.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import settings from '../../config/config.json' assert { type: 'json' };
import untypedSettings from '../../config/config.json' assert { type: 'json' };
import { Config } from '../types/config.js';

const settings = untypedSettings as Config;
import {
EmbedBuilder,
ChatInputCommandInteraction,
Expand Down Expand Up @@ -55,6 +58,6 @@ export async function fireVerification(data: ChatInputCommandInteraction) {
);

await (
data.client.channels.cache.get(settings.promptChannel) as TextChannel
data.client.channels.cache.get(settings.PromptChannel) as TextChannel
).send({ embeds: [embed], components: [buttons] });
}
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ await pushCommands();
import { commands } from './lib/indexer.js';
import { handleCommand } from './handler/command.js';
import { handleButton } from './handler/button.js';
import { handleAutocomplete } from './handler/autocomplete.js';
client.on(Events.InteractionCreate, async (interaction) => {
if (interaction.isCommand()) {
await handleCommand(
Expand All @@ -33,7 +34,7 @@ client.on(Events.InteractionCreate, async (interaction) => {
// L13 - handler/button.ts - This should probably be async, but we will have to see
handleButton(interaction, client, commands);
} else if (interaction.isAutocomplete()) {
console.log('Autocomplete interaction received');
await handleAutocomplete(interaction, client, commands);
}
});

Expand Down
8 changes: 8 additions & 0 deletions src/lib/checkDomain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Config } from '../types/config.js';
import untypedConfig from '../../config/config.json' assert { type: 'json' };

const settings = untypedConfig as Config;

export function isAllowedDomain(domain: string): boolean {
return settings.Domains.some((v) => domain.includes(v));
}
9 changes: 7 additions & 2 deletions src/lib/pushCommands.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { commandData } from './indexer.js';
import { REST } from '@discordjs/rest';
import { Routes } from 'discord-api-types/v10';
import credentials from '../../config/credentials.json' assert { type: 'json' };
import settings from '../../config/config.json' assert { type: 'json' };
import untypedCredentials from '../../config/credentials.json' assert { type: 'json' };
import untypedConfig from '../../config/config.json' assert { type: 'json' };

import { Config, Credentials } from '../types/config.js';

const settings = untypedConfig as Config;
const credentials = untypedCredentials as Credentials;

const restAPI = new REST({ version: '10' }).setToken(credentials.DiscordToken);

Expand Down
Loading

0 comments on commit 8cfca0d

Please sign in to comment.