feat: Added Discord Bot && Added Upload Service
This commit is contained in:
parent
cf39c03ba3
commit
27ff15a8aa
|
@ -48,6 +48,19 @@ export interface IConfigKeys {
|
|||
KEYCLOAK_REDIRECT_URI: string
|
||||
KEYCLOAK_AUTH_SERVER_URL: string
|
||||
KEYCLOAK_REALM: string
|
||||
R2_CLIENT_ID: string
|
||||
R2_CLIENT_SECRET: string
|
||||
R2_BUCKET_NAME: string
|
||||
R2_BUCKET_REGION: string
|
||||
R2_BUCKET_ENDPOINT: string
|
||||
R2_BUCKET_URL: string
|
||||
R2_BUCKET_FOLDER: string
|
||||
DISCORD_BOT_TOKEN: string
|
||||
DISCORD_BOT_CLIENT_ID: number
|
||||
DISCORD_BOT_CLIENT_SECRET: string
|
||||
DISCORD_SERVER_ID: number
|
||||
DISCORD_WEBHOOK_URL: string
|
||||
DISCORD_SELF_ID: number
|
||||
}
|
||||
|
||||
export default class ConfigStoreFactory {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Request, Response } from 'express'
|
|||
import { makeResponse } from '../libs'
|
||||
|
||||
export default class DiscordController extends DiscordService {
|
||||
public getProfile = async (req: Request, res: Response) => {
|
||||
public getActivity = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const data = await this.activity()
|
||||
res.send(makeResponse(data))
|
||||
|
@ -22,4 +22,22 @@ export default class DiscordController extends DiscordService {
|
|||
res.send(makeResponse(error.message, {}, 'Failed', true))
|
||||
}
|
||||
}
|
||||
|
||||
public getProfile = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const data = await this.profile()
|
||||
res.send(makeResponse(data))
|
||||
} catch (error: any) {
|
||||
res.send(makeResponse(error.message, {}, 'Failed', true))
|
||||
}
|
||||
}
|
||||
|
||||
public getPresence = async (req: Request, res: Response) => {
|
||||
try {
|
||||
const data = await this.presence()
|
||||
res.send(makeResponse(data))
|
||||
} catch (error: any) {
|
||||
res.send(makeResponse(error.message, {}, 'Failed', true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { NextFunction, Request, Response } from 'express'
|
||||
import { ModRequest } from '../types'
|
||||
import Uploader from '../services/upload.service'
|
||||
|
||||
export default class UploadController extends Uploader {
|
||||
public upload = async (
|
||||
req: ModRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { file } = req
|
||||
if (!file) {
|
||||
const error = new Error('Please upload a file')
|
||||
next(error)
|
||||
}
|
||||
const data = await this.uploadS(file)
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
message: 'File uploaded successfully',
|
||||
data,
|
||||
})
|
||||
} catch (error: any) {
|
||||
next(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import { S3Client, PutObjectCommand, S3ClientConfig } from '@aws-sdk/client-s3'
|
||||
import { configKeys } from '..'
|
||||
|
||||
type IUploaderServerType = 's3' | 'r2' | 'safe'
|
||||
|
||||
export default class UploaderService {
|
||||
private static _s3Client
|
||||
private static _s3Opts
|
||||
private static _clientType
|
||||
|
||||
constructor(bucket, clientType: IUploaderServerType = 's3') {
|
||||
const options = {
|
||||
bucket,
|
||||
}
|
||||
UploaderService._s3Opts = options
|
||||
UploaderService._clientType = clientType
|
||||
|
||||
if (clientType === 's3') {
|
||||
const s3ClientOpts: S3ClientConfig = {
|
||||
region: configKeys.S3_BUCKET_REGION || '',
|
||||
endpoint: configKeys.S3_BUCKET_ENDPOINT || '',
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: configKeys.S3_CLIENT_ID || '',
|
||||
secretAccessKey: configKeys.S3_CLIENT_SECRET || '',
|
||||
},
|
||||
}
|
||||
const client = new S3Client(s3ClientOpts)
|
||||
UploaderService._s3Client = client
|
||||
} else if (clientType === 'r2') {
|
||||
const s3ClientOpts: S3ClientConfig = {
|
||||
region: configKeys.R2_BUCKET_REGION || '',
|
||||
endpoint: configKeys.R2_BUCKET_ENDPOINT || '',
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: configKeys.R2_CLIENT_ID || '',
|
||||
secretAccessKey: configKeys.R2_CLIENT_SECRET || '',
|
||||
},
|
||||
}
|
||||
const client = new S3Client(s3ClientOpts)
|
||||
UploaderService._s3Client = client
|
||||
}
|
||||
}
|
||||
|
||||
async uploadFile(
|
||||
entity: string,
|
||||
id: string,
|
||||
file: Buffer,
|
||||
fileType: string,
|
||||
acl?: string
|
||||
) {
|
||||
const key = [entity, id].join('/')
|
||||
const uploadParams = {
|
||||
Bucket: UploaderService._s3Opts.bucket,
|
||||
ACL: acl,
|
||||
ContentType: fileType,
|
||||
Body: file,
|
||||
Key: key,
|
||||
}
|
||||
await UploaderService._s3Client.send(new PutObjectCommand(uploadParams))
|
||||
return {
|
||||
url: configKeys.S3_BUCKET_URL,
|
||||
bucket_name: configKeys.S3_BUCKET_NAME,
|
||||
folder: configKeys.S3_BUCKET_FOLDER,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import { REST } from '@discordjs/rest'
|
||||
import {
|
||||
WebSocketManager,
|
||||
WebSocketShardStatus,
|
||||
WebSocketShardEvents,
|
||||
} from '@discordjs/ws'
|
||||
import {
|
||||
GatewayDispatchEvents,
|
||||
GatewayIntentBits,
|
||||
Client,
|
||||
} from '@discordjs/core'
|
||||
import { configKeys } from '..'
|
||||
|
||||
export default class DiscordBotClient {
|
||||
public static _client: Client
|
||||
private static _rest: REST
|
||||
public static _gateway: WebSocketManager
|
||||
private static _currentPresence
|
||||
|
||||
public static init() {
|
||||
this._rest = new REST({ version: '10' }).setToken(
|
||||
configKeys.DISCORD_BOT_TOKEN
|
||||
)
|
||||
this._gateway = new WebSocketManager({
|
||||
token: configKeys.DISCORD_BOT_TOKEN,
|
||||
intents: GatewayIntentBits.GuildPresences,
|
||||
rest: this._rest,
|
||||
shardCount: 1,
|
||||
shardIds: [0],
|
||||
})
|
||||
this._client = new Client({
|
||||
rest: this._rest,
|
||||
gateway: this._gateway,
|
||||
})
|
||||
}
|
||||
|
||||
public static getPresence = async () => {
|
||||
return this._currentPresence
|
||||
}
|
||||
|
||||
// public static getShard = async () => {
|
||||
// return this._gateway.on(WebSocketShardEvents.Dispatch, (data) => {
|
||||
// console.log(data)
|
||||
// })
|
||||
// }
|
||||
|
||||
public static setPresence = async (presence: any) => {
|
||||
this._currentPresence = presence
|
||||
}
|
||||
|
||||
public static getUser = async (id: number) => {
|
||||
return this._client.rest.get(`/users/${id}`)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { GatewayDispatchEvents } from '@discordjs/core'
|
||||
|
||||
import DiscordBotClient from './discord_bot.factory'
|
||||
import { configKeys } from '..'
|
||||
|
||||
export default async () => {
|
||||
DiscordBotClient.init()
|
||||
|
||||
const DiscordBot = DiscordBotClient._client
|
||||
|
||||
DiscordBot.on(GatewayDispatchEvents.PresenceUpdate, async ({ data }) => {
|
||||
if (data.user.id == String(configKeys.DISCORD_SELF_ID)) {
|
||||
DiscordBotClient.setPresence(data)
|
||||
}
|
||||
})
|
||||
|
||||
const selfInfo = await DiscordBot.api.users.getCurrent()
|
||||
DiscordBot.once(GatewayDispatchEvents.Ready, async () => {
|
||||
console.log(
|
||||
'🔮 ' +
|
||||
selfInfo.username +
|
||||
'#' +
|
||||
selfInfo.discriminator +
|
||||
' : Gateway Connected!'
|
||||
)
|
||||
})
|
||||
|
||||
DiscordBotClient._gateway.connect()
|
||||
}
|
|
@ -1,82 +1,43 @@
|
|||
import { S3Client } from '@aws-sdk/client-s3'
|
||||
import multer from 'multer'
|
||||
import multerS3 from 'multer-s3'
|
||||
import path from 'path'
|
||||
import napiNanoId from 'napi-nanoid'
|
||||
import { configKeys } from '..'
|
||||
interface UploadFactoryOptions {
|
||||
region: string
|
||||
bucket: string
|
||||
accessKey: string
|
||||
secretKey: string
|
||||
}
|
||||
import { nanoid } from 'napi-nanoid'
|
||||
interface UploaderConfig {
|
||||
folder: string
|
||||
mimeFilters: string[]
|
||||
}
|
||||
export class UploadFactory {
|
||||
private options: UploadFactoryOptions & Partial<UploaderConfig>
|
||||
private s3Client: S3Client
|
||||
|
||||
constructor(options?: Partial<UploadFactoryOptions>) {
|
||||
this.options = {
|
||||
bucket: options?.bucket || configKeys.S3_BUCKET_NAME || '',
|
||||
region: options?.region || configKeys.S3_BUCKET_REGION || '',
|
||||
accessKey: options?.accessKey || configKeys.S3_CLIENT_ID || '',
|
||||
secretKey: options?.secretKey || configKeys.S3_CLIENT_SECRET || '',
|
||||
}
|
||||
|
||||
this.s3Client = new S3Client({
|
||||
region: this.options.region,
|
||||
credentials: {
|
||||
accessKeyId: this.options.accessKey,
|
||||
secretAccessKey: this.options.secretKey,
|
||||
public getUploader(config?: UploaderConfig) {
|
||||
const storage = multer.memoryStorage({
|
||||
filename: (req, file, cb) => {
|
||||
const fileName =
|
||||
file.originalname +
|
||||
'-' +
|
||||
nanoid() +
|
||||
path.extname(file.originalname)
|
||||
cb(null, fileName)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
public get serviceName(): string {
|
||||
return 'aws:' + this.options.bucket
|
||||
}
|
||||
|
||||
public getUploader(
|
||||
config?: Partial<UploadFactoryOptions & UploaderConfig>
|
||||
) {
|
||||
const finalOptions = {
|
||||
...this.options,
|
||||
...(config || {}),
|
||||
}
|
||||
|
||||
return multer({
|
||||
fileFilter(_req, file, cb) {
|
||||
const res = finalOptions.mimeFilters
|
||||
? finalOptions.mimeFilters.includes(file.mimetype)
|
||||
: true
|
||||
cb(null, res)
|
||||
},
|
||||
storage: multerS3({
|
||||
s3: this.s3Client,
|
||||
bucket: this.options.bucket,
|
||||
acl: 'public-read',
|
||||
contentType: multerS3.AUTO_CONTENT_TYPE,
|
||||
metadata: function (_req, file, cb) {
|
||||
const meta = {
|
||||
fieldName: file.fieldname,
|
||||
fileName: file.originalname,
|
||||
uploadOn: new Date().toISOString(),
|
||||
storage: storage,
|
||||
fileFilter: (req, file, cb) => {
|
||||
const fileName =
|
||||
file.originalname.split('.')[
|
||||
file.originalname.split('.').length - 2
|
||||
] +
|
||||
'-' +
|
||||
nanoid() +
|
||||
path.extname(file.originalname)
|
||||
file.newName = fileName
|
||||
if (config?.mimeFilters?.length) {
|
||||
if (config.mimeFilters.includes(file.mimetype)) {
|
||||
cb(null, true)
|
||||
} else {
|
||||
cb(new Error('File type not allowed'), false)
|
||||
}
|
||||
cb(null, meta)
|
||||
},
|
||||
key: function (_req, file, cb) {
|
||||
const key: string[] = []
|
||||
if (finalOptions.folder) key.push(finalOptions.folder)
|
||||
const value = napiNanoId.nanoid()
|
||||
const ext = path.extname(file.originalname)
|
||||
key.push(value + ext)
|
||||
|
||||
cb(null, key.join('/'))
|
||||
},
|
||||
}),
|
||||
} else {
|
||||
cb(null, true)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import routes from './routes'
|
|||
import { errorHandler, notFoundHandler } from './libs'
|
||||
import pkg from './package.json' assert { type: 'json' }
|
||||
import configStore, { IConfigKeys } from './configs'
|
||||
import discordBotConnect from './helpers/discord_bot_client'
|
||||
|
||||
export const app: express.Application = express()
|
||||
|
||||
|
@ -23,6 +24,8 @@ console.log(isDev ? '🚀 Production Mode' : '👷 Development Mode')
|
|||
const configs = new configStore(isDev)
|
||||
const configKeys: IConfigKeys = (await configs.getConfigStore()) as IConfigKeys
|
||||
|
||||
discordBotConnect()
|
||||
|
||||
app.use(cors())
|
||||
app.use(helmet())
|
||||
app.use(morgan('dev'))
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.226.0",
|
||||
"@discordjs/core": "^0.6.0",
|
||||
"@discordjs/rest": "^1.7.1",
|
||||
"@types/express": "^4.17.14",
|
||||
"axios": "^1.2.1",
|
||||
"cors": "^2.8.5",
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { UploadFactory } from '../../helpers/upload.factory'
|
||||
import { Router } from 'express'
|
||||
import UploadController from '../../controllers/upload.controller'
|
||||
|
||||
const router = Router()
|
||||
const uploadController = new UploadController()
|
||||
const uploaderFactory = new UploadFactory()
|
||||
|
||||
router.post(
|
||||
'/',
|
||||
uploaderFactory
|
||||
.getUploader({
|
||||
mimeFilters: [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'application/json',
|
||||
],
|
||||
})
|
||||
.single('file'),
|
||||
uploadController.upload as any
|
||||
)
|
||||
export default router
|
|
@ -2,9 +2,13 @@ import { Router } from 'express'
|
|||
import DiscordController from '../../controllers/discord.controller'
|
||||
|
||||
const router = Router()
|
||||
const { getProfile, getBanner } = new DiscordController()
|
||||
const { getProfile, getBanner, getActivity, getPresence } =
|
||||
new DiscordController()
|
||||
|
||||
router.get('/profile', getProfile)
|
||||
router.get('/profile', getActivity)
|
||||
router.get('/banner', getBanner)
|
||||
|
||||
router.get('/v2/profile', getProfile)
|
||||
router.get('/v2/activity', getPresence)
|
||||
|
||||
export default router
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { configKeys } from '..'
|
||||
import axios from '../helpers/axios_client'
|
||||
import DiscordBotClient from '../helpers/discord_bot.factory'
|
||||
|
||||
export default class DiscordService {
|
||||
public activity = async () => {
|
||||
|
@ -14,4 +16,12 @@ export default class DiscordService {
|
|||
)
|
||||
return data
|
||||
}
|
||||
|
||||
public profile = async () => {
|
||||
return DiscordBotClient.getUser(configKeys.DISCORD_SELF_ID)
|
||||
}
|
||||
|
||||
public presence = async () => {
|
||||
return DiscordBotClient.getPresence()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import UploaderService from '../data/uploader.service'
|
||||
import { configKeys } from '..'
|
||||
|
||||
const uploaderService = new UploaderService(configKeys.S3_BUCKET_NAME, 'r2')
|
||||
|
||||
export default class Uploader {
|
||||
public uploadS = async (file: any) => {
|
||||
await uploaderService.uploadFile(
|
||||
configKeys.S3_BUCKET_FOLDER,
|
||||
file.newName,
|
||||
file.buffer,
|
||||
file.mimetype,
|
||||
'public-read'
|
||||
)
|
||||
return {
|
||||
url:
|
||||
configKeys.S3_BUCKET_URL +
|
||||
'/' +
|
||||
configKeys.S3_BUCKET_NAME +
|
||||
'/' +
|
||||
configKeys.S3_BUCKET_FOLDER +
|
||||
'/' +
|
||||
file.newName,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,4 +10,5 @@ export interface PaginationType {
|
|||
|
||||
export interface ModRequest extends Request {
|
||||
user: any
|
||||
file: any
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
Binary file not shown.
After Width: | Height: | Size: 161 KiB |
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"defaultPrefix": "d!",
|
||||
"disabledCommandCatagories": [],
|
||||
"githubLink": "https://github.com/cktsun1031/DraconianBot",
|
||||
"name": "Hanako",
|
||||
"ownerId": "457039372009865226",
|
||||
"useShards": false
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"error": "❌",
|
||||
"success": "✅",
|
||||
"warning": "⚠️"
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { GuildConfig } from '../src/sturctures/database'
|
||||
|
||||
const MYGuildConfig: GuildConfig = {
|
||||
prefix: 'd!',
|
||||
serverId: '636830997790326794',
|
||||
commands: {
|
||||
global: {
|
||||
disabled: [],
|
||||
disabledCatagories: [],
|
||||
customCooldown: [],
|
||||
},
|
||||
userDisabled: [],
|
||||
roleDisabled: [],
|
||||
channelDisabled: [],
|
||||
},
|
||||
antiSpam: {
|
||||
enabled: true,
|
||||
whitelistedUsers: [],
|
||||
whitelistedRoles: [],
|
||||
whitelistedChannels: [],
|
||||
inviteLinks: {
|
||||
enabled: true,
|
||||
whitelistedUsers: [],
|
||||
whitelistedRoles: [],
|
||||
whitelistedChannels: [],
|
||||
},
|
||||
mentions: {
|
||||
enabled: true,
|
||||
maxmiumCheck: {
|
||||
enabled: true,
|
||||
value: 5,
|
||||
},
|
||||
publicRoleCheck: true,
|
||||
whitelistedUsers: [],
|
||||
whitelistedRoles: [],
|
||||
whitelistedChannels: [],
|
||||
},
|
||||
},
|
||||
thread: {
|
||||
listen: true,
|
||||
},
|
||||
snipe: {
|
||||
channelDisabled: [],
|
||||
},
|
||||
}
|
||||
|
||||
export default MYGuildConfig
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "bot",
|
||||
"version": "1.0.0",
|
||||
"description": "Hanako's Egg",
|
||||
"main": "src/index.ts",
|
||||
"author": "BRAVO68WEB",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/gifencoder": "^2.0.1",
|
||||
"@types/node": "^20.1.3",
|
||||
"@types/pidusage": "^2.0.2",
|
||||
"@types/string-similarity": "^4.0.0",
|
||||
"nodemon": "^2.0.22",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/core": "^0.6.0",
|
||||
"@discordjs/rest": "^1.7.1",
|
||||
"@esbuild-kit/esm-loader": "^2.5.5",
|
||||
"@types/xml2js": "^0.4.11",
|
||||
"axios": "^1.4.0",
|
||||
"canvas": "^2.11.2",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.7",
|
||||
"discord.js": "^14.11.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"gifencoder": "^2.0.1",
|
||||
"glob": "^10.2.3",
|
||||
"graphql": "^16.6.0",
|
||||
"graphql-request": "^6.0.0",
|
||||
"napi-nanoid": "^0.2.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"pidusage": "^3.0.2",
|
||||
"redis": "^4.6.6",
|
||||
"string-similarity": "^4.0.4",
|
||||
"xml2js": "^0.5.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "nodemon --watch 'src/**/*.ts' --exec node --loader @esbuild-kit/esm-loader src/index.ts",
|
||||
"start": "node --loader @esbuild-kit/esm-loader src/index.ts"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import { Client, Collection, GatewayIntentBits, Partials } from 'discord.js'
|
||||
|
||||
// import './http/server';
|
||||
|
||||
import { loadSlashCommand, loadTextCommand } from './loaders/command'
|
||||
import { loadDiscordEvent } from './loaders/event'
|
||||
import type { SlashCommand, TextCommand } from './sturctures/command'
|
||||
import { hgqlInit } from './utils/database'
|
||||
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.DirectMessages,
|
||||
GatewayIntentBits.MessageContent,
|
||||
],
|
||||
allowedMentions: { parse: ['users', 'roles'], repliedUser: false },
|
||||
partials: [
|
||||
Partials.User,
|
||||
Partials.Channel,
|
||||
Partials.Message,
|
||||
Partials.GuildMember,
|
||||
],
|
||||
})
|
||||
|
||||
client.commands = new Collection()
|
||||
client.commandsCatagories = new Collection()
|
||||
client.aliases = new Collection()
|
||||
client.slashcommands = new Collection()
|
||||
|
||||
await loadDiscordEvent(client)
|
||||
await loadTextCommand(client)
|
||||
|
||||
await hgqlInit()
|
||||
|
||||
await client.login(process.env.TOKEN)
|
||||
|
||||
await loadSlashCommand(client, process.env.CLIENT_ID, process.env.TOKEN)
|
||||
|
||||
export default client
|
||||
|
||||
// declare types.
|
||||
declare module 'discord.js' {
|
||||
export interface Client {
|
||||
aliases: Collection<string, string>
|
||||
commands: Collection<string, TextCommand>
|
||||
slashcommands: Collection<string, SlashCommand>
|
||||
commandsCatagories: Collection<string, string[]>
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
import { EmbedBuilder, version as discordJsVersion } from 'discord.js'
|
||||
import pidusage from 'pidusage'
|
||||
|
||||
import { version as packageVersion } from '../../../../package.json'
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { parseMsToVisibleText } from '../../../utils/formatters'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'botinfo',
|
||||
description: 'Check Bot information.',
|
||||
directMessageAllowed: true,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const apiDelayMS = Math.round(message.client.ws.ping)
|
||||
const osStats = await pidusage(process.pid)
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Bot's Information")
|
||||
.setDescription(
|
||||
'Hello! I am Draconian Bot, honored to see you here. Information below is my body analysis :)'
|
||||
)
|
||||
.addFields([
|
||||
{
|
||||
name: 'Version',
|
||||
value: `\`${packageVersion}\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Discord.js',
|
||||
value: `\`${discordJsVersion}\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Node',
|
||||
value: `\`${process.version}\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'CPU',
|
||||
value: `\`${Math.round(Number(osStats.cpu.toFixed(2)))}%\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Memory',
|
||||
value: `\`${Math.round(
|
||||
osStats.memory / (1024 * 1024)
|
||||
)}MB\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Uptime',
|
||||
value: `\`${parseMsToVisibleText(message.client.uptime)}\``,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Network Delay',
|
||||
value: `\`${apiDelayMS} ms\``,
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
import dayjs from 'dayjs'
|
||||
import type { TextChannel, ThreadChannel, VoiceChannel } from 'discord.js'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'channelinfo',
|
||||
description: "Check server's channel information.",
|
||||
directMessageAllowed: false,
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
run: async ({ message, args }) => {
|
||||
const { guild, channel, mentions, member } = message
|
||||
|
||||
if (!guild || !member) return
|
||||
|
||||
const targetNameId = args[0]
|
||||
|
||||
let targetChannel = mentions.channels.first()
|
||||
|
||||
if (!targetChannel && targetNameId) {
|
||||
const fetchedChannel = guild.channels.cache.get(targetNameId)
|
||||
if (fetchedChannel) targetChannel = fetchedChannel
|
||||
else {
|
||||
const name = String(targetNameId).toLowerCase()
|
||||
const fetchedChannelByKW = guild.channels.cache.find((ur) =>
|
||||
ur.name.toLowerCase().includes(name)
|
||||
)
|
||||
if (fetchedChannelByKW) targetChannel = fetchedChannelByKW
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetChannel) targetChannel = channel
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
if (targetChannel.isTextBased()) {
|
||||
const textChannel = targetChannel as TextChannel
|
||||
|
||||
embed.setTitle(`${textChannel.name}'s information:`).addFields([
|
||||
{ name: 'ID', value: textChannel.id },
|
||||
{
|
||||
// eslint-disable-next-line
|
||||
name: 'Created on',
|
||||
// eslint-disable-next-line
|
||||
value: dayjs(textChannel.createdAt.getTime()).format(
|
||||
'DD/MM/YYYY'
|
||||
),
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
if (textChannel.parent?.name) {
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Parent',
|
||||
value: textChannel.parent.name,
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Position',
|
||||
value: textChannel.rawPosition.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'NSFW',
|
||||
value: textChannel.nsfw ? 'YES' : 'NO',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Viewable',
|
||||
value: textChannel.viewable ? 'YES' : 'NO',
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
embed.setFooter({
|
||||
iconURL: guild.iconURL() ?? '',
|
||||
text: `Shard ID: ${guild.shardId}`,
|
||||
})
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (targetChannel.isThread()) {
|
||||
const voiceChannel = targetChannel as ThreadChannel
|
||||
|
||||
embed.setTitle(`${voiceChannel.name}'s information:`).addFields([
|
||||
{ name: 'ID', value: voiceChannel.id },
|
||||
{
|
||||
name: 'Created on',
|
||||
value: dayjs(voiceChannel.createdAt?.getTime()).format(
|
||||
'DD/MM/YYYY'
|
||||
),
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
if (voiceChannel.parent?.name) {
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Parent',
|
||||
value: voiceChannel.parent.name,
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Joinable',
|
||||
value: voiceChannel.joinable ? 'YES' : 'NO',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Locked',
|
||||
value: voiceChannel.locked ? 'YES' : 'NO',
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
embed.setFooter({
|
||||
iconURL: guild.iconURL() ?? '',
|
||||
text: `Shard ID: ${guild.shardId}`,
|
||||
})
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
|
||||
if (targetChannel.isVoiceBased()) {
|
||||
const voiceChannel = targetChannel as unknown as VoiceChannel
|
||||
|
||||
embed.setTitle(`${voiceChannel.name}'s information:`).addFields([
|
||||
{ name: 'ID', value: voiceChannel.id },
|
||||
{
|
||||
name: 'Created on',
|
||||
value: dayjs(voiceChannel.createdAt.getTime()).format(
|
||||
'DD/MM/YYYY'
|
||||
),
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
if (voiceChannel.parent?.name) {
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Parent',
|
||||
value: voiceChannel.parent.name,
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Position',
|
||||
value: voiceChannel.rawPosition.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Joinable',
|
||||
value: voiceChannel.joinable ? 'YES' : 'NO',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Speakable',
|
||||
value: voiceChannel.speakable ? 'YES' : 'NO',
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
embed.setFooter({
|
||||
iconURL: guild.iconURL() ?? '',
|
||||
text: `Shard ID: ${guild.shardId}`,
|
||||
})
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import type { TextChannel } from 'discord.js'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import config from '../../../../config/bot.json'
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { getCommandHelpInfo } from '../../../utils/cmds'
|
||||
import { callbackEmbed } from '../../../utils/messages'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'help',
|
||||
description: 'Get information or help from the bot.',
|
||||
directMessageAllowed: true,
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
run: async ({ message, args }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const { client, channel, guildId } = message
|
||||
|
||||
if (args[0]) {
|
||||
let cmd: TextCommand | undefined
|
||||
|
||||
const commandMatching = client.commands.get(args[0])
|
||||
const aliasesMatching = client.aliases.get(args[0])
|
||||
|
||||
// Fetch command destination.
|
||||
if (commandMatching) {
|
||||
cmd = commandMatching
|
||||
} else if (aliasesMatching) {
|
||||
cmd = client.commands.get(aliasesMatching)
|
||||
} else {
|
||||
const cEmbed = callbackEmbed({
|
||||
text: 'Command requested does not exist!',
|
||||
color: 'Red',
|
||||
mode: 'error',
|
||||
})
|
||||
await message.reply({
|
||||
embeds: [cEmbed],
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (cmd) {
|
||||
const helpInfo = getCommandHelpInfo(cmd)
|
||||
await message.reply({
|
||||
embeds: [helpInfo],
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const commandsCatagories = client.commandsCatagories
|
||||
|
||||
embed.setDescription(
|
||||
`Hello🙋♂️!\nOur source code: [Here](${config.githubLink})\nTurely appreciate that you are supporting us.`
|
||||
)
|
||||
|
||||
for (const catagory of commandsCatagories) {
|
||||
if (catagory[0].toLocaleLowerCase() === 'nsfw') {
|
||||
if ((channel as TextChannel).nsfw) {
|
||||
catagory[0] += ' THIS CHANNEL ONLY'
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
const text = catagory[1]
|
||||
.map((index: string) => {
|
||||
let _text: string | undefined
|
||||
const cmd = client.commands.get(index)
|
||||
if (
|
||||
guildId ||
|
||||
(cmd && cmd.data.directMessageAllowed === true)
|
||||
) {
|
||||
_text = `\`${index}\``
|
||||
}
|
||||
|
||||
return _text
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
|
||||
if (text.length > 0) {
|
||||
embed.addFields([{ name: catagory[0], value: text }])
|
||||
}
|
||||
}
|
||||
|
||||
const avatarURL = client.user.defaultAvatarURL
|
||||
|
||||
embed.setTitle('Bot Assistance Centre').setFooter({
|
||||
text: `© ${new Date().getFullYear()} ${config.name}`,
|
||||
iconURL: avatarURL,
|
||||
})
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { isDev } from '../../../utils/constants'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'invite',
|
||||
description: 'Invite me to your server!',
|
||||
directMessageAllowed: true,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const { client } = message
|
||||
|
||||
if (isDev || client.application.botPublic) {
|
||||
const link = `https://discord.com/api/oauth2/authorize?client_id=${client.application.id}&permissions=1636381879799&scope=applications.commands%20bot`
|
||||
|
||||
const embed = new EmbedBuilder().setDescription(
|
||||
`Invite to your server: [HERE](${link})`
|
||||
)
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'ping',
|
||||
description: "Check Bot's network delay.",
|
||||
directMessageAllowed: true,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const apiDelayMS = Math.round(message.client.ws.ping)
|
||||
const messageDelayMS = Date.now() - message.createdTimestamp
|
||||
|
||||
const embed = new EmbedBuilder().setDescription(
|
||||
`Action Delay: \`${messageDelayMS}ms\`\nAPI Delay: \`${apiDelayMS}ms\``
|
||||
)
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import dayjs from 'dayjs'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'serverinfo',
|
||||
description: "Check server's stats and information.",
|
||||
directMessageAllowed: false,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const { guild } = message
|
||||
|
||||
if (!guild) return
|
||||
|
||||
const owner = await guild.fetchOwner()
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setThumbnail(guild.iconURL() ?? '')
|
||||
.setTitle(`${guild.name}'s information:`)
|
||||
.addFields([
|
||||
{ name: 'Owner', value: owner.user.tag, inline: true },
|
||||
|
||||
{
|
||||
name: 'Created on',
|
||||
value: dayjs(guild.createdAt.getTime()).format(
|
||||
'DD/MM/YYYY'
|
||||
),
|
||||
inline: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'User Count',
|
||||
value: guild.memberCount.toString(),
|
||||
inline: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Bot Count',
|
||||
value: guild.members.cache
|
||||
.filter((mb) => mb.user.bot)
|
||||
.size.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Roles',
|
||||
value: guild.members.cache
|
||||
.filter((mb) => mb.user.bot)
|
||||
.size.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Roles',
|
||||
value: guild.roles.cache.size.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Emojis',
|
||||
value: guild.channels.cache.size.toString(),
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Verification Level',
|
||||
value: guild.verificationLevel.toString(),
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
.setFooter({
|
||||
text: `Shard ID: ${guild.shardId}`,
|
||||
})
|
||||
|
||||
if (guild.description) embed.setDescription(guild.description)
|
||||
|
||||
if (guild.premiumSubscriptionCount) {
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Total Boosts',
|
||||
value: guild.premiumSubscriptionCount.toString(),
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
if (guild.vanityURLCode) {
|
||||
const url = `https://discord.gg/${guild.vanityURLCode}`
|
||||
embed.addFields([
|
||||
{ name: 'Vanity Invite URL', value: `[${url}](${url})` },
|
||||
])
|
||||
}
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { parseMsToVisibleText } from '../../../utils/formatters'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'uptime',
|
||||
description: 'Check Bot uptime duration.',
|
||||
directMessageAllowed: true,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const instanceBoot = message.client.uptime
|
||||
const bootTimeMS = Math.round((Date.now() - instanceBoot) / 1000)
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle("Bot's Uptime")
|
||||
.setDescription(
|
||||
`\`${parseMsToVisibleText(
|
||||
instanceBoot
|
||||
)}\`\n**Booted at** <t:${bootTimeMS}>`
|
||||
)
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import axios from 'axios'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'cat',
|
||||
description: 'Fetch cat image.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 6 * 1000,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const url = 'https://api.thecatapi.com/v1/images/search?format=json'
|
||||
|
||||
try {
|
||||
const response = await axios.get(url)
|
||||
const responseData = response.data
|
||||
|
||||
const data: {
|
||||
id: string
|
||||
url: string
|
||||
} = responseData[0]
|
||||
|
||||
embed
|
||||
.setTitle('Cat here')
|
||||
.setImage(data.url)
|
||||
.setFooter({
|
||||
text: `ID: ${data.id}`,
|
||||
})
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
} catch {
|
||||
embed.setDescription('Error occured when fetching meme content.')
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import axios from 'axios'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'dog',
|
||||
description: 'Fetch dog image.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 6 * 1000,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const url = 'https://dog.ceo/api/breeds/image/random'
|
||||
|
||||
try {
|
||||
const response = await axios.get(url)
|
||||
const responseData: {
|
||||
status: string
|
||||
message: string
|
||||
} = response.data
|
||||
|
||||
if (responseData.status === 'success') {
|
||||
embed.setTitle('Dog here').setImage(responseData.message)
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
embed.setDescription('Error occured when fetching meme content.')
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
import axios from 'axios'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'joke',
|
||||
description: 'Get some jokes.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 6 * 1000,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const url = 'https://v2.jokeapi.dev/joke/Any?type=single'
|
||||
|
||||
try {
|
||||
const response = await axios.get(url)
|
||||
const responseData: {
|
||||
id: string
|
||||
joke: string
|
||||
error: boolean
|
||||
category: string
|
||||
} = response.data
|
||||
|
||||
if (responseData.error) throw 0
|
||||
|
||||
embed.setTitle(responseData.joke).setFooter({
|
||||
text: `Category: ${responseData.category} • ID: ${responseData.id}`,
|
||||
})
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
} catch {
|
||||
embed.setDescription('Error occured when fetching meme content.')
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import axios from 'axios'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'meme',
|
||||
description: "Fetch meme's image.",
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 6 * 1000,
|
||||
},
|
||||
run: async ({ message }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const url = 'https://meme-api.herokuapp.com/gimme'
|
||||
|
||||
try {
|
||||
const response = await axios.get(url)
|
||||
const responseData: {
|
||||
url: string
|
||||
title: string
|
||||
postLink: string
|
||||
subreddit: string
|
||||
} = response.data
|
||||
|
||||
embed
|
||||
.setTitle(responseData.title)
|
||||
.setImage(responseData.url)
|
||||
.setFooter({
|
||||
text: `Credit: ${responseData.subreddit} • ${responseData.postLink}`,
|
||||
})
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
} catch {
|
||||
embed.setDescription('Error occured when fetching meme content.')
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { callbackEmbed } from '../../../utils/messages'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'qrcode',
|
||||
description: 'Generate QR Code by one click.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 5 * 1000,
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
if (message.channel.isVoiceBased()) return
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const data = args.join(' ')
|
||||
|
||||
if (!data) {
|
||||
const cEmbed = callbackEmbed({
|
||||
text: 'Missing QR Code data!',
|
||||
color: 'Red',
|
||||
mode: 'error',
|
||||
})
|
||||
await message.reply({
|
||||
embeds: [cEmbed],
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const url = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${data.replace(
|
||||
/\s/g,
|
||||
'%20'
|
||||
)}`
|
||||
|
||||
embed.setImage(url)
|
||||
|
||||
await message.channel.send({
|
||||
embeds: [embed],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { createCanvas, loadImage } from 'canvas'
|
||||
import { AttachmentBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'rip',
|
||||
description: 'R I P.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 10 * 1000,
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
const { attachments, author, guild, channel } = message
|
||||
|
||||
if (channel.isVoiceBased()) return
|
||||
|
||||
// Image fetching
|
||||
let image = attachments.first()?.proxyURL
|
||||
|
||||
for (let index = 0; index < 2; index++) {
|
||||
if (image) break
|
||||
|
||||
if (index === 1) {
|
||||
image = author.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
if (guild && args[0]) {
|
||||
if (args[0].length >= 18) {
|
||||
const idMember = guild.members.cache.get(args[0])
|
||||
if (idMember) {
|
||||
image = idMember.user.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const username = String(args[0]).toLowerCase()
|
||||
const target = guild.members.cache.find((ur) =>
|
||||
ur.user.username.toLowerCase().includes(username)
|
||||
)
|
||||
if (target) {
|
||||
image = target.user.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!image) return
|
||||
|
||||
const targetImage = await loadImage(image)
|
||||
const background = await loadImage('./assets/rip.jpg')
|
||||
|
||||
const canvas = createCanvas(background.width, background.height)
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
context.drawImage(background, 0, 0, canvas.width, canvas.height)
|
||||
context.drawImage(targetImage, 95, 200, 150, 150)
|
||||
|
||||
const attachment = new AttachmentBuilder(canvas.toBuffer(), {
|
||||
name: `${Date.now()}_rip.png`,
|
||||
})
|
||||
|
||||
await channel.send({
|
||||
files: [attachment],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import { createCanvas, loadImage } from 'canvas'
|
||||
import { AttachmentBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { blur } from '../../../utils/canvas'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'trash',
|
||||
description: 'TRAshh.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 10 * 1000,
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
const { attachments, author, guild, channel } = message
|
||||
|
||||
if (channel.isVoiceBased()) return
|
||||
|
||||
// Image fetching
|
||||
let image = attachments.first()?.proxyURL
|
||||
|
||||
for (let index = 0; index < 2; index++) {
|
||||
if (image) break
|
||||
|
||||
if (index === 1) {
|
||||
image = author.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
if (guild && args[0]) {
|
||||
if (args[0].length >= 18) {
|
||||
const idMember = guild.members.cache.get(args[0])
|
||||
if (idMember) {
|
||||
image = idMember.user.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const username = String(args[0]).toLowerCase()
|
||||
const target = guild.members.cache.find((ur) =>
|
||||
ur.user.username.toLowerCase().includes(username)
|
||||
)
|
||||
if (target) {
|
||||
image = target.user.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!image) return
|
||||
|
||||
const blurredImg = await blur(image)
|
||||
|
||||
const targetImage = await loadImage(blurredImg)
|
||||
const background = await loadImage('./assets/trash.png')
|
||||
|
||||
const canvas = createCanvas(background.width, background.height)
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
context.drawImage(background, 0, 0)
|
||||
context.drawImage(targetImage, 309, 0, 309, 309)
|
||||
|
||||
const attachment = new AttachmentBuilder(canvas.toBuffer(), {
|
||||
name: `${Date.now()}_trash.png`,
|
||||
})
|
||||
|
||||
await channel.send({
|
||||
files: [attachment],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import { createCanvas, loadImage } from 'canvas'
|
||||
import { AttachmentBuilder } from 'discord.js'
|
||||
import GIFEncoder from 'gifencoder'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'trigger',
|
||||
description: 'TRIGGER~.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 10 * 1000,
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
const { attachments, author, guild, channel } = message
|
||||
|
||||
if (channel.isVoiceBased()) return
|
||||
|
||||
// Image fetching
|
||||
let image = attachments.first()?.proxyURL
|
||||
|
||||
for (let index = 0; index < 2; index++) {
|
||||
if (image) break
|
||||
|
||||
if (index === 1) {
|
||||
image = author.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
if (guild && args[0]) {
|
||||
if (args[0].length >= 18) {
|
||||
const idMember = guild.members.cache.get(args[0])
|
||||
if (idMember) {
|
||||
image = idMember.user.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const username = String(args[0]).toLowerCase()
|
||||
const target = guild.members.cache.find((ur) =>
|
||||
ur.user.username.toLowerCase().includes(username)
|
||||
)
|
||||
if (target) {
|
||||
image = target.user.displayAvatarURL({
|
||||
size: 256,
|
||||
extension: 'png',
|
||||
forceStatic: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!image) return
|
||||
|
||||
const targetImage = await loadImage(image)
|
||||
const background = await loadImage('./assets/triggered.png')
|
||||
|
||||
const gif: any = new GIFEncoder(256, 310)
|
||||
|
||||
gif.start()
|
||||
gif.setRepeat(0)
|
||||
gif.setDelay(15)
|
||||
|
||||
const canvas = createCanvas(256, 310)
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
const BR = 30
|
||||
const LR = 20
|
||||
|
||||
for (let index = 0; index < 9; index++) {
|
||||
context.clearRect(0, 0, 256, 310)
|
||||
context.drawImage(
|
||||
targetImage,
|
||||
Math.floor(Math.random() * BR) - BR,
|
||||
Math.floor(Math.random() * BR) - BR,
|
||||
256 + BR,
|
||||
310 - 54 + BR
|
||||
)
|
||||
context.fillStyle = '#FF000033'
|
||||
context.fillRect(0, 0, 256, 310)
|
||||
context.drawImage(
|
||||
background,
|
||||
Math.floor(Math.random() * LR) - LR,
|
||||
310 - 54 + Math.floor(Math.random() * LR) - LR,
|
||||
256 + LR,
|
||||
54 + LR
|
||||
)
|
||||
gif.addFrame(context)
|
||||
}
|
||||
|
||||
gif.finish()
|
||||
|
||||
const attachment = new AttachmentBuilder(gif.out.getData(), {
|
||||
name: `${Date.now()}_trgigered.gif`,
|
||||
})
|
||||
|
||||
await channel.send({
|
||||
files: [attachment],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { inspect } from 'node:util'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { isDev } from '../../../utils/constants'
|
||||
|
||||
function clean(text: string) {
|
||||
if (typeof text === 'string') {
|
||||
return text
|
||||
.replaceAll('`', `\`${String.fromCodePoint(8203)}`)
|
||||
.replaceAll(/@/g, `@${String.fromCodePoint(8203)}`)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
export const command: TextCommand = {
|
||||
enabled: isDev,
|
||||
data: {
|
||||
name: 'eval',
|
||||
publicLevel: 'None',
|
||||
description: 'Eval javascript code in Nodejs runtime.',
|
||||
ownerOnly: true,
|
||||
developmentOnly: true,
|
||||
directMessageAllowed: true,
|
||||
requiredArgs: [
|
||||
{
|
||||
type: 'STRING',
|
||||
rest: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
const { channel } = message
|
||||
|
||||
if (channel.isVoiceBased()) return
|
||||
|
||||
const code = args.join(' ')
|
||||
|
||||
if (code) {
|
||||
try {
|
||||
let evaled = eval(code)
|
||||
if (typeof evaled !== 'string') evaled = inspect(evaled)
|
||||
if (evaled.length > 1999) return console.log(evaled)
|
||||
await channel.send({
|
||||
content: `\`\`\`${clean(evaled as string)}\`\`\``,
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.length < 1999) {
|
||||
await channel.send({
|
||||
content: `\`ERROR\` \`\`\`xl\n${clean(
|
||||
error.message
|
||||
)}\n\`\`\``,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
import { exec } from 'node:child_process'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
import { isDev } from '../../../utils/constants'
|
||||
|
||||
function clean(text: string) {
|
||||
if (typeof text === 'string') {
|
||||
return text
|
||||
.replaceAll('`', `\`${String.fromCodePoint(8203)}`)
|
||||
.replaceAll(/@/g, `@${String.fromCodePoint(8203)}`)
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
export const command: TextCommand = {
|
||||
enabled: isDev,
|
||||
data: {
|
||||
name: 'exec',
|
||||
publicLevel: 'None',
|
||||
ownerOnly: true,
|
||||
developmentOnly: true,
|
||||
description: 'Execute shell command in Nodejs runtime.',
|
||||
directMessageAllowed: true,
|
||||
requiredArgs: [
|
||||
{
|
||||
type: 'STRING',
|
||||
rest: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
run: ({ message, args }) => {
|
||||
const { channel } = message
|
||||
if (channel.isVoiceBased()) return
|
||||
|
||||
const code = args.join(' ')
|
||||
|
||||
if (code) {
|
||||
exec(code, async (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
await channel.send({
|
||||
content: `\`ERROR\` \`\`\`xl\n${clean(
|
||||
error.message
|
||||
)}\n\`\`\``,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
await channel.send({
|
||||
content: `\`ERROR\` \`\`\`xl\n${clean(stderr)}\n\`\`\``,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await channel.send({ content: `\`\`\`${clean(stdout)}\`\`\`` })
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import AES from 'crypto-js/aes'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'aes',
|
||||
description: 'Encrypt and Decrypt stuff by AES256.',
|
||||
directMessageAllowed: true,
|
||||
requiredArgs: [
|
||||
{
|
||||
name: 'mode',
|
||||
type: 'STRING',
|
||||
text: ['encrypt', 'decrypt'],
|
||||
},
|
||||
{
|
||||
name: 'key',
|
||||
type: 'STRING',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'STRING',
|
||||
},
|
||||
],
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
if (message.channel.isVoiceBased()) return
|
||||
|
||||
const [mode, key, ...value] = args
|
||||
const string = value.join(' ')
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
if (message.deletable) message.delete().catch(() => undefined)
|
||||
|
||||
switch (mode) {
|
||||
case 'encrypt': {
|
||||
const encryptedValue = AES.encrypt(string, key).toString()
|
||||
|
||||
embed
|
||||
.setTitle('Encryped Data:')
|
||||
.setDescription(`\`\`\`${encryptedValue}\`\`\``)
|
||||
|
||||
await message.channel.send({
|
||||
embeds: [embed],
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'decrypt': {
|
||||
const decryptedValue = AES.decrypt(string, key).toString()
|
||||
|
||||
embed
|
||||
.setTitle('Decryped Data:')
|
||||
.setDescription(`\`\`\`${decryptedValue}\`\`\``)
|
||||
|
||||
await message.channel.send({
|
||||
embeds: [embed],
|
||||
})
|
||||
break
|
||||
}
|
||||
default: {
|
||||
embed
|
||||
.setTitle('Wrong Type!')
|
||||
.setDescription('`encrypt / decrypt` [key] [value...]')
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'avatar',
|
||||
aliases: ['av'],
|
||||
description: "Fetch user's avatar.",
|
||||
directMessageAllowed: true,
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const { guild, mentions, author } = message
|
||||
|
||||
let targetUser = mentions.users.first()
|
||||
|
||||
if (!targetUser) {
|
||||
if (guild && args[0]) {
|
||||
if (args[0].length >= 18) {
|
||||
const idMember = guild.members.cache.get(args[0])
|
||||
if (idMember) {
|
||||
targetUser = idMember.user
|
||||
}
|
||||
} else {
|
||||
const username = String(args[0]).toLowerCase()
|
||||
const target = guild.members.cache.find((ur) =>
|
||||
ur.user.username.toLowerCase().includes(username)
|
||||
)
|
||||
if (target) targetUser = target.user
|
||||
}
|
||||
} else {
|
||||
targetUser = author
|
||||
}
|
||||
}
|
||||
|
||||
if (!targetUser) targetUser = author
|
||||
|
||||
const avatarURL = targetUser.displayAvatarURL({
|
||||
size: 4096,
|
||||
})
|
||||
|
||||
embed
|
||||
.setTitle(`Icon from ${targetUser.tag}`)
|
||||
.setDescription(`Link: [Click Here](${avatarURL})`)
|
||||
.setImage(avatarURL)
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import axios from 'axios'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'shortenurl',
|
||||
description: "Fetch meme's image.",
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 6 * 1000,
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const destination = args[0]
|
||||
|
||||
try {
|
||||
const url = `https://is.gd/create.php?format=simple&url=${encodeURI(
|
||||
destination
|
||||
)}`
|
||||
|
||||
const response = await axios.get(url)
|
||||
const responseData: string = response.data
|
||||
|
||||
embed
|
||||
.setTitle('Converted!')
|
||||
.setDescription(
|
||||
`URL: ${responseData}\nDestination: \`${destination}\``
|
||||
)
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
} catch {
|
||||
embed.setDescription('Error occured when fetching the content.')
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
import axios from 'axios'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
import { parseStringPromise } from 'xml2js'
|
||||
|
||||
import type { TextCommand } from '../../../sturctures/command'
|
||||
|
||||
export const command: TextCommand = {
|
||||
data: {
|
||||
name: 'weather',
|
||||
description: 'Get weather.',
|
||||
directMessageAllowed: true,
|
||||
cooldownInterval: 10 * 1000,
|
||||
},
|
||||
run: async ({ message, args }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const targetLocation = args.join(' ')
|
||||
|
||||
if (!targetLocation) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const url = 'https://weather.service.msn.com/find.aspx'
|
||||
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
src: 'outlook',
|
||||
weadegreetype: 'C',
|
||||
culture: 'en-US',
|
||||
weasearchstr: targetLocation,
|
||||
},
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
const responseData = response.data as string
|
||||
|
||||
if (!responseData.includes('<')) {
|
||||
if (responseData.search(/not found/i) !== -1) {
|
||||
throw new Error('Location not found!')
|
||||
}
|
||||
throw new Error('Unknown error!')
|
||||
}
|
||||
|
||||
const data = await parseStringPromise(responseData)
|
||||
|
||||
interface CurrentWeather {
|
||||
$: {
|
||||
temperature: string
|
||||
skytext: string
|
||||
date: string
|
||||
observationtime: string
|
||||
observationpoint: string
|
||||
feelslike: string
|
||||
humidity: string
|
||||
winddisplay: string
|
||||
day: string
|
||||
shortday: string
|
||||
windspeed: string
|
||||
}
|
||||
}
|
||||
|
||||
interface WeatherData {
|
||||
$: {
|
||||
weatherlocationname: string
|
||||
timezone: string
|
||||
url: string
|
||||
imagerelativeurl: string
|
||||
degreetype: string
|
||||
entityid: string
|
||||
}
|
||||
current: CurrentWeather[]
|
||||
}
|
||||
|
||||
const weatherData: WeatherData = data.weatherdata.weather[0]
|
||||
|
||||
embed
|
||||
.setTitle(
|
||||
`${weatherData.$.weatherlocationname}'s Current Weather:`
|
||||
)
|
||||
.setDescription(
|
||||
`More Information: [HERE](${weatherData.$.url})`
|
||||
)
|
||||
.setFooter({
|
||||
text: `ID: ${weatherData.$.entityid}`,
|
||||
})
|
||||
.addFields([
|
||||
{
|
||||
name: 'Date',
|
||||
value: `${weatherData.current[0].$.date} (${weatherData.current[0].$.day})`,
|
||||
inline: false,
|
||||
},
|
||||
{
|
||||
name: 'Time Zone',
|
||||
value: `UTC${
|
||||
weatherData.$.timezone.startsWith('-')
|
||||
? weatherData.$.timezone
|
||||
: '+' + weatherData.$.timezone
|
||||
}`,
|
||||
inline: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Status',
|
||||
value: weatherData.current[0].$.skytext,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Temperature',
|
||||
value: weatherData.current[0].$.temperature + '°C',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Feels like',
|
||||
value: weatherData.current[0].$.feelslike + '°C',
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Humidity',
|
||||
value: weatherData.current[0].$.humidity,
|
||||
inline: true,
|
||||
},
|
||||
{
|
||||
name: 'Windspeed',
|
||||
value: weatherData.current[0].$.winddisplay,
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
embed.setTitle(error.message)
|
||||
|
||||
await message.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import { SlashCommandBuilder } from '@discordjs/builders'
|
||||
import type { TextChannel } from 'discord.js'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import { githubLink, name as botname } from '../../../config/bot.json'
|
||||
import type { SlashCommand, TextCommand } from '../../sturctures/command'
|
||||
import { getCommandHelpInfo } from '../../utils/cmds'
|
||||
import { callbackEmbed } from '../../utils/messages'
|
||||
import { command as helpTextCommand } from '../message/general/help'
|
||||
|
||||
export const command: SlashCommand = {
|
||||
slashData: new SlashCommandBuilder()
|
||||
.setName('help')
|
||||
.setDescription(helpTextCommand.data.description)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName('command')
|
||||
.setDescription('Get specified Text Command')
|
||||
.setRequired(false)
|
||||
),
|
||||
run: async ({ interaction }) => {
|
||||
const embed = new EmbedBuilder()
|
||||
|
||||
const { client, channel, options } = interaction
|
||||
|
||||
const commandInput = options.get('command')?.value
|
||||
|
||||
if (!commandInput || typeof commandInput !== 'string') {
|
||||
const commandsCatagories = client.commandsCatagories
|
||||
|
||||
embed.setDescription(
|
||||
`Hello🙋♂️!\nOur source code: [Here](${githubLink})\nTurely appreciate that you are supporting us.`
|
||||
)
|
||||
|
||||
for (const catagory of commandsCatagories) {
|
||||
if (catagory[0].toLocaleLowerCase() === 'nsfw') {
|
||||
if ((channel as TextChannel).nsfw) {
|
||||
catagory[0] += ' THIS CHANNEL ONLY'
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
const text = catagory[1]
|
||||
.map((index: string) => `\`${index}\``)
|
||||
.join(', ')
|
||||
embed.addFields([{ name: catagory[0], value: text }])
|
||||
}
|
||||
|
||||
const avatarURL = client.user.defaultAvatarURL
|
||||
|
||||
embed.setTitle('Bot Assistance Centre').setFooter({
|
||||
text: `© ${new Date().getFullYear()} ${botname}`,
|
||||
iconURL: avatarURL,
|
||||
})
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let cmd: TextCommand | undefined
|
||||
|
||||
const commandMatching = client.commands.get(commandInput)
|
||||
const aliasesMatching = client.aliases.get(commandInput)
|
||||
// Fetch command destination.
|
||||
if (commandMatching) {
|
||||
cmd = commandMatching
|
||||
} else if (aliasesMatching) {
|
||||
cmd = client.commands.get(aliasesMatching)
|
||||
} else {
|
||||
const cEmbed = callbackEmbed({
|
||||
text: 'Command requested does not exist!',
|
||||
color: 'Red',
|
||||
mode: 'error',
|
||||
})
|
||||
await interaction.reply({
|
||||
embeds: [cEmbed],
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (cmd) {
|
||||
const helpInfo = getCommandHelpInfo(cmd)
|
||||
await interaction.reply({
|
||||
embeds: [helpInfo],
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { SlashCommandBuilder } from '@discordjs/builders'
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
import type { SlashCommand } from '../../sturctures/command'
|
||||
import { command as TextCommand } from '../message/general/ping'
|
||||
|
||||
export const command: SlashCommand = {
|
||||
slashData: new SlashCommandBuilder()
|
||||
.setName('ping')
|
||||
.setDescription(TextCommand.data.description),
|
||||
run: async ({ interaction }) => {
|
||||
const apiDelayMS = Math.round(interaction.client.ws.ping)
|
||||
const messageDelayMS = Date.now() - interaction.createdTimestamp
|
||||
|
||||
const embed = new EmbedBuilder().setDescription(
|
||||
`Action Delay: \`${messageDelayMS}ms\`\nAPI Delay: \`${apiDelayMS}ms\``
|
||||
)
|
||||
|
||||
await interaction.reply({
|
||||
embeds: [embed],
|
||||
})
|
||||
},
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
import type { CommandInteraction } from 'discord.js'
|
||||
|
||||
import { ownerId } from '../../config/bot.json'
|
||||
import type { DiscordEvent } from '../sturctures/event'
|
||||
import { cooldownCache } from '../utils/cache'
|
||||
import { isDev } from '../utils/constants'
|
||||
import { parseMsToVisibleText } from '../utils/formatters'
|
||||
|
||||
export const event: DiscordEvent = {
|
||||
name: 'interactionCreate',
|
||||
// eslint-disable-next-line
|
||||
run: async (interaction: CommandInteraction) => {
|
||||
if (interaction.guild && !interaction.member) {
|
||||
await interaction.guild.members.fetch(interaction.user.id)
|
||||
}
|
||||
|
||||
const returnOfInter = async (content: string, ephemeral = true) => {
|
||||
await interaction.reply({ content, ephemeral })
|
||||
}
|
||||
|
||||
if (interaction.isChatInputCommand()) {
|
||||
const { commandName, user, client } = interaction
|
||||
|
||||
const slashCollection = client.slashcommands
|
||||
|
||||
const slash = slashCollection.get(commandName)
|
||||
|
||||
if (!slashCollection.has(commandName) || !slash) {
|
||||
return returnOfInter('Error Occured!')
|
||||
}
|
||||
|
||||
// Eligibility Validations
|
||||
if (slash.enabled === false) {
|
||||
return returnOfInter('This command is not enabled to execute.')
|
||||
}
|
||||
|
||||
if (slash.data?.ownerOnly === true && user.id !== ownerId) {
|
||||
return returnOfInter('This command is not enabled to execute.')
|
||||
}
|
||||
|
||||
if (slash.data?.developmentOnly === true && !isDev) {
|
||||
return returnOfInter(
|
||||
'This command is not enabled to execute in current state.'
|
||||
)
|
||||
}
|
||||
|
||||
// Cooldown Validation
|
||||
const now = Date.now()
|
||||
const keyName = `CMD_${user.id}_${slash.slashData.name}`
|
||||
const cooldownInterval = slash.data?.cooldownInterval ?? 3000
|
||||
|
||||
// Reject if user exists in cooldown.
|
||||
if (cooldownCache.has(keyName)) {
|
||||
const expectedEnd = cooldownCache.get(keyName)
|
||||
if (expectedEnd && now < Number(expectedEnd)) {
|
||||
const timeleft = parseMsToVisibleText(
|
||||
Number(expectedEnd) - now
|
||||
)
|
||||
return returnOfInter(
|
||||
`Before using this command, please wait for **${timeleft}**.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Set cooldown
|
||||
cooldownCache.set(
|
||||
keyName,
|
||||
now + cooldownInterval,
|
||||
cooldownInterval / 1000
|
||||
)
|
||||
|
||||
try {
|
||||
return slash.run({ interaction })
|
||||
} catch (error) {
|
||||
if (error instanceof Error) console.error(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,456 @@
|
|||
import type { Message, PermissionResolvable, TextChannel } from 'discord.js'
|
||||
|
||||
import { ownerId } from '../../config/bot.json'
|
||||
import type { TextCommand } from '../sturctures/command'
|
||||
import type { GuildConfig } from '../sturctures/database'
|
||||
import type { DiscordEvent } from '../sturctures/event'
|
||||
import { cooldownCache } from '../utils/cache'
|
||||
import { getCommandHelpInfo, resembleCommandCheck } from '../utils/cmds'
|
||||
import { isDev } from '../utils/constants'
|
||||
import { parseMsToVisibleText } from '../utils/formatters'
|
||||
import { callbackEmbed } from '../utils/messages'
|
||||
import MYGuildConfig from '../../config/guild.config'
|
||||
|
||||
async function reject(message: Message, usage: string, missing: string) {
|
||||
const postMessage = await message.reply({
|
||||
content: `Usage: \`${usage}\`\nMissing: \`${missing}\``,
|
||||
allowedMentions: { repliedUser: true },
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (postMessage.deletable) postMessage.delete().catch(() => undefined)
|
||||
}, 6000)
|
||||
}
|
||||
|
||||
export const event: DiscordEvent = {
|
||||
name: 'messageCreate',
|
||||
// eslint-disable-next-line
|
||||
run: async (message: Message) => {
|
||||
const { content, channel, author, webhookId, member, guild, client } =
|
||||
message
|
||||
|
||||
if (
|
||||
author.bot ||
|
||||
channel.isVoiceBased() ||
|
||||
webhookId ||
|
||||
author.id === client.user.id
|
||||
)
|
||||
return
|
||||
|
||||
const prefix = 'd!'
|
||||
|
||||
if (guild) {
|
||||
if (!member) await guild.members.fetch(author.id)
|
||||
}
|
||||
|
||||
const guildDatabase: GuildConfig = MYGuildConfig
|
||||
|
||||
const mentionReg = new RegExp(`^(<@!?${client.user.id}>)`)
|
||||
const mentionTest = mentionReg.test(content)
|
||||
if (mentionTest) {
|
||||
await channel.send(`Hey! My prefix is \`${prefix}\``)
|
||||
return
|
||||
}
|
||||
|
||||
const prefixReg = new RegExp(`^${prefix}`)
|
||||
const prefixTest = content.match(prefixReg)
|
||||
if (prefixTest) {
|
||||
const [prefix] = prefixTest
|
||||
const parsedContent = content.slice(prefix.length)
|
||||
if (!parsedContent) return
|
||||
|
||||
const command = parsedContent.split(' ')[0]
|
||||
// Command Content Parsing.
|
||||
let cmd: TextCommand | undefined
|
||||
|
||||
let cmdName = command
|
||||
|
||||
// Fetch command destination.
|
||||
for (let index = 0; index < 2; index++) {
|
||||
const commandMatching = client.commands.get(cmdName)
|
||||
const aliasesMatching = client.aliases.get(cmdName)
|
||||
|
||||
if (commandMatching) {
|
||||
cmd = commandMatching
|
||||
cmdName = cmd.data.name
|
||||
break
|
||||
} else if (aliasesMatching) {
|
||||
cmd = client.commands.get(aliasesMatching)
|
||||
if (cmd) {
|
||||
cmdName = cmd.data.name
|
||||
}
|
||||
break
|
||||
} else {
|
||||
if (index === 0) {
|
||||
const expectedCommandName = await resembleCommandCheck(
|
||||
message,
|
||||
cmdName
|
||||
)
|
||||
if (expectedCommandName) {
|
||||
cmdName = expectedCommandName.name
|
||||
message.createdTimestamp +=
|
||||
expectedCommandName.timeTaken
|
||||
continue
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// Reject if no.
|
||||
if (!cmd) return
|
||||
|
||||
const cmdData = cmd.data
|
||||
|
||||
/**
|
||||
* Command's eligibility vaildation.
|
||||
*/
|
||||
|
||||
if (cmd.enabled === false) return
|
||||
// Reject if not in development mode
|
||||
if (cmdData.developmentOnly === true && !isDev) {
|
||||
return
|
||||
}
|
||||
|
||||
if (cmdData.ownerOnly === true && author.id !== ownerId) return
|
||||
|
||||
// Reject if dm mode while configurated to guild.
|
||||
if (!guild && !cmdData.directMessageAllowed) return
|
||||
|
||||
// Reject if command can executed NSFW channel when it's not in.
|
||||
if (
|
||||
cmdData.nsfwChannelRequired &&
|
||||
(!guild || !channel.isTextBased()) &&
|
||||
!(channel as TextChannel).nsfw
|
||||
) {
|
||||
const cEmbed = callbackEmbed({
|
||||
text: 'You must be in **NSFW** channel before executing this commmand.',
|
||||
color: 'Red',
|
||||
mode: 'error',
|
||||
})
|
||||
author
|
||||
.send({
|
||||
embeds: [cEmbed],
|
||||
})
|
||||
.catch(() => undefined)
|
||||
return
|
||||
}
|
||||
|
||||
// Reject if dm mode while configurated to guild.
|
||||
if (!guild && !cmdData.directMessageAllowed) return
|
||||
|
||||
// Reject when Target disabled or didn't pass.
|
||||
if (guild) {
|
||||
const commandDatasbase = guildDatabase?.commands
|
||||
// GUILD specifies disabled command.
|
||||
if (
|
||||
!commandDatasbase ||
|
||||
commandDatasbase.global.disabled.includes(cmdName)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Specified CATAGORIES
|
||||
if (
|
||||
cmd.data.catagory &&
|
||||
commandDatasbase.global.disabledCatagories.includes(
|
||||
cmd.data.catagory
|
||||
)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// Specified CHANNEL
|
||||
if (
|
||||
commandDatasbase.channelDisabled
|
||||
.find((x) => x.id === channel.id)
|
||||
?.cmds.includes(cmdName)
|
||||
) {
|
||||
author
|
||||
.send({
|
||||
content: `Command cannot be executed in this channel (#${channel.id})!`,
|
||||
allowedMentions: { repliedUser: true },
|
||||
})
|
||||
.catch(() => undefined)
|
||||
return
|
||||
}
|
||||
|
||||
// Specified ROLE
|
||||
if (member?.roles.cache) {
|
||||
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||
for (const role of member.roles.cache) {
|
||||
const hasRole = commandDatasbase.roleDisabled
|
||||
.find((x) => x.id === role[1].id)
|
||||
?.cmds.includes(cmdName)
|
||||
if (hasRole) return
|
||||
}
|
||||
}
|
||||
|
||||
// Specified USER
|
||||
if (
|
||||
commandDatasbase.userDisabled
|
||||
.find((x) => x.id === author.id)
|
||||
?.cmds.includes(cmdName)
|
||||
) {
|
||||
author
|
||||
.send({
|
||||
content:
|
||||
'You are disabled from executing this command!',
|
||||
allowedMentions: { repliedUser: true },
|
||||
})
|
||||
.catch(() => undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
cmd.data.inVoiceChannelRequired === true &&
|
||||
!member?.voice.channel
|
||||
) {
|
||||
const cEmbed = callbackEmbed({
|
||||
text: 'You must be in voice channel before executing this commmand.',
|
||||
color: 'Red',
|
||||
mode: 'error',
|
||||
})
|
||||
await message.reply({
|
||||
embeds: [cEmbed],
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* END of Command's eligibility vaildation.
|
||||
*/
|
||||
|
||||
// Cooldown Validation
|
||||
const now = Date.now()
|
||||
const keyName = `CMD_${author.id}_${cmdName}`
|
||||
|
||||
const cooldownInterval = cmd.data.cooldownInterval ?? 3000
|
||||
|
||||
// Reject if user exists in cooldown.
|
||||
if (cooldownCache.has(keyName)) {
|
||||
const expectedEnd = cooldownCache.get(keyName)
|
||||
if (expectedEnd && now < Number(expectedEnd)) {
|
||||
const timeleft = parseMsToVisibleText(
|
||||
Number(expectedEnd) - now
|
||||
)
|
||||
const postMessage = await message.reply({
|
||||
content: `Before using this command, please wait for **${timeleft}**.`,
|
||||
allowedMentions: { repliedUser: true },
|
||||
})
|
||||
setTimeout(() => {
|
||||
if (postMessage.deletable)
|
||||
postMessage.delete().catch(() => undefined)
|
||||
}, 6000)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Set cooldown.
|
||||
cooldownCache.set(
|
||||
keyName,
|
||||
now + cooldownInterval,
|
||||
cooldownInterval / 1000
|
||||
)
|
||||
|
||||
// Reject if excess usage.
|
||||
if (cmd.data.intervalLimit) {
|
||||
const key1 = 'INTERVAL' + keyName
|
||||
|
||||
let doRejection = { is: false, which: '' }
|
||||
const customTTL = {
|
||||
minute: 60 * 1000,
|
||||
hour: 60 * 60 * 1000,
|
||||
day: 24 * 60 * 60 * 1000,
|
||||
}
|
||||
const intervalList = cmd.data.intervalLimit
|
||||
for (const [key, ms] of Object.entries(intervalList)) {
|
||||
if (!ms) continue
|
||||
const keyTyped = key as keyof typeof intervalList
|
||||
if (!intervalList[keyTyped]) continue
|
||||
|
||||
const userFeq = cooldownCache.get(keyTyped + key1) ?? '0'
|
||||
|
||||
// Do Rejection if number reached specified amount of allowed cooldown.
|
||||
if (Number(userFeq) === intervalList[keyTyped]) {
|
||||
doRejection = { is: true, which: keyTyped }
|
||||
break
|
||||
}
|
||||
|
||||
// Set to Database with TTL.
|
||||
cooldownCache.set(
|
||||
keyTyped + key1,
|
||||
(Number(userFeq) + 1).toString(),
|
||||
customTTL[keyTyped]
|
||||
)
|
||||
}
|
||||
|
||||
if (doRejection.is) {
|
||||
const postMessage = await message.reply({
|
||||
content: `You have reached the maxmium usage in 1 **${doRejection.which}**!`,
|
||||
allowedMentions: { repliedUser: true },
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (postMessage.deletable)
|
||||
postMessage.delete().catch(() => undefined)
|
||||
}, 6000)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Permission Check (BOT)
|
||||
const requestPermsClient = cmd.data.clientRequiredPermissions
|
||||
if (guild && requestPermsClient) {
|
||||
const permMissing: PermissionResolvable[] = []
|
||||
for (const perm of requestPermsClient) {
|
||||
const botId = client.user.id
|
||||
if (botId) {
|
||||
const isOwned = guild.members.cache
|
||||
.get(botId)
|
||||
?.permissions.has(perm)
|
||||
if (!isOwned) permMissing.push(perm)
|
||||
}
|
||||
}
|
||||
|
||||
// Reject if BOT doesn't own permission(s).
|
||||
if (permMissing.length > 0) {
|
||||
const perms = permMissing
|
||||
.map((index) => `\`${Number(index)}\``)
|
||||
.join(', ')
|
||||
|
||||
await message.reply({
|
||||
content: `I don't have **PERMISSIONS**: ${perms}`,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Permission Check (AUTHOR)
|
||||
const requestPermsAuthor = cmd.data.authorRequiredPermissions
|
||||
if (guild && requestPermsAuthor) {
|
||||
const permMissing: PermissionResolvable[] = []
|
||||
for (const perm of requestPermsAuthor) {
|
||||
const isOwned = member?.permissions.has(perm)
|
||||
if (!isOwned) permMissing.push(perm)
|
||||
}
|
||||
|
||||
// Reject if AUTHOR doesn't own permission(s).
|
||||
if (permMissing.length > 0) {
|
||||
const perms = permMissing
|
||||
.map((index) => `\`${Number(index)}\``)
|
||||
.join(', ')
|
||||
|
||||
await message.reply({
|
||||
content: `You do not have required **PERMISSIONS**: ${perms}`,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Pass
|
||||
console.log(
|
||||
`[CMD] ${author.tag} executed ${cmdName} in ${
|
||||
guild?.name ?? 'DM'
|
||||
}.`
|
||||
)
|
||||
const arguments_ = parsedContent.split(' ').slice(1)
|
||||
|
||||
if (arguments_[0] === 'help') {
|
||||
if (
|
||||
cmd.data.nsfwChannelRequired === true &&
|
||||
(channel as TextChannel).nsfw
|
||||
) {
|
||||
const helpInfo = getCommandHelpInfo(cmd)
|
||||
await message.reply({
|
||||
embeds: [helpInfo],
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Arguments Checking
|
||||
const requiredArugments = cmd.data.requiredArgs
|
||||
if (requiredArugments && requiredArugments.length > 0) {
|
||||
let usage = `${prefix}${cmd.data.name}`
|
||||
for (const _argument_ of requiredArugments) {
|
||||
let namedArguments: string = _argument_.type
|
||||
if (_argument_.type === 'STRING' && _argument_.text) {
|
||||
namedArguments = _argument_.text.join(' | ')
|
||||
} else if (_argument_.name) {
|
||||
namedArguments += `(${_argument_.name})`
|
||||
}
|
||||
usage += ` [${namedArguments}]`
|
||||
}
|
||||
|
||||
for (
|
||||
let index = 0, l = requiredArugments.length;
|
||||
index < l;
|
||||
index++
|
||||
) {
|
||||
const _argument = requiredArugments[index]
|
||||
const userArgument = arguments_[index]
|
||||
|
||||
switch (_argument.type) {
|
||||
case 'STRING': {
|
||||
if (_argument.required) {
|
||||
if (
|
||||
!userArgument ||
|
||||
userArgument.length === 0
|
||||
) {
|
||||
return reject(
|
||||
message,
|
||||
usage,
|
||||
index.toString()
|
||||
)
|
||||
}
|
||||
if (
|
||||
_argument.text &&
|
||||
!_argument.text.includes(userArgument)
|
||||
) {
|
||||
const text = `${index.toString()} (NOT_MATCH)`
|
||||
return reject(message, usage, text)
|
||||
}
|
||||
if (
|
||||
_argument.customLength &&
|
||||
(content.length >
|
||||
_argument.customLength.max! ||
|
||||
content.length <
|
||||
_argument.customLength.min!)
|
||||
) {
|
||||
const text = `${index.toString()} (ERR_Length)`
|
||||
return reject(message, usage, text)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'NUMBER': {
|
||||
if (
|
||||
_argument.required &&
|
||||
Number.isNaN(Number(userArgument))
|
||||
) {
|
||||
return reject(message, usage, index.toString())
|
||||
}
|
||||
break
|
||||
}
|
||||
default: {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (_argument.rest) break
|
||||
}
|
||||
}
|
||||
|
||||
// Run the actual command.
|
||||
try {
|
||||
return cmd.run({ message, args: arguments_ })
|
||||
} catch (error) {
|
||||
if (error instanceof Error) console.error(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import type { Client } from 'discord.js'
|
||||
import { ActivityType } from 'discord.js'
|
||||
|
||||
import { defaultPrefix } from '../../config/bot.json'
|
||||
import type { DiscordEvent } from '../sturctures/event'
|
||||
|
||||
export const event: DiscordEvent = {
|
||||
once: true,
|
||||
name: 'ready',
|
||||
run: (client: Client) => {
|
||||
// Dynamic Status
|
||||
let index = 0
|
||||
|
||||
const status = [`${client.guilds.cache.size} Servers`]
|
||||
|
||||
setInterval(() => {
|
||||
const _status = status[index++ % status.length]
|
||||
const text = `${defaultPrefix}help | ${_status}`
|
||||
client.user?.setActivity(text, {
|
||||
type: ActivityType.Watching,
|
||||
})
|
||||
}, 15 * 1000)
|
||||
|
||||
console.log('Bot is in ready status!')
|
||||
},
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import 'dotenv/config'
|
||||
|
||||
import chalk from 'chalk'
|
||||
|
||||
import './validate-env'
|
||||
|
||||
import { isDev } from './utils/constants'
|
||||
|
||||
// Check NODE Version
|
||||
const nodeVersions = process.versions.node.split('.')
|
||||
if (Number(nodeVersions[0]) <= 16 && Number(nodeVersions[1]) < 9) {
|
||||
throw new Error(
|
||||
'Node.js version must be 16.9.0 higher. Please update your Node.js version.'
|
||||
)
|
||||
}
|
||||
|
||||
process.setMaxListeners(15)
|
||||
|
||||
// If instacne is not production mode.
|
||||
if (isDev) {
|
||||
const log = console.log
|
||||
|
||||
log(chalk.bold.red('DEVELOPMENT / MAINTAINANCE MODE'))
|
||||
log(
|
||||
chalk.red.bold(
|
||||
'Some production features will be disrupted or terminated.'
|
||||
)
|
||||
)
|
||||
} else {
|
||||
process.on('uncaughtException', console.error)
|
||||
process.on('unhandledRejection', console.error)
|
||||
}
|
||||
|
||||
import('./bot')
|
|
@ -0,0 +1,167 @@
|
|||
import { basename, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { REST } from '@discordjs/rest'
|
||||
import chalk from 'chalk'
|
||||
import type { Client } from 'discord.js'
|
||||
import type {
|
||||
RESTGetAPIApplicationCommandsResult,
|
||||
RESTPostAPIApplicationCommandsJSONBody,
|
||||
} from 'discord-api-types/v9'
|
||||
import { Routes } from 'discord-api-types/v9'
|
||||
import { glob } from 'glob'
|
||||
|
||||
import { disabledCommandCatagories } from '../../config/bot.json'
|
||||
import type { SlashCommand, TextCommand } from '../sturctures/command'
|
||||
import { isDev } from '../utils/constants'
|
||||
|
||||
type TextCommandCatagories = Record<string, string[]>
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
/** Text Command Loaders */
|
||||
export async function loadTextCommand(client: Client) {
|
||||
let folderPath = join(__dirname, '../commands/message/**/*.ts')
|
||||
|
||||
// Parse path in windows
|
||||
if (process.platform === 'win32') {
|
||||
folderPath = folderPath.replaceAll('\\', '/')
|
||||
}
|
||||
|
||||
const allFiles = await glob(folderPath)
|
||||
|
||||
if (allFiles.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const catagories: TextCommandCatagories = {}
|
||||
|
||||
for (const filePath of allFiles) {
|
||||
const commandFile = await import(filePath)
|
||||
const command: TextCommand = commandFile.command
|
||||
|
||||
// Neglect if disabled.
|
||||
if (command.enabled === false) continue
|
||||
|
||||
// Store command to memory.
|
||||
const cmdName = command.data.name
|
||||
if (client.commands.has(cmdName)) {
|
||||
throw new Error('Duplicated command is found!')
|
||||
}
|
||||
|
||||
const catagory = basename(dirname(filePath))
|
||||
|
||||
const disabledCatagories: string[] = disabledCommandCatagories
|
||||
|
||||
if (!disabledCatagories.includes(catagory)) {
|
||||
if (catagory) {
|
||||
command.data.catagory = catagory
|
||||
if (command.data.publicLevel !== 'None') {
|
||||
if (!catagories[String(catagory)]) {
|
||||
catagories[String(catagory)] = []
|
||||
}
|
||||
catagories[String(catagory)].push(cmdName)
|
||||
}
|
||||
}
|
||||
|
||||
if (command.data.intervalLimit) {
|
||||
const list = command.data.intervalLimit
|
||||
if (list.minute! > list.hour! || list.hour! > list.day!) {
|
||||
throw 'Impolitic Custom Interval style!'
|
||||
}
|
||||
}
|
||||
|
||||
client.commands.set(cmdName, command)
|
||||
|
||||
if (command.data.aliases) {
|
||||
for (const alias of command.data.aliases) {
|
||||
if (client.aliases.has(alias)) {
|
||||
throw new Error('Duplicated alias is found!')
|
||||
}
|
||||
// Store aliase(s) to memory if exists.
|
||||
client.aliases.set(alias, command.data.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const value of Object.entries(catagories)) {
|
||||
client.commandsCatagories.set(value[0], value[1])
|
||||
}
|
||||
|
||||
// Print number of loaded commands.
|
||||
console.log(
|
||||
chalk.greenBright.bold(`Loaded ${client.commands.size} text commands.`)
|
||||
)
|
||||
}
|
||||
|
||||
/** Load Slash commands to API & Collection */
|
||||
export async function loadSlashCommand(
|
||||
client: Client,
|
||||
clientId: string,
|
||||
token: string
|
||||
) {
|
||||
let folderPath = join(__dirname, '../commands/slash/**/*.ts')
|
||||
|
||||
// Parse path in windows
|
||||
if (process.platform === 'win32') {
|
||||
folderPath = folderPath.replaceAll('\\', '/')
|
||||
}
|
||||
|
||||
const allFiles = await glob(folderPath)
|
||||
|
||||
const slashCommandData: RESTPostAPIApplicationCommandsJSONBody[] = []
|
||||
|
||||
for (const filePath of allFiles) {
|
||||
const commandFile = await import(filePath)
|
||||
const slashCommand: SlashCommand = commandFile.command
|
||||
|
||||
const slashCommandCollection = client.slashcommands
|
||||
const name = slashCommand.slashData.name
|
||||
|
||||
if (slashCommandCollection.has(name)) {
|
||||
throw new Error('Duplicated slash command is found!')
|
||||
}
|
||||
|
||||
client.slashcommands.set(name, slashCommand)
|
||||
|
||||
slashCommandData.push(slashCommand.slashData.toJSON())
|
||||
}
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken(token)
|
||||
|
||||
if (isDev) {
|
||||
// Guild & Development Commands.
|
||||
const guildId = process.env.DEV_GUILD_ID
|
||||
|
||||
if (guildId) {
|
||||
const guildCommands = await rest.get(
|
||||
Routes.applicationGuildCommands(clientId, guildId)
|
||||
)
|
||||
|
||||
for (const command of guildCommands as RESTGetAPIApplicationCommandsResult) {
|
||||
const deleteUrl = `${Routes.applicationGuildCommands(
|
||||
clientId,
|
||||
guildId
|
||||
)}/${command.id}`
|
||||
await rest.delete(`/${deleteUrl}`)
|
||||
}
|
||||
|
||||
await rest.put(Routes.applicationGuildCommands(clientId, guildId), {
|
||||
body: slashCommandData,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Global Commands
|
||||
await rest.put(Routes.applicationCommands(clientId), {
|
||||
body: slashCommandData,
|
||||
})
|
||||
}
|
||||
|
||||
// Print number of loaded commands.
|
||||
console.log(
|
||||
chalk.greenBright.bold(
|
||||
`Loaded ${client.slashcommands.size} slash commands.`
|
||||
)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import chalk from 'chalk'
|
||||
import type { Client } from 'discord.js'
|
||||
import { glob } from 'glob'
|
||||
|
||||
import type { DiscordEvent } from '../sturctures/event'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export async function loadDiscordEvent(client: Client) {
|
||||
let folderPath = join(__dirname, '../events/**/*.ts')
|
||||
|
||||
// Parse path in windows
|
||||
if (process.platform === 'win32') {
|
||||
folderPath = folderPath.replaceAll('\\', '/')
|
||||
}
|
||||
|
||||
const allFiles = await glob(folderPath)
|
||||
|
||||
if (allFiles.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const filePath of allFiles) {
|
||||
// Get event content.
|
||||
const eventFile = await import(filePath)
|
||||
const event: DiscordEvent = eventFile.event
|
||||
|
||||
// Check triggering mode.
|
||||
if (event.once === true) {
|
||||
// eslint-disable-next-line
|
||||
client.once(event.name, event.run.bind(undefined))
|
||||
} else {
|
||||
// eslint-disable-next-line
|
||||
client.on(event.name, event.run.bind(undefined))
|
||||
}
|
||||
}
|
||||
|
||||
// Print number of loaded events.
|
||||
console.log(
|
||||
chalk.greenBright.bold(`Loaded ${allFiles.length} Discord events.`)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import type { SlashCommandBuilder } from '@discordjs/builders'
|
||||
import type {
|
||||
CommandInteraction,
|
||||
Message,
|
||||
PermissionResolvable,
|
||||
} from 'discord.js'
|
||||
|
||||
interface TextCommandExecution {
|
||||
message: Message
|
||||
args: string[]
|
||||
}
|
||||
|
||||
interface SlashCommandExecution {
|
||||
interaction: CommandInteraction
|
||||
}
|
||||
|
||||
interface TextCommandRequiredArgumentsDefault {
|
||||
name?: string
|
||||
rest?: boolean
|
||||
text?: string[]
|
||||
type: 'NUMBER' | 'STRING'
|
||||
customLength?: {
|
||||
min?: number
|
||||
max?: number
|
||||
}
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
type TextCommandRequiredArguments = TextCommandRequiredArgumentsDefault
|
||||
|
||||
/**
|
||||
* As default, command can only be accessed in guild.
|
||||
*
|
||||
* Everyone can access wihtout any permission limitations.
|
||||
*
|
||||
* Cooldown Interval is 3 seconds (3000 milliseconds)
|
||||
*/
|
||||
export interface TextCommand {
|
||||
enabled?: boolean
|
||||
|
||||
// Command Data
|
||||
readonly data: {
|
||||
// Permissions
|
||||
clientRequiredPermissions?: PermissionResolvable[]
|
||||
authorRequiredPermissions?: PermissionResolvable[]
|
||||
|
||||
// Access, Environment & Scenes
|
||||
ownerOnly?: boolean
|
||||
developmentOnly?: boolean
|
||||
nsfwChannelRequired?: boolean
|
||||
inVoiceChannelRequired?: boolean
|
||||
threadChannelAllowed?: boolean
|
||||
directMessageAllowed?: boolean
|
||||
publicLevel?: 'All' | 'Permission' | 'None'
|
||||
requiredArgs?: TextCommandRequiredArguments[]
|
||||
|
||||
// Info
|
||||
name: string
|
||||
description: string
|
||||
catagory?: string
|
||||
usage?: string
|
||||
aliases?: string[]
|
||||
|
||||
// Specified Configurations
|
||||
cooldownInterval?: number
|
||||
intervalLimit?: {
|
||||
minute?: number
|
||||
hour?: number
|
||||
day?: number
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
run: ({ message, args }: TextCommandExecution) => Promise<void> | void
|
||||
}
|
||||
|
||||
export interface SlashCommand {
|
||||
enabled?: boolean
|
||||
|
||||
// Slash Data
|
||||
slashData: Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>
|
||||
|
||||
readonly data?: {
|
||||
clientRequiredPermissions?: PermissionResolvable[]
|
||||
|
||||
ownerOnly?: boolean
|
||||
developmentOnly?: boolean
|
||||
|
||||
cooldownInterval?: number
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
run: ({ interaction }: SlashCommandExecution) => Promise<void>
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
export interface GuildConfig {
|
||||
prefix: string
|
||||
serverId: string
|
||||
commands: {
|
||||
global: {
|
||||
// Access
|
||||
disabled: string[]
|
||||
disabledCatagories: string[]
|
||||
// Cooldown
|
||||
customCooldown: {
|
||||
name: string
|
||||
value: number
|
||||
intervals: {
|
||||
minute: number
|
||||
hour: number
|
||||
day: number
|
||||
}
|
||||
}[]
|
||||
}
|
||||
// Access
|
||||
userDisabled: { id: string; cmds: string[] }[]
|
||||
roleDisabled: { id: string; cmds: string[] }[]
|
||||
channelDisabled: { id: string; cmds: string[] }[]
|
||||
}
|
||||
antiSpam: {
|
||||
enabled: boolean
|
||||
whitelistedUsers: string[]
|
||||
whitelistedRoles: string[]
|
||||
whitelistedChannels: string[]
|
||||
inviteLinks: {
|
||||
enabled: boolean
|
||||
whitelistedUsers: string[]
|
||||
whitelistedRoles: string[]
|
||||
whitelistedChannels: string[]
|
||||
}
|
||||
mentions: {
|
||||
enabled: boolean
|
||||
maxmiumCheck: {
|
||||
enabled: boolean
|
||||
value: number
|
||||
}
|
||||
publicRoleCheck: boolean
|
||||
whitelistedUsers: string[]
|
||||
whitelistedRoles: string[]
|
||||
whitelistedChannels: string[]
|
||||
}
|
||||
}
|
||||
thread: {
|
||||
listen?: boolean
|
||||
}
|
||||
snipe: {
|
||||
channelDisabled: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface SnipeConfig {
|
||||
channelId: string
|
||||
author: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
content: {
|
||||
text: string
|
||||
date: number
|
||||
imageURL: string | undefined
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import type { ClientEvents } from 'discord.js'
|
||||
|
||||
/** Discord Client events */
|
||||
export interface DiscordEvent {
|
||||
// Event Data
|
||||
name: keyof ClientEvents
|
||||
once?: boolean
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
run: (...arguments_: any[]) => Promise<void> | void
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import NodeCache from 'node-cache'
|
||||
|
||||
const cooldownCache = new NodeCache()
|
||||
|
||||
export { cooldownCache }
|
|
@ -0,0 +1,28 @@
|
|||
import { createCanvas, loadImage } from 'canvas'
|
||||
|
||||
async function blur(image: string | Buffer): Promise<Buffer> {
|
||||
if (!image) throw new Error('Image was not provided!')
|
||||
const img = await loadImage(image)
|
||||
const canvas = createCanvas(img.width, img.height)
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
context.fillStyle = '#ffffff'
|
||||
context.fillRect(0, 0, canvas.width, canvas.height)
|
||||
context.drawImage(img, 0, 0, canvas.width / 4, canvas.height / 4)
|
||||
context.imageSmoothingEnabled = true
|
||||
context.drawImage(
|
||||
canvas,
|
||||
0,
|
||||
0,
|
||||
canvas.width / 4,
|
||||
canvas.height / 4,
|
||||
0,
|
||||
0,
|
||||
canvas.width + 5,
|
||||
canvas.height + 5
|
||||
)
|
||||
|
||||
return canvas.toBuffer()
|
||||
}
|
||||
|
||||
export { blur }
|
|
@ -0,0 +1,127 @@
|
|||
import type {
|
||||
CollectorFilter,
|
||||
Message,
|
||||
MessageComponentInteraction,
|
||||
} from 'discord.js'
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
EmbedBuilder,
|
||||
} from 'discord.js'
|
||||
import { findBestMatch } from 'string-similarity'
|
||||
|
||||
import type { TextCommand } from '../sturctures/command'
|
||||
|
||||
/** Send command information message embed. */
|
||||
export function getCommandHelpInfo(cmd: TextCommand): EmbedBuilder {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`Command: ${cmd.data.name}`)
|
||||
.addFields([{ name: 'Description', value: cmd.data.description }])
|
||||
|
||||
if (cmd.data.usage) {
|
||||
embed.addFields([{ name: 'Usage', value: cmd.data.usage }])
|
||||
}
|
||||
|
||||
embed.addFields([
|
||||
{ name: 'Catagory', value: cmd.data.catagory ?? '', inline: true },
|
||||
{
|
||||
name: 'Cooldown',
|
||||
value: `${
|
||||
(cmd.data.cooldownInterval ?? 3000) / 1000 || '3'
|
||||
} seconds`,
|
||||
inline: true,
|
||||
},
|
||||
])
|
||||
|
||||
if (cmd.data.intervalLimit) {
|
||||
embed.addFields([
|
||||
{
|
||||
name: 'Allowed Intervals',
|
||||
value: Object.entries(cmd.data.intervalLimit)
|
||||
.map((value) => {
|
||||
return `${value[0]} - \`${value[1]}\``
|
||||
})
|
||||
.join('\n'),
|
||||
inline: false,
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
return embed
|
||||
}
|
||||
|
||||
interface ExpectedWord {
|
||||
name: string
|
||||
timeTaken: number
|
||||
}
|
||||
|
||||
/** Similarity check of excuting commands. */
|
||||
export async function resembleCommandCheck(
|
||||
message: Message,
|
||||
word: string
|
||||
): Promise<ExpectedWord | undefined> {
|
||||
const timeStarted = Date.now()
|
||||
|
||||
const cmdList = [...message.client.commands.keys()]
|
||||
|
||||
const { bestMatch } = findBestMatch(word, cmdList)
|
||||
|
||||
if (bestMatch.rating < 0.65) return undefined
|
||||
|
||||
const acceptButtonId = 'accCMD'
|
||||
const declineButtonId = 'denCMD'
|
||||
|
||||
const acceptButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setEmoji('👍')
|
||||
.setCustomId(acceptButtonId)
|
||||
const declineButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setEmoji('👎')
|
||||
.setCustomId(declineButtonId)
|
||||
|
||||
const row: ActionRowBuilder<any> = new ActionRowBuilder().addComponents([
|
||||
acceptButton,
|
||||
declineButton,
|
||||
])
|
||||
|
||||
const matchRate = Number(bestMatch.rating.toFixed(2))
|
||||
|
||||
const _message = await message.reply({
|
||||
content: `Do you prefer excuting \`${
|
||||
bestMatch.target
|
||||
}\`? (Similarity: \`${matchRate * 100}%\`)`,
|
||||
components: [row],
|
||||
})
|
||||
|
||||
const filter: CollectorFilter<[MessageComponentInteraction]> = (inter) => {
|
||||
return (
|
||||
[acceptButtonId, declineButtonId].includes(inter.customId) &&
|
||||
inter.user.id === message.author.id
|
||||
)
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
return _message
|
||||
.awaitMessageComponent({
|
||||
filter,
|
||||
time: 30_000,
|
||||
})
|
||||
.then(async (_inter) => {
|
||||
// Delete message first
|
||||
if (_message.deletable) {
|
||||
await _message.delete().catch(() => undefined)
|
||||
}
|
||||
if (_inter.customId === acceptButtonId) {
|
||||
const timeTaken = timeStarted - Date.now()
|
||||
return resolve({ name: bestMatch.target, timeTaken })
|
||||
}
|
||||
throw new Error('Declined')
|
||||
})
|
||||
.catch(() => {
|
||||
// eslint-disable-next-line
|
||||
return resolve(undefined)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export const isDev = process.env.NODE_ENV === 'development'
|
|
@ -0,0 +1,17 @@
|
|||
import { GraphQLClient } from 'graphql-request'
|
||||
|
||||
export let client = new GraphQLClient('')
|
||||
|
||||
export const hgqlInit = () => {
|
||||
console.log('\n🚀 GraphQL Client Initialized')
|
||||
|
||||
let HASURA_URL: string = process.env.HASURA_GRAPHQL_ENDPOINT || ''
|
||||
HASURA_URL += HASURA_URL.endsWith('/') ? 'v1/graphql' : '/v1/graphql'
|
||||
const HASURA_ADMIN: string = process.env.HASURA_GRAPHQL_ADMIN_SECRET || ''
|
||||
|
||||
client = new GraphQLClient(HASURA_URL, {
|
||||
headers: {
|
||||
'x-hasura-admin-secret': HASURA_ADMIN,
|
||||
},
|
||||
})
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Transfer milliseconds to visible duration
|
||||
*
|
||||
* 1000 -> 0h 0m 1s
|
||||
* */
|
||||
export function parseMsToVisibleText(ms: number): string {
|
||||
if (ms < 1000) return 'less than 1s'
|
||||
let seconds: number | string = Math.trunc((ms / 1000) % 60)
|
||||
let minutes: number | string = Math.trunc((ms / (1000 * 60)) % 60)
|
||||
let hours: number | string = Math.trunc((ms / (1000 * 60 * 60)) % 24)
|
||||
seconds = seconds > 0 ? `${seconds}s` : ''
|
||||
minutes = minutes > 0 ? `${minutes}m ` : ''
|
||||
hours = hours > 0 ? `${hours}h ` : ''
|
||||
|
||||
return `${hours}${minutes}${seconds}`
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import type {
|
||||
CollectorFilter,
|
||||
ColorResolvable,
|
||||
EmbedField,
|
||||
Message,
|
||||
MessageComponentInteraction,
|
||||
} from 'discord.js'
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
EmbedBuilder,
|
||||
} from 'discord.js'
|
||||
|
||||
import emoji from '../../config/emojis.json'
|
||||
|
||||
interface ConfirmInformationButtons {
|
||||
title: string
|
||||
message: Message
|
||||
fields: EmbedField[]
|
||||
}
|
||||
|
||||
export async function confirmInformationButtons({
|
||||
title,
|
||||
fields,
|
||||
message,
|
||||
}: ConfirmInformationButtons): Promise<boolean> {
|
||||
if (message.channel.isVoiceBased()) return false
|
||||
|
||||
const now = Date.now()
|
||||
const embed = new EmbedBuilder().setTitle(title).addFields(fields)
|
||||
|
||||
const confirmId = `CONFIRM_${now}`
|
||||
const cancelId = `CANCEL_${now}`
|
||||
|
||||
const buttonSuccess = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setLabel('Confirm')
|
||||
.setCustomId(confirmId)
|
||||
const buttonCancel = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setLabel('Cancel')
|
||||
.setCustomId(cancelId)
|
||||
|
||||
const row: ActionRowBuilder<any> = new ActionRowBuilder().addComponents([
|
||||
buttonSuccess,
|
||||
buttonCancel,
|
||||
])
|
||||
|
||||
const respondAwaiting = await message.channel.send({
|
||||
embeds: [embed],
|
||||
components: [row],
|
||||
})
|
||||
|
||||
const filter: CollectorFilter<[MessageComponentInteraction]> = (inter) => {
|
||||
return (
|
||||
[confirmId, cancelId].includes(inter.customId) &&
|
||||
inter.user.id === message.author.id
|
||||
)
|
||||
}
|
||||
|
||||
const interaction = await respondAwaiting.awaitMessageComponent({
|
||||
filter,
|
||||
time: 30_000,
|
||||
})
|
||||
|
||||
if (respondAwaiting.deletable)
|
||||
await respondAwaiting.delete().catch(() => undefined)
|
||||
|
||||
return interaction.customId === confirmId
|
||||
}
|
||||
|
||||
interface CallbackEmbed {
|
||||
text: string
|
||||
color?: ColorResolvable
|
||||
mode?: 'error' | 'success' | 'warning'
|
||||
}
|
||||
|
||||
export function callbackEmbed({
|
||||
text,
|
||||
color = 'Grey',
|
||||
mode,
|
||||
}: CallbackEmbed): EmbedBuilder {
|
||||
let emojiText = ''
|
||||
|
||||
if (mode && typeof mode === 'string') {
|
||||
emojiText = emoji[mode]
|
||||
}
|
||||
|
||||
return new EmbedBuilder()
|
||||
.setDescription(`${emojiText} ${text}`)
|
||||
.setColor(color)
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace NodeJS {
|
||||
// eslint-disable-next-line
|
||||
interface ProcessEnv extends z.infer<typeof ZodEnvironmentVariables> {}
|
||||
}
|
||||
}
|
||||
|
||||
const ZodEnvironmentVariables = z.object({
|
||||
TOKEN: z.string(),
|
||||
CLIENT_ID: z.string(),
|
||||
DEV_GUILD_ID: z.string().optional(),
|
||||
PORT: z.string().optional(),
|
||||
HASURA_GRAPHQL_ADMIN_SECRET: z.string(),
|
||||
HASURA_GRAPHQL_ENDPOINT: z.string(),
|
||||
})
|
||||
|
||||
ZodEnvironmentVariables.parse(process.env)
|
||||
|
||||
console.log('✅ Environment variables verified!')
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"alwaysStrict": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
"isolatedModules": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
|
||||
"noEmit": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUncheckedIndexedAccess": false,
|
||||
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"target": "esnext"
|
||||
},
|
||||
"display": "Default tsconfig.json",
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in New Issue