Skip to content

azaresw/devcodes-djs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

devcodes-djs

The essential discord.js v14 toolkit — prefix commands, slash commands, context menus, modals, select menus, embed presets, button paginator, event handler, and full Components V2 container support. One install, everything you need.

Install

npm install devcodes-djs

Peer requirement: discord.js ^14.0.0
Container components require discord.js >=14.18.0


Event Handler

Organize all your client events in one clean, chainable place instead of scattered client.on() calls.

const { EventHandler } = require('devcodes-djs');

const events = new EventHandler()
  .once('ready', (client) => console.log(`Logged in as ${client.user.tag}`))
  .on('guildMemberAdd', (member) => {
    member.guild.systemChannel?.send(`👋 Welcome ${member}!`);
  })
  .on('messageDelete', (message) => {
    console.log(`Message deleted in #${message.channel.name}`);
  });

events.attach(client); // binds all listeners at once

Modals

Build and handle multi-field modal (popup form) interactions.

const { buildModal, ModalHandler, TextInputStyle } = require('devcodes-djs');

const feedbackModal = buildModal({
  id:    'feedback_form',
  title: 'Submit Feedback',
  fields: [
    {
      id:          'name',
      label:       'Your name',
      style:       TextInputStyle.Short,
      placeholder: 'e.g. John',
      required:    true,
    },
    {
      id:        'message',
      label:     'Your feedback',
      style:     TextInputStyle.Paragraph,
      minLength: 10,
      maxLength: 1000,
    },
  ],
});

// Show the modal from a slash command or button
slash.command({
  name:        'feedback',
  description: 'Submit feedback',
  execute: async (interaction) => {
    await interaction.showModal(feedbackModal);
  },
});

// Handle the submission
const modals = new ModalHandler()
  .on('feedback_form', async (interaction) => {
    const name    = interaction.fields.getTextInputValue('name');
    const message = interaction.fields.getTextInputValue('message');
    await interaction.reply({ content: `Thanks ${name}! Got: "${message}"`, ephemeral: true });
  });

modals.attach(client);

Dynamic modal IDs

Use ID prefixes to handle modals with dynamic IDs:

modals.on('report_', async (interaction) => {
  // matches "report_123", "report_456", etc.
  const userId = interaction.customId.replace('report_', '');
  await interaction.reply({ content: `Report filed for user ${userId}`, ephemeral: true });
});

Select Menus

Build all 5 Discord select menu types and handle their interactions.

String select

const { SelectMenus, SelectHandler } = require('devcodes-djs');

const colorMenu = SelectMenus.stringSelect({
  id:          'color_pick',
  placeholder: 'Choose a color',
  options: [
    { label: 'Red',   value: 'red',   emoji: '🔴' },
    { label: 'Green', value: 'green', emoji: '🟢', default: true },
    { label: 'Blue',  value: 'blue',  emoji: '🔵' },
  ],
  maxValues: 2,
});

await message.reply({ components: [colorMenu] });

// Handle selection
const selects = new SelectHandler()
  .on('color_pick', async (interaction) => {
    await interaction.reply(`You picked: **${interaction.values.join(', ')}**`);
  });

selects.attach(client);

Other select types

// User picker
const picker = SelectMenus.userSelect({ id: 'pick_user', placeholder: 'Pick a user', maxValues: 3 });

// Role picker
const roles  = SelectMenus.roleSelect({ id: 'pick_role', placeholder: 'Pick a role' });

// Channel picker (text channels only)
const chans  = SelectMenus.channelSelect({ id: 'pick_chan', channelTypes: [ChannelType.GuildText] });

// Mentionable (users + roles)
const ment   = SelectMenus.mentionableSelect({ id: 'pick_mention' });

Context Menus

Add right-click commands to users and messages.

const { ContextMenuHandler } = require('devcodes-djs');

const ctx = new ContextMenuHandler({
  clientId: process.env.CLIENT_ID,
  token:    process.env.BOT_TOKEN,
  guildId:  process.env.GUILD_ID,
});

ctx
  .command({
    type:    'user',
    name:    'Get Avatar',
    execute: async (interaction) => {
      const url = interaction.targetUser.displayAvatarURL({ size: 512 });
      await interaction.reply({ content: url, ephemeral: true });
    },
  })
  .command({
    type:    'user',
    name:    'User Info',
    execute: async (interaction) => {
      const user = interaction.targetUser;
      await interaction.reply({
        embeds: [Embeds.info(`👤 ${user.tag}`, `ID: \`${user.id}\`\nCreated: <t:${Math.floor(user.createdTimestamp / 1000)}:R>`)],
        ephemeral: true,
      });
    },
  })
  .command({
    type:    'message',
    name:    'Translate Message',
    execute: async (interaction) => {
      await interaction.reply({ content: `📝 Content: ${interaction.targetMessage.content}`, ephemeral: true });
    },
  });

ctx.attach(client);

client.once('ready', () => ctx.deploy());

Slash Commands

Register, deploy, and handle application (/) commands with full support for options, subcommands, permissions, and autocomplete.

Basic setup

const { Client, GatewayIntentBits } = require('discord.js');
const { SlashHandler, Embeds } = require('devcodes-djs');

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

const slash = new SlashHandler({
  clientId: process.env.CLIENT_ID,
  token:    process.env.BOT_TOKEN,
  guildId:  process.env.GUILD_ID, // omit for global commands
});

slash
  .command({
    name:        'ping',
    description: 'Check bot latency',
    execute: async (interaction) => {
      await interaction.reply({
        embeds: [Embeds.success('Pong!', `Latency: ${client.ws.ping}ms`)],
      });
    },
  })
  .command({
    name:        'say',
    description: 'Make the bot say something',
    options: [
      { name: 'message', description: 'What to say',        type: 'string',  required: true },
      { name: 'ephemeral', description: 'Only visible to you', type: 'boolean' },
    ],
    execute: async (interaction) => {
      const text      = interaction.options.getString('message', true);
      const ephemeral = interaction.options.getBoolean('ephemeral') ?? false;
      await interaction.reply({ content: text, ephemeral });
    },
  });

// Attach to client — handles interactionCreate automatically
slash.attach(client);

client.once('ready', async () => {
  await slash.deploy(); // register commands with Discord
  console.log('Ready!');
});

client.login(process.env.BOT_TOKEN);

Subcommands

slash.command({
  name:        'settings',
  description: 'Manage bot settings',
  subcommands: [
    {
      name:        'set',
      description: 'Change a setting',
      options: [
        { name: 'key',   description: 'Setting name',  type: 'string', required: true },
        { name: 'value', description: 'Setting value', type: 'string', required: true },
      ],
      execute: async (interaction) => {
        const key   = interaction.options.getString('key', true);
        const value = interaction.options.getString('value', true);
        await interaction.reply({ embeds: [Embeds.success('Updated', `\`${key}\` → \`${value}\``)] });
      },
    },
    {
      name:        'reset',
      description: 'Reset all settings to defaults',
      execute: async (interaction) => {
        await interaction.reply({ embeds: [Embeds.warn('Reset', 'All settings restored.')] });
      },
    },
  ],
});

Autocomplete

const fruits = ['apple', 'banana', 'cherry', 'durian', 'elderberry'];

slash.command({
  name:        'fruit',
  description: 'Pick a fruit',
  options: [
    { name: 'name', description: 'Fruit name', type: 'string', required: true, autocomplete: true },
  ],
  autocomplete: async (interaction) => {
    const focused = interaction.options.getFocused().toLowerCase();
    await interaction.respond(
      fruits.filter(f => f.startsWith(focused)).map(f => ({ name: f, value: f }))
    );
  },
  execute: async (interaction) => {
    await interaction.reply(`You picked: **${interaction.options.getString('name', true)}**`);
  },
});

Permission guard

slash.command({
  name:        'ban',
  description: 'Ban a user',
  permissions: ['BanMembers'],
  options: [
    { name: 'user', description: 'User to ban', type: 'user', required: true },
  ],
  execute: async (interaction) => {
    const user = interaction.options.getUser('user', true);
    await interaction.reply({ embeds: [Embeds.success('Banned', `${user.tag} was banned.`)] });
  },
});

Option types

Type discord.js getter
string options.getString(name)
integer options.getInteger(name)
number options.getNumber(name)
boolean options.getBoolean(name)
user options.getUser(name)
channel options.getChannel(name)
role options.getRole(name)
mentionable options.getMentionable(name)
attachment options.getAttachment(name)

SlashHandlerConfig

Option Type Description
clientId string Your bot's application ID
token string Bot token for REST deployment
guildId string? Deploy to a specific guild (instant). Omit for global.

Command Handler

Replace giant if/else chains with a clean, chainable command registry. Supports aliases, per-user cooldowns, and permission guards.

const { CommandHandler } = require('devcodes-djs');

const handler = new CommandHandler({ prefix: '!' });

handler
  .command('ping', async (message) => {
    await message.reply('Pong!');
  })
  .command('hello', async (message, args) => {
    await message.reply(`Hey ${args[0] ?? 'there'}!`);
  }, {
    aliases: ['hi', 'hey'],
    description: 'Say hello',
    usage: '[name]',
  })
  .command('ban', async (message, args) => {
    await message.reply(`Banned ${args[0]}`);
  }, {
    permissions: ['BanMembers'],
    cooldown: 5000,
    description: 'Ban a user',
    usage: '<user>',
  });

// In your messageCreate listener:
client.on('messageCreate', (message) => handler.handle(message));

Auto help command

Use handler.getCommands() to build a dynamic help menu:

handler.command('help', async (message) => {
  const lines = handler.getCommands()
    .map(cmd => `**!${cmd.name}** ${cmd.options.usage ?? ''}${cmd.options.description ?? ''}`)
    .join('\n');
  await message.reply(lines);
});

HandlerConfig

Option Type Default Description
prefix string "!" Command prefix
ignoreBots boolean true Ignore messages from bots

CommandOptions

Option Type Default Description
aliases string[] [] Alternative trigger names
cooldown number 0 Per-user cooldown in ms
permissions PermissionResolvable[] [] Required member permissions
description string Short description
usage string Usage hint e.g. <user> [reason]

Embed Presets

One-liner embeds with consistent colors — no more chaining .setColor().setTitle().setFooter() every time.

const { Embeds } = require('devcodes-djs');

// ✅ Green
await message.reply({ embeds: [Embeds.success('Done!', 'Operation completed.')] });

// ❌ Red
await message.reply({ embeds: [Embeds.error('Failed', 'Something went wrong.')] });

// ℹ️ Blurple
await message.reply({ embeds: [Embeds.info('Info', 'Here is some information.')] });

// ⚠️ Yellow
await message.reply({ embeds: [Embeds.warn('Warning', 'Be careful.')] });

// 🎨 Any color
await message.reply({ embeds: [Embeds.custom(0xFF8800, { title: 'Custom', description: 'Orange embed' })] });

With extra options

await message.reply({
  embeds: [
    Embeds.success('Deployment Complete', 'Version **1.4.2** is live.', {
      fields: [
        { name: 'Environment', value: 'Production', inline: true },
        { name: 'Duration',    value: '42s',        inline: true },
      ],
      footer:    'Dev Codes CI',
      thumbnail: 'https://example.com/logo.png',
      timestamp: true,
    }),
  ],
});

Preset colors

Method Color
Embeds.success() #57F287 (green)
Embeds.error() #ED4245 (red)
Embeds.info() #5865F2 (blurple)
Embeds.warn() #FEE75C (yellow)
Embeds.custom(color, opts) Any hex integer

Button Paginator

Reply with a paginated embed controlled by ⏮ ◀ ✖ ▶ ⏭ buttons. Buttons disable automatically at the first/last page and are removed when the collector times out.

const { paginate, Embeds } = require('devcodes-djs');

handler.command('list', async (message) => {
  const pages = [
    Embeds.info('Page 1', 'First batch of results...'),
    Embeds.info('Page 2', 'Second batch of results...'),
    Embeds.info('Page 3', 'Third batch of results...'),
  ];

  await paginate(message, pages, { timeout: 60_000, authorOnly: true });
});

PaginatorOptions

Option Type Default Description
timeout number 30000 Button collector timeout in ms
authorOnly boolean true Only the command author can interact

Containers (Components V2)

Build rich Discord Components V2 messages with a clean, fluent API. Containers support text, sections with thumbnails, separators, media galleries, and button rows — all composable.

⚠️ Components V2 messages require the MessageFlags.IsComponentsV2 flag and cannot include embeds.

Quick compose

const { Containers } = require('devcodes-djs');
const { MessageFlags, ButtonBuilder, ButtonStyle } = require('discord.js');

const c = Containers.compose({
  color:     0x57F287,
  header:    '✅ Deployment complete',
  body:      'Version **1.4.2** is now live on production.',
  thumbnail: 'https://example.com/logo.png',
  images:    ['https://example.com/screenshot.png'],
  buttons: [
    new ButtonBuilder().setCustomId('view').setLabel('View Logs').setStyle(ButtonStyle.Primary),
    new ButtonBuilder().setCustomId('rollback').setLabel('Rollback').setStyle(ButtonStyle.Danger),
  ],
});

await message.reply({
  components: [c],
  flags: MessageFlags.IsComponentsV2,
});

Build manually

Use the individual factory functions for full control:

const {
  container, text, section, separator, mediaGallery, buttonRow,
  SeparatorSpacingSize,
} = require('devcodes-djs');
const { ButtonBuilder, ButtonStyle, MessageFlags } = require('discord.js');

const c = container(0x5865F2) // accent color
  .addTextDisplayComponents(
    text('**📦 New Release — v2.0.0**')
  )
  .addSeparatorComponents(
    separator(true, SeparatorSpacingSize.Large)
  )
  .addSectionComponents(
    section('A major update with breaking changes. Read the migration guide before upgrading.', 'https://example.com/icon.png')
  )
  .addTextDisplayComponents(
    text('**Changes:**\n- Removed deprecated API\n- New `compose()` API\n- Performance improvements')
  )
  .addSeparatorComponents(separator())
  .addMediaGalleryComponents(
    mediaGallery([
      { url: 'https://example.com/before.png', description: 'Before' },
      { url: 'https://example.com/after.png',  description: 'After'  },
    ])
  )
  .addActionRowComponents(
    buttonRow([
      new ButtonBuilder().setCustomId('changelog').setLabel('Changelog').setStyle(ButtonStyle.Primary),
      new ButtonBuilder().setURL('https://docs.example.com').setLabel('Docs').setStyle(ButtonStyle.Link),
    ])
  );

await message.reply({ components: [c], flags: MessageFlags.IsComponentsV2 });

Container components

Function What it creates
container(color?, spoiler?) Top-level container, wraps everything
text(content) Plain / markdown text block
section(content, imageUrl?) Text + optional right-hand thumbnail
separator(divider?, spacing?) Horizontal divider
mediaGallery(items) Image gallery (up to 10 images)
buttonRow(buttons[]) Action row of buttons
compose(opts) Build a complete container from options

compose() options

Option Type Description
color number Accent color hex integer
header string Bold header text
body string Body text (shown in a section)
thumbnail string Image URL next to body
extra string[] Additional text blocks
images string[] Media gallery image URLs
imageSeparator boolean Separator before gallery (default: true)
buttons ButtonBuilder[] Buttons row at the bottom
spoiler boolean Mark container as spoiler

Spoiler container

const c = Containers.compose({
  header:  '🔞 Spoiler content',
  body:    'This will be hidden behind a spoiler.',
  spoiler: true,
});

TypeScript

Full types for everything.

import {
  CommandHandler,
  Embeds,
  paginate,
  Containers,
  type HandlerConfig,
  type CommandOptions,
  type EmbedPresetOptions,
  type PaginatorOptions,
  type QuickContainerOptions,
} from 'devcodes-djs';

Full bot example

const { Client, GatewayIntentBits, MessageFlags, ButtonBuilder, ButtonStyle } = require('discord.js');
const { CommandHandler, Embeds, paginate, Containers } = require('devcodes-djs');

const client  = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent] });
const handler = new CommandHandler({ prefix: '!' });

handler
  .command('ping', async (msg) => {
    await msg.reply({ embeds: [Embeds.success('Pong!', `Latency: ${client.ws.ping}ms`)] });
  })
  .command('list', async (msg) => {
    await paginate(msg, [
      Embeds.info('Results — Page 1', 'Item A\nItem B\nItem C'),
      Embeds.info('Results — Page 2', 'Item D\nItem E\nItem F'),
    ]);
  })
  .command('card', async (msg) => {
    const c = Containers.compose({
      color:     0x5865F2,
      header:    '🤖 Bot Info',
      body:      `Servers: **${client.guilds.cache.size}**\nPing: **${client.ws.ping}ms**`,
      thumbnail: client.user.displayAvatarURL(),
    });
    await msg.reply({ components: [c], flags: MessageFlags.IsComponentsV2 });
  });

client.on('messageCreate', (msg) => handler.handle(msg));
client.login(process.env.BOT_TOKEN);

License

MIT © azaresw

Support

Join our Discord for help and updates: discord.gg/ESh2Dp2xX9

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors