mirror of
https://github.com/discordeno/discordeno.git
synced 2026-09-17 08:47:22 +00:00
feat(examples): Migrate example bots to discordeno v19 (#3647)
* Migrate beginner and minimal bot to discordeno v19
* Add .swcrc and fix minimal yarn.lock
* update .gitignore files
* Update nodejs template
Discordeno.js (DD v13) -> DD v19 "raw"
Currently the permission checking is not working correctly
* Fix permission issue
* Rename the templates
* remove unused indents
* Rename starter to beginner
So now it is minimal (main branch) -> beginner
* Really small refactor & eslint fixes (bigbot template)
This is to make my life less miserable at a later time
* mark rabbitMQ plugins as binary files
git seems to be treating them as text
* Add v19 bigbot rest
* Add gateway code
and rabbitmq_message_deduplication v0.6.2 plugin
* fix yarn messy semevr version for @types/amqplib
* clear channel con amqp connection close
* Add bot code for bigbot v19
missing prisma setup, collector setup & language setup
* Add localization
The "command versioning" system works the same say as before, but instead of a command version the code updates the commands every time they change based on the SHA1 of the commands
* Use file relative paths instead of cwd relative paths
* Fix todos
* revert autocomplete tests
* Revert "Add localization"
This reverts commit 2b1da8d2cd.
* move env assertion to config.ts
* Add shard ping to /ping
* fix small issue
* Update readme files
* use Date.now() for the bigbot REST ping
* Remove bigbot v16 code
* Add docker (compose) setup to bigbot template
* remove healthchecks from rest & gateway
* Update dependencies of examples
* Apply readme(s) suggestions from code review
Hopefully i haven't missed any related to markdown files
Co-authored-by: LTS20050703 <lts20050703@gmail.com>
* Apply code suggestions from code review
Co-authored-by: LTS20050703 <lts20050703@gmail.com>
---------
Co-authored-by: LTS20050703 <lts20050703@gmail.com>
This commit is contained in:
@@ -1,2 +1 @@
|
||||
BOT_TOKEN=''
|
||||
DEV_GUILD_ID=''
|
||||
@@ -0,0 +1,32 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
|
||||
# build
|
||||
dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"legacyDecorator": true,
|
||||
"decoratorMetadata": true
|
||||
},
|
||||
"target": "es2022",
|
||||
"keepClassNames": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "es6",
|
||||
"strict": false,
|
||||
"strictMode": true,
|
||||
"lazy": false,
|
||||
"noInterop": false
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
# Beginner Bot Template
|
||||
|
||||
This template is designed for the beginner developer to start coding discord bots.
|
||||
This template is designed for beginners to start coding discord bots.
|
||||
|
||||
Make sure to install the latest version when you use it.
|
||||
This template includes caching (using `dd-cache-proxy`) and support for slash subcommands.
|
||||
This template also includes a /ping command to show the bot latency
|
||||
|
||||
## Setup
|
||||
|
||||
- [Click here](https://github.com/discordeno/template/generate) to make your own copy.
|
||||
- Delete all the template folders except the beginner folder.
|
||||
- Move all files from this folder to the root of the project.
|
||||
- You may encounter an issue with README.md file but force move the files to the root of the project.
|
||||
- Rename the .env.example file to .env OR create a new .env file and copy the example file code to this new file.
|
||||
- Download the source
|
||||
- Install the dependencies using `yarn`
|
||||
- Copy the .env.example file and rename it to .env
|
||||
- Fill out the .env file
|
||||
|
||||
## Run Bot
|
||||
|
||||
- deno run -A mod.ts
|
||||
- run `yarn` to install the dependencies
|
||||
- run `yarn build` to build the source
|
||||
- run `node dist/register-commands.js` to register the slash commands
|
||||
- run `yarn start` to run the bot
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { configs } from './configs.ts.js'
|
||||
import type { BotWithCache, BotWithHelpersPlugin } from './deps.ts.js'
|
||||
import {
|
||||
Collection,
|
||||
createBot,
|
||||
enableCachePlugin,
|
||||
enableCacheSweepers,
|
||||
enableHelpersPlugin,
|
||||
enablePermissionsPlugin,
|
||||
GatewayIntents,
|
||||
} from './deps.ts.js'
|
||||
import type { Command } from './src/types/commands.ts.js'
|
||||
|
||||
// MAKE THE BASIC BOT OBJECT
|
||||
const bot = createBot({
|
||||
token: configs.token,
|
||||
botId: configs.botId,
|
||||
intents: GatewayIntents.Guilds,
|
||||
events: {},
|
||||
})
|
||||
|
||||
// ENABLE ALL THE PLUGINS THAT WILL HELP MAKE IT EASIER TO CODE YOUR BOT
|
||||
enableHelpersPlugin(bot)
|
||||
enableCachePlugin(bot)
|
||||
enableCacheSweepers(bot as BotWithCache)
|
||||
enablePermissionsPlugin(bot as BotWithCache)
|
||||
|
||||
export interface BotClient extends BotWithCache<BotWithHelpersPlugin> {
|
||||
commands: Collection<string, Command>
|
||||
}
|
||||
|
||||
// THIS IS THE BOT YOU WANT TO USE EVERYWHERE IN YOUR CODE! IT HAS EVERYTHING BUILT INTO IT!
|
||||
export const Bot = bot as BotClient
|
||||
// PREPARE COMMANDS HOLDER
|
||||
Bot.commands = new Collection()
|
||||
@@ -1,19 +0,0 @@
|
||||
import { dotEnvConfig } from './deps.ts.js'
|
||||
|
||||
// Get the .env file that the user should have created, and get the token
|
||||
const env = dotEnvConfig({ export: true, path: './.env' })
|
||||
const token = env.BOT_TOKEN || ''
|
||||
|
||||
export interface Config {
|
||||
token: string
|
||||
botId: bigint
|
||||
}
|
||||
|
||||
export const configs = {
|
||||
/** Get token from ENV variable */
|
||||
token,
|
||||
/** Get the BotId from the token */
|
||||
botId: BigInt(atob(token.split('.')[0])),
|
||||
/** The server id where you develop your bot and want dev commands created. */
|
||||
devGuildId: BigInt(env.DEV_GUILD_ID!),
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export * from 'https://deno.land/x/discordeno@17.0.0/mod.ts'
|
||||
export * from 'https://deno.land/x/discordeno@17.0.0/plugins/mod.ts'
|
||||
|
||||
// Terminal Colors!
|
||||
export * from 'https://deno.land/std@0.117.0/fmt/colors.ts'
|
||||
// Get data from .env files
|
||||
export { config as dotEnvConfig } from 'https://deno.land/x/dotenv@v3.1.0/mod.ts'
|
||||
// Database, thx Tri!
|
||||
export { decode as KwikDecode, encode as KwikEncode, Kwik } from 'https://deno.land/x/kwik@v1.3.1/mod.ts'
|
||||
@@ -1,25 +0,0 @@
|
||||
import { startBot } from './deps.ts.js'
|
||||
import log from './src/utils/logger.ts.js'
|
||||
import { fileLoader, importDirectory } from './src/utils/loader.ts.js'
|
||||
import { updateApplicationCommands } from './src/utils/updateCommands.ts.js'
|
||||
// setup db
|
||||
import './src/database/mod.ts.js'
|
||||
import { Bot } from './bot.ts.js'
|
||||
|
||||
log.info('Starting bot...')
|
||||
|
||||
// Forces deno to read all the files which will fill the commands/inhibitors cache etc.
|
||||
await Promise.all(
|
||||
[
|
||||
'./src/commands',
|
||||
'./src/events',
|
||||
// "./src/tasks",
|
||||
].map((path) => importDirectory(Deno.realPathSync(path))),
|
||||
)
|
||||
await fileLoader()
|
||||
|
||||
// UPDATES YOUR COMMANDS TO LATEST COMMANDS
|
||||
await updateApplicationCommands()
|
||||
|
||||
// STARTS THE CONNECTION TO DISCORD
|
||||
await startBot(Bot)
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "dd-beginner-bot",
|
||||
"version": "1.0.0",
|
||||
"description": "An example bot for beginner developers to start coding discord bots.",
|
||||
"main": "dist/index.js",
|
||||
"type": "module",
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"packageManager": "yarn@4.0.2",
|
||||
"scripts": {
|
||||
"start": "node dist/index.js",
|
||||
"build": "swc src --strip-leading-paths --delete-dir-on-start --out-dir dist",
|
||||
"setup-dd": ""
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordeno/bot": "19.0.0-next.92bf166",
|
||||
"chalk": "^5.3.0",
|
||||
"dd-cache-proxy": "^2.1.1",
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.3.12",
|
||||
"@swc/core": "^1.6.3",
|
||||
"@types/node": "^20.14.6",
|
||||
"typescript": "^5.5.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Intents, createBot } from '@discordeno/bot'
|
||||
import { createProxyCache } from 'dd-cache-proxy'
|
||||
import { configs } from './config.js'
|
||||
|
||||
export const bot = createProxyCache(
|
||||
createBot({
|
||||
token: configs.token,
|
||||
intents: Intents.Guilds,
|
||||
}),
|
||||
{
|
||||
desiredProps: {
|
||||
guilds: ['id', 'name'],
|
||||
},
|
||||
cacheInMemory: {
|
||||
guilds: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// Setup desired proprieties
|
||||
bot.transformers.desiredProperties.interaction.id = true
|
||||
bot.transformers.desiredProperties.interaction.type = true
|
||||
bot.transformers.desiredProperties.interaction.data = true
|
||||
bot.transformers.desiredProperties.interaction.user = true
|
||||
bot.transformers.desiredProperties.interaction.token = true
|
||||
bot.transformers.desiredProperties.interaction.guildId = true
|
||||
|
||||
bot.transformers.desiredProperties.guild.id = true
|
||||
bot.transformers.desiredProperties.guild.name = true
|
||||
|
||||
bot.transformers.desiredProperties.user.username = true
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Collection, type ApplicationCommandOption, type ApplicationCommandTypes, type Interaction } from '@discordeno/bot'
|
||||
|
||||
export const commands = new Collection<string, Command>()
|
||||
|
||||
export function createCommand(command: Command): void {
|
||||
commands.set(command.name, command)
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
name: string
|
||||
description: string
|
||||
usage?: string[]
|
||||
options?: ApplicationCommandOption[]
|
||||
type: ApplicationCommandTypes
|
||||
/** Defaults to `Guild` */
|
||||
scope?: 'Global' | 'Guild'
|
||||
execute: (interaction: Interaction) => unknown
|
||||
subcommands?: Array<SubCommandGroup | SubCommand>
|
||||
}
|
||||
|
||||
export type SubCommand = Omit<Command, 'subcommands'>
|
||||
|
||||
export interface SubCommandGroup {
|
||||
name: string
|
||||
subCommands: SubCommand[]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Bot } from '../../bot.ts.js'
|
||||
import type { Command } from '../types/commands.ts.js'
|
||||
|
||||
export function createCommand(command: Command) {
|
||||
Bot.commands.set(command.name, command)
|
||||
}
|
||||
@@ -1,18 +1,15 @@
|
||||
import { ApplicationCommandTypes, InteractionResponseTypes } from '../../deps.ts.js'
|
||||
import { snowflakeToTimestamp } from '../utils/helpers.ts.js'
|
||||
import { createCommand } from './mod.ts.js'
|
||||
import { ApplicationCommandTypes, snowflakeToTimestamp } from '@discordeno/bot'
|
||||
import { createCommand } from '../commands.js'
|
||||
import { humanizeMilliseconds } from '../utils/helpers.js'
|
||||
|
||||
createCommand({
|
||||
name: 'ping',
|
||||
description: 'Ping the Bot!',
|
||||
type: ApplicationCommandTypes.ChatInput,
|
||||
execute: async (Bot, interaction) => {
|
||||
scope: 'Global',
|
||||
async execute(interaction) {
|
||||
const ping = Date.now() - snowflakeToTimestamp(interaction.id)
|
||||
await Bot.helpers.sendInteractionResponse(interaction.id, interaction.token, {
|
||||
type: InteractionResponseTypes.ChannelMessageWithSource,
|
||||
data: {
|
||||
content: `🏓 Pong! ${ping}ms`,
|
||||
},
|
||||
})
|
||||
|
||||
await interaction.respond(`🏓 Pong! Ping ${ping}ms (${humanizeMilliseconds(ping)})`)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
const token = process.env.BOT_TOKEN
|
||||
|
||||
if (!token) throw new Error('Missing BOT_TOKEN environment variable')
|
||||
|
||||
export const configs: Config = {
|
||||
/** Get token from ENV variable */
|
||||
token,
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
token: string
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Kwik, KwikDecode, KwikEncode } from '../../deps.ts.js'
|
||||
import { logger } from '../utils/logger.ts.js'
|
||||
|
||||
const log = logger({ name: 'DB Manager' })
|
||||
|
||||
log.info('Initializing Database')
|
||||
|
||||
const kwik = new Kwik()
|
||||
|
||||
// Add BigInt Support
|
||||
kwik.msgpackExtensionCodec.register({
|
||||
type: 0,
|
||||
encode: (object: unknown): Uint8Array | null => {
|
||||
if (typeof object === 'bigint') {
|
||||
if (object <= Number.MAX_SAFE_INTEGER && object >= Number.MIN_SAFE_INTEGER) {
|
||||
return KwikEncode(parseInt(object.toString(), 10), {})
|
||||
} else {
|
||||
return KwikEncode(object.toString(), {})
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
decode: (data: Uint8Array) => {
|
||||
return BigInt(KwikDecode(data, {}) as string)
|
||||
},
|
||||
})
|
||||
|
||||
// Initialize the Database
|
||||
await kwik.init()
|
||||
|
||||
log.info('Database Initialized!')
|
||||
@@ -0,0 +1,4 @@
|
||||
import { bot } from '../bot.js'
|
||||
import { updateGuildCommands } from '../utils/helpers.js'
|
||||
|
||||
bot.events.guildCreate = async (guild) => await updateGuildCommands(bot, guild)
|
||||
@@ -1,14 +1,94 @@
|
||||
import { Bot } from '../../bot.ts.js'
|
||||
import { InteractionTypes } from '../../deps.ts.js'
|
||||
import log from '../utils/logger.ts.js'
|
||||
import { ApplicationCommandOptionTypes, hasProperty, type Guild } from '@discordeno/bot'
|
||||
import chalk from 'chalk'
|
||||
import { bot } from '../bot.js'
|
||||
import { commands } from '../commands.js'
|
||||
import { getGuildFromId, isSubCommand, isSubCommandGroup } from '../utils/helpers.js'
|
||||
import { createLogger } from '../utils/logger.js'
|
||||
|
||||
Bot.events.interactionCreate = (_, interaction) => {
|
||||
if (!interaction.data) return
|
||||
const logger = createLogger({ name: 'Event: InteractionCreate' })
|
||||
|
||||
switch (interaction.type) {
|
||||
case InteractionTypes.ApplicationCommand:
|
||||
log.info(`[Application Command] ${interaction.data.name} command executed.`)
|
||||
Bot.commands.get(interaction.data.name!)?.execute(Bot, interaction)
|
||||
break
|
||||
bot.events.interactionCreate = async (interaction) => {
|
||||
if (!interaction.data || !interaction.id) return
|
||||
|
||||
let guildName = 'Direct Message'
|
||||
let guild = {} as Guild
|
||||
|
||||
// Set guild, if there was an error getting the guild, then just say it was a DM. (What else are we going to do?)
|
||||
if (interaction.guildId) {
|
||||
const guildOrVoid = await getGuildFromId(interaction.guildId).catch((err) => {
|
||||
logger.error(err)
|
||||
})
|
||||
|
||||
if (guildOrVoid) {
|
||||
guild = guildOrVoid
|
||||
guildName = guild.name
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[Command: ${chalk.bgYellow.black(interaction.data.name)} - ${chalk.bgBlack.white(`Trigger`)}] by @${interaction.user.username} in ${guildName}${guildName !== 'Direct Message' ? ` (${guild.id})` : ``}`,
|
||||
)
|
||||
|
||||
let command = commands.get(interaction.data.name)
|
||||
|
||||
if (!command) {
|
||||
logger.warn(
|
||||
`[Command: ${chalk.bgYellow.black(interaction.data.name)} - ${chalk.bgBlack.yellow(`Not Found`)}] by @${interaction.user.username} in ${guildName}${guildName !== 'Direct Message' ? ` (${guild.id})` : ``}`,
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (interaction.data.options?.[0]) {
|
||||
const optionType = interaction.data.options[0].type
|
||||
|
||||
if (optionType === ApplicationCommandOptionTypes.SubCommandGroup) {
|
||||
// Check if command has subcommand and handle types
|
||||
if (!command.subcommands) return
|
||||
|
||||
// Try to find the subcommand group
|
||||
const subCommandGroup = command.subcommands?.find((command) => command.name === interaction.data?.options?.[0].name)
|
||||
if (!subCommandGroup) return
|
||||
|
||||
if (isSubCommand(subCommandGroup)) return
|
||||
|
||||
// Get name of the command which we are looking for
|
||||
const targetCmdName = interaction.data.options?.[0].options?.[0].name ?? interaction.data.options?.[0].options?.[0].name
|
||||
if (!targetCmdName) return
|
||||
|
||||
// Try to find the command
|
||||
command = subCommandGroup.subCommands.find((c) => c.name === targetCmdName)
|
||||
}
|
||||
|
||||
if (optionType === ApplicationCommandOptionTypes.SubCommand) {
|
||||
// Check if command has subcommand and handle types
|
||||
if (!command?.subcommands) return
|
||||
|
||||
// Try to find the command
|
||||
const found = command.subcommands.find((command) => command.name === interaction.data?.options?.[0].name)
|
||||
if (!found) return
|
||||
|
||||
if (isSubCommandGroup(found)) return
|
||||
|
||||
command = found
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!command) throw new Error('Not command could be found')
|
||||
|
||||
await command.execute(interaction)
|
||||
|
||||
logger.info(
|
||||
`[Command: ${chalk.bgYellow.black(interaction.data.name)} - ${chalk.bgBlack.green(`Success`)}] by @${interaction.user.username} in ${guildName}${guildName !== 'Direct Message' ? ` (${guild.id})` : ``}`,
|
||||
)
|
||||
} catch (err) {
|
||||
logger.error(
|
||||
`[Command: ${chalk.bgYellow.black(interaction.data.name)} - ${chalk.bgBlack.red(`Error`)}] by @${interaction.user.username} in ${guildName}${guildName !== 'Direct Message' ? ` (${guild.id})` : ``}`,
|
||||
)
|
||||
|
||||
if (typeof err !== 'object' || !err || !hasProperty(err, 'message') || err.message === 'Not command could be found') return
|
||||
|
||||
logger.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import { Bot } from '../../bot.ts.js'
|
||||
import log from '../utils/logger.ts.js'
|
||||
import { ActivityTypes } from '@discordeno/bot'
|
||||
import { bot } from '../bot.js'
|
||||
import { createLogger } from '../utils/logger.js'
|
||||
|
||||
Bot.events.ready = (_, payload) => {
|
||||
log.info(`[READY] Shard ID ${payload.shardId} of ${Bot.gateway.lastShardId + 1} shards is ready!`)
|
||||
const logger = createLogger({ name: 'Event: Ready' })
|
||||
|
||||
if (payload.shardId === Bot.gateway.lastShardId) {
|
||||
botFullyReady()
|
||||
}
|
||||
}
|
||||
bot.events.ready = async ({ shardId }) => {
|
||||
logger.info('Bot Ready')
|
||||
|
||||
// This function lets you run custom code when all your bot's shards are online.
|
||||
function botFullyReady() {
|
||||
// DO STUFF YOU WANT HERE ONCE BOT IS FULLY ONLINE.
|
||||
log.info('[READY] Bot is fully online.')
|
||||
await bot.gateway.editShardStatus(shardId, {
|
||||
status: 'online',
|
||||
activities: [
|
||||
{
|
||||
name: 'Discordeno is the Best Lib',
|
||||
type: ActivityTypes.Game,
|
||||
timestamps: {
|
||||
start: Date.now(),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'dotenv/config'
|
||||
|
||||
import { bot } from './bot.js'
|
||||
import importDirectory from './utils/loader.js'
|
||||
import logger from './utils/logger.js'
|
||||
|
||||
logger.info('Starting bot...')
|
||||
|
||||
logger.info('Loading commands...')
|
||||
await importDirectory('./dist/commands')
|
||||
|
||||
logger.info('Loading events...')
|
||||
await importDirectory('./dist/events')
|
||||
|
||||
await bot.start()
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'dotenv/config'
|
||||
|
||||
import { bot } from './bot.js'
|
||||
import { updateCommands } from './utils/helpers.js'
|
||||
|
||||
bot.logger.info('Updating commands...')
|
||||
await updateCommands()
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { BotClient } from '../../bot.ts.js'
|
||||
import type { ApplicationCommandOption, ApplicationCommandTypes, Interaction } from '../../deps.ts.js'
|
||||
|
||||
export interface Command {
|
||||
/** The name of this command. */
|
||||
name: string
|
||||
/** What does this command do? */
|
||||
description: string
|
||||
/** The type of command this is. */
|
||||
type: ApplicationCommandTypes
|
||||
/** Whether or not this command is for the dev server only. */
|
||||
devOnly?: boolean
|
||||
/** The options for this command */
|
||||
options?: ApplicationCommandOption[]
|
||||
/** This will be executed when the command is run. */
|
||||
execute: (bot: BotClient, interaction: Interaction) => unknown
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// This file will export all of the types in this directory.
|
||||
|
||||
export * from './commands.ts.js'
|
||||
@@ -1,3 +1,101 @@
|
||||
export function snowflakeToTimestamp(id: bigint) {
|
||||
return Number(id / 4194304n + 1420070400000n)
|
||||
import { hasProperty, type Bot, type CreateApplicationCommand, type Guild } from '@discordeno/bot'
|
||||
import { bot } from '../bot.js'
|
||||
import { commands, type SubCommand, type SubCommandGroup } from '../commands.js'
|
||||
import { createLogger } from './logger.js'
|
||||
|
||||
const logger = createLogger({ name: 'Helpers' })
|
||||
|
||||
/** This function will update all commands, or the defined scope */
|
||||
export async function updateCommands(scope?: 'Guild' | 'Global'): Promise<void> {
|
||||
const globalCommands: Array<MakeRequired<CreateApplicationCommand, 'name'>> = []
|
||||
const perGuildCommands: Array<MakeRequired<CreateApplicationCommand, 'name'>> = []
|
||||
|
||||
for (const command of commands.values()) {
|
||||
if (command.scope === 'Guild') {
|
||||
perGuildCommands.push({
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
type: command.type,
|
||||
options: command.options ? command.options : undefined,
|
||||
})
|
||||
} else {
|
||||
globalCommands.push({
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
type: command.type,
|
||||
options: command.options ? command.options : undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (globalCommands.length && (scope === 'Global' || scope === undefined)) {
|
||||
logger.info('Updating Global Commands, changes should apply in short...')
|
||||
await bot.helpers.upsertGlobalApplicationCommands(globalCommands).catch(logger.error)
|
||||
}
|
||||
|
||||
if (perGuildCommands.length && (scope === 'Guild' || scope === undefined)) {
|
||||
await Promise.all(
|
||||
bot.cache.guilds.memory.map(async (guild: Guild) => {
|
||||
await bot.helpers.upsertGuildApplicationCommands(guild.id, perGuildCommands)
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Update commands for a guild */
|
||||
export async function updateGuildCommands(bot: Bot, guild: Guild): Promise<void> {
|
||||
const perGuildCommands: Array<MakeRequired<CreateApplicationCommand, 'name'>> = []
|
||||
|
||||
for (const command of commands.values()) {
|
||||
if (command.scope === 'Guild') {
|
||||
perGuildCommands.push({
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
type: command.type,
|
||||
options: command.options ? command.options : undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (perGuildCommands.length) {
|
||||
await bot.helpers.upsertGuildApplicationCommands(guild.id, perGuildCommands)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getGuildFromId(guildId: bigint): Promise<Guild> {
|
||||
const cached = await bot.cache.guilds.get(guildId)
|
||||
|
||||
if (cached) return cached
|
||||
|
||||
return await bot.helpers.getGuild(guildId)
|
||||
}
|
||||
|
||||
export function humanizeMilliseconds(milliseconds: number): string {
|
||||
// Gets ms into seconds
|
||||
const time = milliseconds / 1000
|
||||
if (time < 1) return '< 1s'
|
||||
|
||||
const days = Math.floor(time / 86400)
|
||||
const hours = Math.floor((time % 86400) / 3600)
|
||||
const minutes = Math.floor(((time % 86400) % 3600) / 60)
|
||||
const seconds = Math.floor(((time % 86400) % 3600) % 60)
|
||||
|
||||
const dayString = days ? `${days}d ` : ''
|
||||
const hourString = hours ? `${hours}h ` : ''
|
||||
const minuteString = minutes ? `${minutes}m ` : ''
|
||||
const secondString = seconds ? `${seconds}s ` : ''
|
||||
|
||||
return `${dayString}${hourString}${minuteString}${secondString}`
|
||||
}
|
||||
|
||||
export function isSubCommand(data: SubCommand | SubCommandGroup): data is SubCommand {
|
||||
return !hasProperty(data, 'subCommands')
|
||||
}
|
||||
|
||||
export function isSubCommandGroup(data: SubCommand | SubCommandGroup): data is SubCommandGroup {
|
||||
return hasProperty(data, 'subCommands')
|
||||
}
|
||||
|
||||
type MakeRequired<TObj, TKey extends keyof TObj> = TObj & {
|
||||
[Key in TKey]-?: TObj[Key]
|
||||
}
|
||||
|
||||
@@ -1,40 +1,15 @@
|
||||
import log from './logger.ts.js'
|
||||
import { readdir } from 'node:fs/promises'
|
||||
import logger from './logger.js'
|
||||
|
||||
// Very important to make sure files are reloaded properly
|
||||
let uniqueFilePathCounter = 0
|
||||
let paths: string[] = []
|
||||
export default async function importDirectory(folder: string): Promise<void> {
|
||||
const files = await readdir(folder, { recursive: true })
|
||||
|
||||
/** This function allows reading all files in a folder. Useful for loading/reloading commands, monitors etc */
|
||||
export async function importDirectory(path: string) {
|
||||
path = path.replaceAll('\\', '/')
|
||||
const files = Deno.readDirSync(Deno.realPathSync(path))
|
||||
const folder = path.substring(path.indexOf('/src/') + 5)
|
||||
for (const filename of files) {
|
||||
if (!filename.endsWith('.js')) continue
|
||||
|
||||
if (!folder.includes('/')) log.info(`Loading ${folder}...`)
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.name) continue
|
||||
|
||||
const currentPath = `${path}/${file.name}`
|
||||
if (file.isFile) {
|
||||
if (!currentPath.endsWith('.ts')) continue
|
||||
paths.push(
|
||||
`import "${Deno.mainModule.substring(0, Deno.mainModule.lastIndexOf('/'))}/${currentPath.substring(
|
||||
currentPath.indexOf('src/'),
|
||||
)}#${uniqueFilePathCounter}";`,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
await importDirectory(currentPath)
|
||||
// Using `file://` and `process.cwd()` to avoid weird issues with relative paths and/or Windows
|
||||
await import(`file://${process.cwd()}/${folder}/${filename}`).catch((x) =>
|
||||
logger.fatal(`Cannot import file (${folder}/${filename}) for reason:`, x),
|
||||
)
|
||||
}
|
||||
|
||||
uniqueFilePathCounter++
|
||||
}
|
||||
|
||||
/** Imports all everything in fileloader.ts */
|
||||
export async function fileLoader() {
|
||||
await Deno.writeTextFile('fileloader.ts', paths.join('\n').replaceAll('\\', '/'))
|
||||
await import(`${Deno.mainModule.substring(0, Deno.mainModule.lastIndexOf('/'))}/fileloader.ts#${uniqueFilePathCounter}`)
|
||||
paths = []
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { bold, cyan, gray, italic, red, yellow } from '../../deps.ts.js'
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
import chalk from 'chalk'
|
||||
|
||||
export enum LogLevels {
|
||||
Debug,
|
||||
@@ -19,21 +19,15 @@ const prefixes = new Map<LogLevels, string>([
|
||||
|
||||
const noColor: (str: string) => string = (msg) => msg
|
||||
const colorFunctions = new Map<LogLevels, (str: string) => string>([
|
||||
[LogLevels.Debug, gray],
|
||||
[LogLevels.Info, cyan],
|
||||
[LogLevels.Warn, yellow],
|
||||
[LogLevels.Error, (str: string) => red(str)],
|
||||
[LogLevels.Fatal, (str: string) => red(bold(italic(str)))],
|
||||
[LogLevels.Debug, chalk.gray],
|
||||
[LogLevels.Info, chalk.cyan],
|
||||
[LogLevels.Warn, chalk.yellow],
|
||||
[LogLevels.Error, (str: string) => chalk.red(str)],
|
||||
[LogLevels.Fatal, (str: string) => chalk.red.bold.italic(str)],
|
||||
])
|
||||
|
||||
export function logger({
|
||||
logLevel = LogLevels.Info,
|
||||
name,
|
||||
}: {
|
||||
logLevel?: LogLevels
|
||||
name?: string
|
||||
} = {}) {
|
||||
function log(level: LogLevels, ...args: any[]) {
|
||||
export function createLogger({ logLevel = LogLevels.Info, name }: { logLevel?: LogLevels; name?: string } = {}): Logger {
|
||||
function log(level: LogLevels, ...args: any[]): void {
|
||||
if (level < logLevel) return
|
||||
|
||||
let color = colorFunctions.get(level)
|
||||
@@ -42,7 +36,7 @@ export function logger({
|
||||
const date = new Date()
|
||||
const log = [
|
||||
`[${date.toLocaleDateString()} ${date.toLocaleTimeString()}]`,
|
||||
color(prefixes.get(level) || 'DEBUG'),
|
||||
color(prefixes.get(level) ?? 'DEBUG'),
|
||||
name ? `${name} >` : '>',
|
||||
...args,
|
||||
]
|
||||
@@ -63,27 +57,27 @@ export function logger({
|
||||
}
|
||||
}
|
||||
|
||||
function setLevel(level: LogLevels) {
|
||||
function setLevel(level: LogLevels): void {
|
||||
logLevel = level
|
||||
}
|
||||
|
||||
function debug(...args: any[]) {
|
||||
function debug(...args: any[]): void {
|
||||
log(LogLevels.Debug, ...args)
|
||||
}
|
||||
|
||||
function info(...args: any[]) {
|
||||
function info(...args: any[]): void {
|
||||
log(LogLevels.Info, ...args)
|
||||
}
|
||||
|
||||
function warn(...args: any[]) {
|
||||
function warn(...args: any[]): void {
|
||||
log(LogLevels.Warn, ...args)
|
||||
}
|
||||
|
||||
function error(...args: any[]) {
|
||||
function error(...args: any[]): void {
|
||||
log(LogLevels.Error, ...args)
|
||||
}
|
||||
|
||||
function fatal(...args: any[]) {
|
||||
function fatal(...args: any[]): void {
|
||||
log(LogLevels.Fatal, ...args)
|
||||
}
|
||||
|
||||
@@ -98,5 +92,15 @@ export function logger({
|
||||
}
|
||||
}
|
||||
|
||||
export const log = logger({ name: 'Main' })
|
||||
export default log
|
||||
export const logger = createLogger({ name: 'Main' })
|
||||
export default logger
|
||||
|
||||
export interface Logger {
|
||||
log: (level: LogLevels, ...args: any[]) => void
|
||||
debug: (...args: any[]) => void
|
||||
info: (...args: any[]) => void
|
||||
warn: (...args: any[]) => void
|
||||
error: (...args: any[]) => void
|
||||
fatal: (...args: any[]) => void
|
||||
setLevel: (level: LogLevels) => void
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Bot } from '../../bot.ts.js'
|
||||
import { configs } from '../../configs.ts.js'
|
||||
|
||||
export async function updateApplicationCommands() {
|
||||
await Bot.helpers.upsertGlobalApplicationCommands(
|
||||
Bot.commands
|
||||
// ONLY GLOBAL COMMANDS
|
||||
.filter((command) => !command.devOnly)
|
||||
.array(),
|
||||
)
|
||||
|
||||
await Bot.helpers.upsertGuildApplicationCommands(
|
||||
configs.devGuildId,
|
||||
Bot.commands
|
||||
// ONLY GLOBAL COMMANDS
|
||||
.filter((command) => !!command.devOnly)
|
||||
.array(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"moduleResolution": "node",
|
||||
"skipDefaultLibCheck": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"incremental": true
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user