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_REDIRECT_URI: string
|
||||||
KEYCLOAK_AUTH_SERVER_URL: string
|
KEYCLOAK_AUTH_SERVER_URL: string
|
||||||
KEYCLOAK_REALM: 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 {
|
export default class ConfigStoreFactory {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Request, Response } from 'express'
|
||||||
import { makeResponse } from '../libs'
|
import { makeResponse } from '../libs'
|
||||||
|
|
||||||
export default class DiscordController extends DiscordService {
|
export default class DiscordController extends DiscordService {
|
||||||
public getProfile = async (req: Request, res: Response) => {
|
public getActivity = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const data = await this.activity()
|
const data = await this.activity()
|
||||||
res.send(makeResponse(data))
|
res.send(makeResponse(data))
|
||||||
|
@ -22,4 +22,22 @@ export default class DiscordController extends DiscordService {
|
||||||
res.send(makeResponse(error.message, {}, 'Failed', true))
|
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 multer from 'multer'
|
||||||
import multerS3 from 'multer-s3'
|
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import napiNanoId from 'napi-nanoid'
|
import { nanoid } from 'napi-nanoid'
|
||||||
import { configKeys } from '..'
|
|
||||||
interface UploadFactoryOptions {
|
|
||||||
region: string
|
|
||||||
bucket: string
|
|
||||||
accessKey: string
|
|
||||||
secretKey: string
|
|
||||||
}
|
|
||||||
interface UploaderConfig {
|
interface UploaderConfig {
|
||||||
folder: string
|
|
||||||
mimeFilters: string[]
|
mimeFilters: string[]
|
||||||
}
|
}
|
||||||
export class UploadFactory {
|
export class UploadFactory {
|
||||||
private options: UploadFactoryOptions & Partial<UploaderConfig>
|
public getUploader(config?: UploaderConfig) {
|
||||||
private s3Client: S3Client
|
const storage = multer.memoryStorage({
|
||||||
|
filename: (req, file, cb) => {
|
||||||
constructor(options?: Partial<UploadFactoryOptions>) {
|
const fileName =
|
||||||
this.options = {
|
file.originalname +
|
||||||
bucket: options?.bucket || configKeys.S3_BUCKET_NAME || '',
|
'-' +
|
||||||
region: options?.region || configKeys.S3_BUCKET_REGION || '',
|
nanoid() +
|
||||||
accessKey: options?.accessKey || configKeys.S3_CLIENT_ID || '',
|
path.extname(file.originalname)
|
||||||
secretKey: options?.secretKey || configKeys.S3_CLIENT_SECRET || '',
|
cb(null, fileName)
|
||||||
}
|
|
||||||
|
|
||||||
this.s3Client = new S3Client({
|
|
||||||
region: this.options.region,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: this.options.accessKey,
|
|
||||||
secretAccessKey: this.options.secretKey,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
public get serviceName(): string {
|
|
||||||
return 'aws:' + this.options.bucket
|
|
||||||
}
|
|
||||||
|
|
||||||
public getUploader(
|
|
||||||
config?: Partial<UploadFactoryOptions & UploaderConfig>
|
|
||||||
) {
|
|
||||||
const finalOptions = {
|
|
||||||
...this.options,
|
|
||||||
...(config || {}),
|
|
||||||
}
|
|
||||||
|
|
||||||
return multer({
|
return multer({
|
||||||
fileFilter(_req, file, cb) {
|
storage: storage,
|
||||||
const res = finalOptions.mimeFilters
|
fileFilter: (req, file, cb) => {
|
||||||
? finalOptions.mimeFilters.includes(file.mimetype)
|
const fileName =
|
||||||
: true
|
file.originalname.split('.')[
|
||||||
cb(null, res)
|
file.originalname.split('.').length - 2
|
||||||
},
|
] +
|
||||||
storage: multerS3({
|
'-' +
|
||||||
s3: this.s3Client,
|
nanoid() +
|
||||||
bucket: this.options.bucket,
|
path.extname(file.originalname)
|
||||||
acl: 'public-read',
|
file.newName = fileName
|
||||||
contentType: multerS3.AUTO_CONTENT_TYPE,
|
if (config?.mimeFilters?.length) {
|
||||||
metadata: function (_req, file, cb) {
|
if (config.mimeFilters.includes(file.mimetype)) {
|
||||||
const meta = {
|
cb(null, true)
|
||||||
fieldName: file.fieldname,
|
} else {
|
||||||
fileName: file.originalname,
|
cb(new Error('File type not allowed'), false)
|
||||||
uploadOn: new Date().toISOString(),
|
|
||||||
}
|
}
|
||||||
cb(null, meta)
|
} else {
|
||||||
},
|
cb(null, true)
|
||||||
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('/'))
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import routes from './routes'
|
||||||
import { errorHandler, notFoundHandler } from './libs'
|
import { errorHandler, notFoundHandler } from './libs'
|
||||||
import pkg from './package.json' assert { type: 'json' }
|
import pkg from './package.json' assert { type: 'json' }
|
||||||
import configStore, { IConfigKeys } from './configs'
|
import configStore, { IConfigKeys } from './configs'
|
||||||
|
import discordBotConnect from './helpers/discord_bot_client'
|
||||||
|
|
||||||
export const app: express.Application = express()
|
export const app: express.Application = express()
|
||||||
|
|
||||||
|
@ -23,6 +24,8 @@ console.log(isDev ? '🚀 Production Mode' : '👷 Development Mode')
|
||||||
const configs = new configStore(isDev)
|
const configs = new configStore(isDev)
|
||||||
const configKeys: IConfigKeys = (await configs.getConfigStore()) as IConfigKeys
|
const configKeys: IConfigKeys = (await configs.getConfigStore()) as IConfigKeys
|
||||||
|
|
||||||
|
discordBotConnect()
|
||||||
|
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
app.use(helmet())
|
app.use(helmet())
|
||||||
app.use(morgan('dev'))
|
app.use(morgan('dev'))
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.226.0",
|
"@aws-sdk/client-s3": "^3.226.0",
|
||||||
|
"@discordjs/core": "^0.6.0",
|
||||||
|
"@discordjs/rest": "^1.7.1",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
"axios": "^1.2.1",
|
"axios": "^1.2.1",
|
||||||
"cors": "^2.8.5",
|
"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'
|
import DiscordController from '../../controllers/discord.controller'
|
||||||
|
|
||||||
const router = Router()
|
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('/banner', getBanner)
|
||||||
|
|
||||||
|
router.get('/v2/profile', getProfile)
|
||||||
|
router.get('/v2/activity', getPresence)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import { configKeys } from '..'
|
||||||
import axios from '../helpers/axios_client'
|
import axios from '../helpers/axios_client'
|
||||||
|
import DiscordBotClient from '../helpers/discord_bot.factory'
|
||||||
|
|
||||||
export default class DiscordService {
|
export default class DiscordService {
|
||||||
public activity = async () => {
|
public activity = async () => {
|
||||||
|
@ -14,4 +16,12 @@ export default class DiscordService {
|
||||||
)
|
)
|
||||||
return data
|
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 {
|
export interface ModRequest extends Request {
|
||||||
user: any
|
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