Update nodejs template

Discordeno.js (DD v13) -> DD v19 "raw"

Currently the permission checking is not working correctly
This commit is contained in:
Fleny
2024-06-07 22:11:53 +02:00
parent b4f67f62c6
commit a3d73ea94a
18 changed files with 2484 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
import { createBot, Intents, LogDepth, type logger as discordenoLogger } 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 | Intents.GuildMessages | Intents.MessageContent,
}),
{
desiredProps: {
guilds: ['id', 'name', 'roles'],
roles: ['id', 'permissions'],
},
cacheInMemory: {
guilds: true,
roles: true,
default: false,
},
},
)
// By default, bot.logger will use an instance of the logger from @discordeno/bot, this logger supports depth and we need to change it, so we need to say to TS that we know what we are doing with as
;(bot.logger as typeof discordenoLogger).setDepth(LogDepth.Full)
// 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.token = true
bot.transformers.desiredProperties.interaction.guildId = true
bot.transformers.desiredProperties.interaction.member = true
bot.transformers.desiredProperties.guild.id = true
bot.transformers.desiredProperties.guild.name = true
bot.transformers.desiredProperties.role.id = true
bot.transformers.desiredProperties.role.permissions = true
bot.transformers.desiredProperties.member.id = true
bot.transformers.desiredProperties.member.roles = true
bot.transformers.desiredProperties.user.id = true
bot.transformers.desiredProperties.user.username = true
bot.transformers.desiredProperties.user.discriminator = true

View File

@@ -0,0 +1,20 @@
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 {
/** The name of this command. */
name: string
/** What does this command do? */
description: string
/** The type of command this is. */
type: ApplicationCommandTypes
/** The options for this command */
options?: ApplicationCommandOption[]
/** This will be executed when the command is run. */
execute: (interaction: Interaction, options: Record<string, unknown>) => unknown
}

View File

@@ -0,0 +1,15 @@
import { ApplicationCommandTypes, createEmbeds, snowflakeToTimestamp } from '@discordeno/bot'
import { createCommand } from '../commands.js'
createCommand({
name: 'ping',
description: 'See if the bot latency is okay',
type: ApplicationCommandTypes.ChatInput,
async execute(interaction) {
const ping = Date.now() - snowflakeToTimestamp(interaction.id)
const embeds = createEmbeds().setTitle(`The bot ping is ${ping}ms`)
await interaction.respond({ embeds })
},
})

View File

@@ -0,0 +1,72 @@
import { ApplicationCommandOptionTypes, ApplicationCommandTypes, createEmbeds, Permissions, type User } from '@discordeno/bot'
import { bot } from '../bot.js'
import { createCommand } from '../commands.js'
import { calculateMemberPermissions } from '../utils/permissions.js'
createCommand({
name: 'warn',
description: 'Warn a user from the server',
type: ApplicationCommandTypes.ChatInput,
options: [
{
name: 'user',
description: 'The user you want to warn',
type: ApplicationCommandOptionTypes.User,
required: true,
},
{
name: 'reason',
description: 'The reason for the warn',
type: ApplicationCommandOptionTypes.String,
maxLength: 300,
},
],
async execute(interaction, options) {
if (!interaction.guildId || !interaction.member) {
await interaction.respond('This command can only be ran in guilds')
return
}
// Type based on the options declared above
const { user, reason } = options as { user: User; reason?: string }
const guild = await bot.cache.guilds.get(interaction.guildId)
if (!guild) return
await interaction.defer()
const perms = new Permissions(await calculateMemberPermissions(guild, interaction.member))
const adminPerm = perms.has('ADMINISTRATOR')
const kickMembersPerm = adminPerm || perms.has('KICK_MEMBERS')
if (!kickMembersPerm) {
await interaction.respond("You don't have the necessary permissions to warn a members (this command requires `Kick members`)")
return
}
const embeds = createEmbeds()
.setTitle('Warned User:')
.setDescription(`User ID: <@${user.id}>\n Reason: ${reason}`)
.setColor(0x00ff00)
.setTimestamp(Date.now())
const warnEmbeds = createEmbeds()
.setTitle('Warning:')
.setDescription(`You have been warned in **${guild.name}** for \`${reason}\``)
.setTimestamp(Date.now())
try {
const dmChannel = await bot.helpers.getDmChannel(user.id)
await bot.helpers.sendMessage(dmChannel.id, { embeds: warnEmbeds })
} catch (error) {
bot.logger.error(`There was an error in the warn command:`, error)
await interaction.respond(`Could not warn user <@${user.id}> | They likely do not have their DMs open.`)
return
}
await interaction.respond({ embeds })
},
})

View File

@@ -0,0 +1,15 @@
const token = process.env.TOKEN
const owners = process.env.OWNERS
if (!token) throw new Error('Missing TOKEN environment variable')
if (!owners) throw new Error('Missing OWNERS environment variable')
export const configs: Config = {
token,
owners: owners.split(',').map(BigInt),
}
export interface Config {
token: string
owners: bigint[]
}

View File

@@ -0,0 +1,19 @@
import { InteractionTypes, commandOptionsParser } from '@discordeno/bot'
import { bot } from '../bot.js'
import { commands } from '../commands.js'
bot.events.interactionCreate = async (interaction) => {
if (!interaction.data || interaction.type !== InteractionTypes.ApplicationCommand) return
const command = commands.get(interaction.data.name)
if (!command) return
const options = commandOptionsParser(interaction)
try {
await command.execute(interaction, options)
} catch (error) {
bot.logger.error(`There was an error running the ${command.name} command.`, error)
}
}

View File

@@ -0,0 +1,8 @@
import { bot } from '../bot.js'
bot.events.ready = ({ user, shardId }) => {
if (shardId === bot.gateway.lastShardId) {
// All shards are ready
bot.logger.info(`Successfully connected to the gateway as ${user.username}#${user.discriminator}`)
}
}

View File

@@ -0,0 +1,18 @@
import 'dotenv/config'
import { bot } from './bot.js'
import importDirectory from './utils/loader.js'
import { updateApplicationCommands } from './utils/updateCommands.js'
bot.logger.info('Starting bot...')
bot.logger.info('Loading commands...')
await importDirectory('./dist/commands')
bot.logger.info('Loading events...')
await importDirectory('./dist/events')
bot.logger.info('Updating commands...')
await updateApplicationCommands()
await bot.start()

View File

@@ -0,0 +1,15 @@
import { logger } from '@discordeno/bot'
import { readdir } from 'node:fs/promises'
export default async function importDirectory(folder: string): Promise<void> {
const files = await readdir(folder, { recursive: true })
for (const filename of files) {
if (!filename.endsWith('.js')) continue
// 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),
)
}
}

View File

@@ -0,0 +1,21 @@
import { BitwisePermissionFlags, type Guild, type Member } from '@discordeno/bot'
import assert from 'node:assert'
export async function calculateMemberPermissions(guild: Guild, member: Member): Promise<bigint> {
if (member.id === guild.ownerId) return 8n
// FIXME: currently not working
let permissions = guild.roles.get(guild.id)?.permissions.bitfield
const rolePerms = member.roles.map((x) => guild.roles.get(x)?.permissions.bitfield).filter((x): x is bigint => x !== undefined)
// Small hack to avoid calling assert with 0n
if (permissions === undefined) assert(permissions)
for (const rolePerm of rolePerms) {
permissions |= rolePerm
}
if ((permissions & BigInt(BitwisePermissionFlags.ADMINISTRATOR)) === BigInt(BitwisePermissionFlags.ADMINISTRATOR)) return 8n
return permissions
}

View File

@@ -0,0 +1,6 @@
import { bot } from '../bot.js'
import { commands } from '../commands.js'
export async function updateApplicationCommands(): Promise<void> {
await bot.helpers.upsertGlobalApplicationCommands(commands.array())
}