mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-02 08:50:07 +00:00
Update nodejs template
Discordeno.js (DD v13) -> DD v19 "raw" Currently the permission checking is not working correctly
This commit is contained in:
2
examples/nodejs-19/.env.example
Normal file
2
examples/nodejs-19/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
TOKEN=
|
||||
OWNERS= # Comma separeted list of user id(s) to consider bot owner(s)
|
||||
32
examples/nodejs-19/.gitignore
vendored
Normal file
32
examples/nodejs-19/.gitignore
vendored
Normal file
@@ -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
|
||||
24
examples/nodejs-19/.swcrc
Normal file
24
examples/nodejs-19/.swcrc
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
16
examples/nodejs-19/README.md
Normal file
16
examples/nodejs-19/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Beginner Bot Template
|
||||
|
||||
This template is designed for the beginner developer to start coding discord bots.
|
||||
|
||||
## Setup
|
||||
|
||||
- Download the source
|
||||
- Install yarn
|
||||
- Rename the .env.example file to .env OR create a new .env file and copy the example file code to this new file.
|
||||
- Fill out the .env file
|
||||
|
||||
## Run Bot
|
||||
|
||||
- run `yarn` to install the dependencies
|
||||
- run `yarn build` to build the source
|
||||
- run `yarn start` to run the bot
|
||||
26
examples/nodejs-19/package.json
Normal file
26
examples/nodejs-19/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"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.ad7e74c",
|
||||
"dd-cache-proxy": "^2.1.1",
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/cli": "^0.3.12",
|
||||
"@swc/core": "^1.5.25",
|
||||
"@types/node": "^20.14.2",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
45
examples/nodejs-19/src/bot.ts
Normal file
45
examples/nodejs-19/src/bot.ts
Normal 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
|
||||
20
examples/nodejs-19/src/commands.ts
Normal file
20
examples/nodejs-19/src/commands.ts
Normal 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
|
||||
}
|
||||
15
examples/nodejs-19/src/commands/ping.ts
Normal file
15
examples/nodejs-19/src/commands/ping.ts
Normal 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 })
|
||||
},
|
||||
})
|
||||
72
examples/nodejs-19/src/commands/warn.ts
Normal file
72
examples/nodejs-19/src/commands/warn.ts
Normal 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 })
|
||||
},
|
||||
})
|
||||
15
examples/nodejs-19/src/config.ts
Normal file
15
examples/nodejs-19/src/config.ts
Normal 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[]
|
||||
}
|
||||
19
examples/nodejs-19/src/events/interactionCreate.ts
Normal file
19
examples/nodejs-19/src/events/interactionCreate.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
8
examples/nodejs-19/src/events/ready.ts
Normal file
8
examples/nodejs-19/src/events/ready.ts
Normal 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}`)
|
||||
}
|
||||
}
|
||||
18
examples/nodejs-19/src/index.ts
Normal file
18
examples/nodejs-19/src/index.ts
Normal 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()
|
||||
15
examples/nodejs-19/src/utils/loader.ts
Normal file
15
examples/nodejs-19/src/utils/loader.ts
Normal 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),
|
||||
)
|
||||
}
|
||||
}
|
||||
21
examples/nodejs-19/src/utils/permissions.ts
Normal file
21
examples/nodejs-19/src/utils/permissions.ts
Normal 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
|
||||
}
|
||||
6
examples/nodejs-19/src/utils/updateCommands.ts
Normal file
6
examples/nodejs-19/src/utils/updateCommands.ts
Normal 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())
|
||||
}
|
||||
14
examples/nodejs-19/tsconfig.json
Normal file
14
examples/nodejs-19/tsconfig.json
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
2116
examples/nodejs-19/yarn.lock
Normal file
2116
examples/nodejs-19/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user