feat: initial project
This commit is contained in:
commit
683357fec1
25 changed files with 10325 additions and 0 deletions
73
.drone.yml
Normal file
73
.drone.yml
Normal file
|
@ -0,0 +1,73 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: install
|
||||
image: node:16
|
||||
commands:
|
||||
- echo ===== INSTALLING DEPENDENCIES =====
|
||||
- yarn install
|
||||
|
||||
- name: build
|
||||
image: node:16
|
||||
commands:
|
||||
- echo ===== BUILDING APPLICATION =====
|
||||
- yarn build
|
||||
|
||||
- name: publish-dev
|
||||
image: node:16
|
||||
commands:
|
||||
- echo ===== PUBLISHING APPLICATION =====
|
||||
- echo "//registry.npmjs.org/:_authToken=$${NPM_TOKEN}" > ~/.npmrc
|
||||
- apt update
|
||||
- apt install -y jq
|
||||
- npm version --git-tag-version=false $$(jq --raw-output '.version' package.json)-$$(git rev-parse HEAD).$$(date +%s)
|
||||
- npm publish --tag dev --access public
|
||||
environment:
|
||||
NPM_TOKEN:
|
||||
from_secret: NPM_TOKEN
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: publish-stable
|
||||
image: node:16
|
||||
commands:
|
||||
- echo ===== PUBLISHING APPLICATION =====
|
||||
- yarn semantic-release
|
||||
environment:
|
||||
GITEA_URL: "https://code.relms.dev"
|
||||
GITEA_TOKEN:
|
||||
from_secret: GITEA_TOKEN
|
||||
NPM_TOKEN:
|
||||
from_secret: NPM_TOKEN
|
||||
GIT_AUTHOR_NAME: "DroneCI"
|
||||
GIT_AUTHOR_EMAIL: "drone@relms.dev"
|
||||
GIT_COMMITTER_NAME: "DroneCI"
|
||||
GIT_COMMITTER_EMAIL: "drone@relms.dev"
|
||||
when:
|
||||
branch:
|
||||
- stable
|
||||
event:
|
||||
- push
|
||||
|
||||
# - name: discord-notification
|
||||
# image: appleboy/drone-discord
|
||||
# settings:
|
||||
# webhook_id:
|
||||
# from_secret: DISCORD_WEBHOOK_ID
|
||||
# webhook_token:
|
||||
# from_secret: DISCORD_WEBHOOK_TOKEN
|
||||
# message: >
|
||||
# {{#success build.status}}
|
||||
# Build {{build.number}} succeeded.
|
||||
# {{else}}
|
||||
# Build {{build.number}} failed.
|
||||
# {{/success}}
|
||||
# when:
|
||||
# status:
|
||||
# - success
|
||||
# - failure
|
10
.editorconfig
Normal file
10
.editorconfig
Normal file
|
@ -0,0 +1,10 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
trim_trailing_whitespace = true
|
126
.eslintrc
Normal file
126
.eslintrc
Normal file
|
@ -0,0 +1,126 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
"commonjs": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"unicorn"
|
||||
],
|
||||
"rules": {
|
||||
// Base Eslint Rules
|
||||
"indent": [
|
||||
"error",
|
||||
"tab",
|
||||
{
|
||||
"SwitchCase": 1,
|
||||
"CallExpression": {
|
||||
"arguments": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"eol-last": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"object-curly-spacing": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"default-case": "error",
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"always-multiline"
|
||||
],
|
||||
"no-case-declarations": "off",
|
||||
"camelcase": "off",
|
||||
"keyword-spacing": "error",
|
||||
"spaced-comment": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-var": "error",
|
||||
"eqeqeq": "error",
|
||||
"no-eq-null": "error",
|
||||
"arrow-parens": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
// TypeScript Rules
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"error",
|
||||
{
|
||||
"selector": "class",
|
||||
"format": [
|
||||
"PascalCase"
|
||||
]
|
||||
},
|
||||
{
|
||||
"selector": "classProperty",
|
||||
"modifiers": [
|
||||
"private"
|
||||
],
|
||||
"format": [
|
||||
"camelCase"
|
||||
],
|
||||
"leadingUnderscore": "require"
|
||||
},
|
||||
{
|
||||
"selector": "interface",
|
||||
"format": null,
|
||||
"custom": {
|
||||
"regex": "^I",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"selector": "enum",
|
||||
"format": null,
|
||||
"custom": {
|
||||
"regex": "^E",
|
||||
"match": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error",
|
||||
{
|
||||
"accessibility": "explicit"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": "error",
|
||||
// Unicorn Rules
|
||||
"unicorn/filename-case": [
|
||||
"error",
|
||||
{
|
||||
"case": "camelCase"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reportUnusedDisableDirectives": true
|
||||
}
|
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/.yarn/*
|
||||
!/.yarn/patches
|
||||
!/.yarn/plugins
|
||||
!/.yarn/releases
|
||||
!/.yarn/sdks
|
||||
node_modules
|
||||
.DS_store
|
||||
dist
|
4
.husky/commit-msg
Executable file
4
.husky/commit-msg
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn commitlint --edit
|
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
16.11.0
|
54
.releaserc
Normal file
54
.releaserc
Normal file
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"branches": [
|
||||
"stable"
|
||||
],
|
||||
"repositoryUrl": "https://code.relms.dev/NeonJS/framework",
|
||||
"ci": true,
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"parserOpts": {
|
||||
"noteKeywords": [
|
||||
"BREAKING CHANGE",
|
||||
"BREAKING CHANGES"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
"parserOpts": {
|
||||
"noteKeywords": [
|
||||
"BREAKING CHANGE",
|
||||
"BREAKING CHANGES"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"@semantic-release/changelog",
|
||||
[
|
||||
"@semantic-release/npm",
|
||||
{
|
||||
"tarballDir": "dist"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-gitea",
|
||||
{
|
||||
"assets": [
|
||||
"dist/*.tgz"
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
363
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
Normal file
363
.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
9
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
Normal file
9
.yarn/plugins/@yarnpkg/plugin-typescript.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
367
.yarn/plugins/@yarnpkg/plugin-version.cjs
vendored
Normal file
367
.yarn/plugins/@yarnpkg/plugin-version.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
631
.yarn/releases/yarn-3.0.2.cjs
vendored
Executable file
631
.yarn/releases/yarn-3.0.2.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
13
.yarnrc.yml
Normal file
13
.yarnrc.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
nmMode: hardlinks-local
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
spec: "@yarnpkg/plugin-typescript"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
|
||||
spec: "@yarnpkg/plugin-version"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.0.2.cjs
|
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
### [1.0.1](https://code.relms.dev/NeonJS/framework/compare/v1.0.0...v1.0.1) (2021-11-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pino pretty deprecation ([90c6de1](https://code.relms.dev/NeonJS/framework/commit/90c6de1b5d44e196f5666c8cee56b1cde6ba46af))
|
7
LICENSE
Normal file
7
LICENSE
Normal file
|
@ -0,0 +1,7 @@
|
|||
Copyright © 2021-2021 Daryl Ronningen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1
README.md
Normal file
1
README.md
Normal file
|
@ -0,0 +1 @@
|
|||
# framework
|
98
package.json
Normal file
98
package.json
Normal file
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"name": "@neonjs/framework",
|
||||
"version": "1.0.1",
|
||||
"description": "Discord.JS Slash Commands based framework",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"repository": "https://code.relms.dev/NeonJS/framework",
|
||||
"bugs": "https://code.relms.dev/NeonJS/framework/issues",
|
||||
"homepage": "https://code.relms.dev/NeonJS/framework/src/branch/master/README.md",
|
||||
"keywords": [
|
||||
"Discord",
|
||||
"framework",
|
||||
"discord.js",
|
||||
"neon",
|
||||
"neonjs"
|
||||
],
|
||||
"author": "Daryl Ronningen <relms@relms.dev>",
|
||||
"maintainers": [],
|
||||
"contributors": [],
|
||||
"engines": {
|
||||
"node": ">=16.*"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"package.json",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rimraf dist",
|
||||
"commit": "cz",
|
||||
"lint": "eslint --format=pretty src",
|
||||
"lint:fix": "eslint --format=pretty src --fix",
|
||||
"postinstall": "husky install",
|
||||
"prepublishOnly": "pinst --disable",
|
||||
"postpublish": "pinst --enable",
|
||||
"release": "semantic-release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/rest": "^0.1.0-canary.0",
|
||||
"discord-api-types": "^0.24.0",
|
||||
"discord.js": "^13.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"node-fetch": "^3.0.0",
|
||||
"pino": "^7.0.5",
|
||||
"pino-pretty": "^7.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^14.1.0",
|
||||
"@commitlint/config-conventional": "^14.1.0",
|
||||
"@commitlint/cz-commitlint": "^14.1.0",
|
||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||
"@semantic-release/changelog": "^6.0.1",
|
||||
"@semantic-release/commit-analyzer": "^9.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/npm": "^8.0.2",
|
||||
"@semantic-release/release-notes-generator": "^10.0.2",
|
||||
"@types/eslint": "^7.28.2",
|
||||
"@types/lodash": "^4.14.176",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/pino": "^6.3.12",
|
||||
"@types/semantic-release": "^17.2.2",
|
||||
"@types/source-map-support": "^0.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "^5.3.0",
|
||||
"@typescript-eslint/parser": "^5.3.0",
|
||||
"@typescript-eslint/typescript-estree": "^5.3.0",
|
||||
"commitizen": "^4.2.4",
|
||||
"eslint": "^8.1.0",
|
||||
"eslint-formatter-pretty": "^4.1.0",
|
||||
"eslint-plugin-unicorn": "^37.0.1",
|
||||
"husky": "^7.0.4",
|
||||
"inquirer": "^8.2.0",
|
||||
"lint-staged": "^11.2.6",
|
||||
"pinst": "^2.1.6",
|
||||
"rimraf": "^3.0.2",
|
||||
"semantic-release": "^18.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*": [
|
||||
"yarn lint"
|
||||
]
|
||||
},
|
||||
"commitlint": {
|
||||
"extends": [
|
||||
"@commitlint/config-conventional"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "@commitlint/cz-commitlint"
|
||||
}
|
||||
}
|
||||
}
|
5
src/index.ts
Normal file
5
src/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export { Command } from './lib/structures/command';
|
||||
export * from './lib/utils/augments';
|
||||
export * from './lib/utils/types';
|
||||
export * from './lib/utils/utils';
|
||||
export * from './lib/neonClient';
|
201
src/lib/neonClient.ts
Normal file
201
src/lib/neonClient.ts
Normal file
|
@ -0,0 +1,201 @@
|
|||
|
||||
import { REST } from '@discordjs/rest';
|
||||
import { APIApplicationCommand, Routes } from 'discord-api-types/v9';
|
||||
import { ApplicationCommand, ApplicationCommandOptionData, ChatInputApplicationCommandData, Client, ClientOptions, Collection } from 'discord.js';
|
||||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
import pino from 'pino';
|
||||
import PinoPretty from 'pino-pretty';
|
||||
import { walkDir } from './utils/utils';
|
||||
|
||||
import type { Command } from './structures/command';
|
||||
|
||||
const commandCooldowns: Collection<string, Collection<string, number>> = new Collection();
|
||||
const slashCommands: Collection<string, ApplicationCommand> = new Collection();
|
||||
let isBotReady = false;
|
||||
|
||||
export class NeonClient extends Client {
|
||||
public commands: Collection<string, Command> = new Collection();
|
||||
public ownerID: string;
|
||||
|
||||
public static logger: pino.Logger = pino({ level: 'trace', prettyPrint: { levelFirst: true, colorize: true }, prettifier: PinoPretty });
|
||||
|
||||
public constructor(ownerID: string, options: ClientOptions) {
|
||||
super(options);
|
||||
|
||||
this.ownerID = ownerID;
|
||||
|
||||
this.once('ready', async () => {
|
||||
const getCommands = await new REST({ version: '9' }).setToken(this.token as string).get(Routes.applicationCommands(this.application!.id)) as APIApplicationCommand[];
|
||||
getCommands.forEach((val) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const command: ApplicationCommand = new ApplicationCommand(this, val);
|
||||
slashCommands.set(command.id, command);
|
||||
});
|
||||
|
||||
this.logger.info('Loading Commands...');
|
||||
|
||||
try {
|
||||
const files = await walkDir(`${path.dirname(require.main!.filename)}/commands`);
|
||||
|
||||
files.forEach(async (file) => {
|
||||
if (file.endsWith('js')) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fileCommand = require(file);
|
||||
|
||||
let command: Command;
|
||||
fileCommand['default'] !== undefined ? command = new fileCommand['default'](this, file) : command = new fileCommand(this, file);
|
||||
|
||||
this.commands.set(command.options.name!, command);
|
||||
|
||||
if (!slashCommands.find((int) => int.name === command.options.name)) {
|
||||
this.logger.debug(`Creating new command ${command.options.name}`);
|
||||
|
||||
const commandOptions: ApplicationCommandOptionData[] = [];
|
||||
|
||||
command.options.args?.forEach((arg) => {
|
||||
commandOptions.push(arg);
|
||||
});
|
||||
|
||||
this.application?.commands.create({
|
||||
name: command.options.name!,
|
||||
description: command.options.shortDescription!,
|
||||
options: commandOptions,
|
||||
});
|
||||
} else {
|
||||
const commandOptions: ApplicationCommandOptionData[] = [];
|
||||
|
||||
command.options.args?.forEach((arg) => {
|
||||
commandOptions.push(arg);
|
||||
});
|
||||
|
||||
const fileCommand: ChatInputApplicationCommandData = {
|
||||
name: command.options.name!,
|
||||
description: command.options.shortDescription!,
|
||||
options: commandOptions,
|
||||
};
|
||||
|
||||
const cacheCommand1 = slashCommands.find((cmd) => cmd.name === fileCommand.name)?.toJSON() as ChatInputApplicationCommandData;
|
||||
const cacheCommand2 = { name: cacheCommand1.name, description: cacheCommand1.description, options: cacheCommand1.options };
|
||||
|
||||
if (!_.isEqual(fileCommand, cacheCommand2)) {
|
||||
this.logger.debug(`Editing command ${fileCommand.name}`);
|
||||
|
||||
this.application?.commands.edit(slashCommands.find((int) => int.name === command.options.name)!, fileCommand);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug(`Loaded command ${command.options.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
slashCommands.forEach((command) => {
|
||||
if (!this.commands.find((cmd) => command.name === cmd.options.name)) {
|
||||
this.application?.commands.delete(command.id);
|
||||
|
||||
this.logger.debug(`Deleting command ${command.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
this.logger.info(`Finished loading commands! Found ${this.commands.size} commands.`);
|
||||
} catch (err) {
|
||||
this.logger.error(`An error has occurred while attempting to load command files! Please see error below\n${(err as Error).message}`);
|
||||
}
|
||||
|
||||
isBotReady = true;
|
||||
});
|
||||
|
||||
this.on('interactionCreate', async (interaction) => {
|
||||
if (!isBotReady) return;
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
const findCommand = this.commands.find((com) => com.options.name === interaction.commandName);
|
||||
if (!findCommand) return;
|
||||
|
||||
if (findCommand.options.ownerOnly && interaction.user.id !== this.ownerID) {
|
||||
await interaction.reply('This command can be ran by the bot owner only!');
|
||||
|
||||
this.logger.warn(`$${interaction.user.username} tried running command ${findCommand.options.name} but doesn't have the permissions to.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((findCommand.options.runIn === 'dms') && interaction.channel?.type !== 'DM') {
|
||||
await interaction.reply('This command can only be ran in DMs!');
|
||||
|
||||
this.logger.warn(`$${interaction.user.username} tried running command ${findCommand.options.name} in a server but the command can only be ran in a DM.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ((findCommand.options.runIn === 'servers') && interaction.channel?.type !== 'GUILD_TEXT') {
|
||||
await interaction.reply('This command can only be ran in a Server!');
|
||||
|
||||
this.logger.warn(`$${interaction.user.username} tried running command ${findCommand.options.name} in a DM but the command can only be ran in a Server.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (interaction.guild) for (let index = 0; index < findCommand.options.requiredBotPermissions!.length; index++) {
|
||||
const permission = findCommand.options.requiredBotPermissions![index]!;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (!interaction.guild.me!.roles.highest.permissions.toArray(true).includes(permission.toString())) {
|
||||
await interaction.reply(`The bot is missing the permission \`${permission}\`! If you believe this is a mistake, please bring it up with a server admin.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.member) if (typeof interaction.member.permissions === 'string') {
|
||||
// TODO!
|
||||
} else {
|
||||
for (let index = 0; index < findCommand.options.requiredBotPermissions!.length; index++) {
|
||||
const permission = findCommand.options.requiredBotPermissions![index]!;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
if (!interaction.member.permissions.toArray(true)?.includes(permission.toString())) {
|
||||
await interaction.reply(`You are missing the permission \`${permission}\`!`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (interaction.user.id !== this.ownerID) {
|
||||
if (!commandCooldowns.has(findCommand.options.name!)) {
|
||||
commandCooldowns.set(findCommand.options.name!, new Collection());
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const timestamps = commandCooldowns.get(findCommand.options.name!);
|
||||
const cooldownAmount = findCommand.options.cooldown! * 1000;
|
||||
|
||||
if (timestamps!.has(interaction.user.id)) {
|
||||
const expirationTime = timestamps!.get(interaction.user.id)! + cooldownAmount;
|
||||
|
||||
if (now < expirationTime) {
|
||||
const timeLeft = (expirationTime - now) / 1000;
|
||||
|
||||
await interaction.reply(`Please wait ${timeLeft} before running ${findCommand.options.name} again!`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
timestamps!.set(interaction.user.id, now);
|
||||
setTimeout(() => timestamps!.delete(interaction.user.id), cooldownAmount);
|
||||
}
|
||||
|
||||
try {
|
||||
this.logger.info(`Command ${findCommand.options.name} is being ran in ${interaction.guild ? interaction.guild.name : 'DMs'} by ${interaction.user.username}`);
|
||||
await findCommand.run(interaction);
|
||||
this.logger.info(`Finished running ${findCommand.options.name} in ${interaction.guild ? interaction.guild.name : `${interaction.user.username} DMs`}`);
|
||||
} catch (e) {
|
||||
this.logger.error(`An error has occurred while running ${findCommand.options.name}!\n${(e as Error).message}`);
|
||||
|
||||
await interaction.reply(`An error has ocurred while running this command. Please pass this error on to the bot developers\n${(e as Error).message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
29
src/lib/structures/command.ts
Normal file
29
src/lib/structures/command.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import _ from 'lodash';
|
||||
import path from 'path';
|
||||
|
||||
import type { Client, CommandInteraction } from 'discord.js';
|
||||
import type { ICommandOptions } from '../utils/types';
|
||||
|
||||
export abstract class Command {
|
||||
public readonly client: Client;
|
||||
public readonly file: string;
|
||||
public readonly options: ICommandOptions;
|
||||
|
||||
protected constructor(client: Client, file: string, options: ICommandOptions) {
|
||||
this.client = client;
|
||||
this.file = file;
|
||||
|
||||
const defaultOptions: ICommandOptions = {
|
||||
cooldown: 5,
|
||||
group: path.basename(path.dirname(this.file)) === 'commands' ? '' : path.basename(path.dirname(this.file)),
|
||||
name: path.basename(this.file, path.extname(this.file)),
|
||||
shortDescription: '',
|
||||
requiredBotPermissions: [],
|
||||
requiredUserPermissions: [],
|
||||
};
|
||||
|
||||
this.options = _.merge(defaultOptions, options);
|
||||
}
|
||||
|
||||
public abstract run(interaction: CommandInteraction): Promise<void> | void;
|
||||
}
|
11
src/lib/utils/augments.ts
Normal file
11
src/lib/utils/augments.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { Collection } from 'discord.js';
|
||||
import type pino from 'pino';
|
||||
import type { Command } from '../structures/command';
|
||||
|
||||
declare module 'discord.js' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface Client {
|
||||
commands: Collection<string, Command>;
|
||||
logger: pino.Logger;
|
||||
}
|
||||
}
|
15
src/lib/utils/types.ts
Normal file
15
src/lib/utils/types.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import type { ApplicationCommandOptionData, PermissionResolvable } from 'discord.js';
|
||||
|
||||
export interface ICommandOptions {
|
||||
args?: ApplicationCommandOptionData[];
|
||||
cooldown?: number;
|
||||
extendedDescription?: string;
|
||||
group?: string;
|
||||
name?: string;
|
||||
ownerOnly?: boolean;
|
||||
requiredBotPermissions?: PermissionResolvable[]
|
||||
requiredUserPermissions?: PermissionResolvable[]
|
||||
runIn?: 'both' | 'dms' | 'servers';
|
||||
shortDescription: string;
|
||||
usage?: string;
|
||||
}
|
63
src/lib/utils/utils.ts
Normal file
63
src/lib/utils/utils.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export const walkDir = async (dir: string): Promise<string[]> => {
|
||||
let results: string[] = [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(dir, (err, list) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
let pending = list.length;
|
||||
|
||||
if (!pending) return resolve(results);
|
||||
|
||||
list.forEach((file) => {
|
||||
file = path.resolve(dir, file);
|
||||
|
||||
fs.stat(file, async (err, stat) => {
|
||||
if (err) return reject(err);
|
||||
|
||||
if (stat && stat.isDirectory()) {
|
||||
try {
|
||||
const dir = await walkDir(file);
|
||||
|
||||
results = results.concat(dir as unknown as string);
|
||||
|
||||
if (!--pending) resolve(results);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
} else {
|
||||
results.push(file);
|
||||
if (!--pending) resolve(results);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type Builtin = Date | Error | Function | Primitives | RegExp;
|
||||
export type DeepRequired<T> = T extends Builtin
|
||||
? NonNullable<T>
|
||||
: T extends Map<infer K, infer V>
|
||||
? Map<DeepRequired<K>, DeepRequired<V>>
|
||||
: T extends ReadonlyMap<infer K, infer V>
|
||||
? ReadonlyMap<DeepRequired<K>, DeepRequired<V>>
|
||||
: T extends WeakMap<infer K, infer V>
|
||||
? WeakMap<DeepRequired<K>, DeepRequired<V>>
|
||||
: T extends Set<infer U>
|
||||
? Set<DeepRequired<U>>
|
||||
: T extends ReadonlySet<infer U>
|
||||
? ReadonlySet<DeepRequired<U>>
|
||||
: T extends WeakSet<infer U>
|
||||
? WeakSet<DeepRequired<U>>
|
||||
: T extends Promise<infer U>
|
||||
? Promise<DeepRequired<U>>
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
: T extends {}
|
||||
? { [K in keyof T]-?: DeepRequired<T[K]> }
|
||||
: NonNullable<T>;
|
||||
export type Primitives = bigint | boolean | null | number | string | symbol | undefined;
|
48
tsconfig.json
Normal file
48
tsconfig.json
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"lib": [
|
||||
"ES2021",
|
||||
"DOM"
|
||||
],
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist",
|
||||
"removeComments": false,
|
||||
"target": "ES2021",
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"strict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"disableSizeLimit": true,
|
||||
"explainFiles": false,
|
||||
"extendedDiagnostics": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"listEmittedFiles": false,
|
||||
"listFiles": false,
|
||||
"newLine": "lf",
|
||||
"noEmitOnError": false,
|
||||
"preserveConstEnums": true,
|
||||
"traceResolution": false,
|
||||
"moduleResolution": "Node"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
Reference in a new issue