You need to enable JavaScript to run this app. 如何在discord.js自定义机器人中实现MEE6风格的动态自定义图形
优惠活动
大模型
产品
解决方案
定价
更多

如何在discord.js自定义机器人中实现MEE6风格的动态自定义图形

Discord.js 自定义动态图形卡片实现方案

核心可行性说明

这类动态生成的图形内容完全可以落地,实现逻辑本质是在服务端通过画布能力逐像素绘制完目标图片后,以消息附件的形式发送到频道,刚好可以补足Discord原生embed的短板:embed本身不支持自定义字体、自定义用户图标裁切、精细化渐变/阴影特效,完全适配类MEE6等级条的功能需求。

必需引入的依赖包

  • @napi-rs/canvas:Node端跨平台兼容的画布实现,替代老旧的canvas包,安装踩坑率低,支持本地字体加载、图片渲染、形状绘制、像素操作全流程能力,是整个图形生成的核心依赖
  • discord.js:利用其内置的AttachmentBuilder能力,把绘制完成的图片Buffer转成可发送的消息附件,现有成熟的命令处理逻辑可以直接对接这个能力,不需要额外调整消息发送的基础逻辑
  • 可选依赖:如果需要做动态GIF效果的图形,额外引入gifencoder即可把多帧绘制的画布内容编码成可发送的动图,不需要引入Playwright这类重型无头浏览器依赖

对接现有命令流的实现框架(module.exports 结构)

const { AttachmentBuilder } = require('discord.js');
const { createCanvas, GlobalFonts } = require('@napi-rs/canvas');

// 启动时预先注册本地自定义字体,解决embed不支持自定义字体的限制
GlobalFonts.registerFromPath('./assets/fonts/自定义粗体字体.ttf', 'CardCustomFont');

module.exports = {
  /**
   * 核心卡片绘制方法
   * @param {import('discord.js').User} targetUser 动态获取的目标用户实例
   * @param {number} userLevel 用户当前等级
   * @param {number} currentExp 用户当前累计经验
   * @param {number} expRequired 升级到下一级所需经验
   * @returns {Promise<AttachmentBuilder>} 可直接发送的图片附件
   */
  async generateCard(targetUser, userLevel, currentExp, expRequired) {
    // 初始化常用尺寸的画布(800*240是目前等级卡的通用适配尺寸)
    const canvas = createCanvas(800, 240);
    const ctx = canvas.getContext('2d');

    // 1. 绘制渐变背景层
    const bgGradient = ctx.createLinearGradient(0, 0, 800, 240);
    bgGradient.addColorStop(0, '#2f3136');
    bgGradient.addColorStop(1, '#202225');
    ctx.fillStyle = bgGradient;
    ctx.fillRect(0, 0, 800, 240);

    // 2. 绘制圆形裁切的用户动态头像(embed无法实现的自定义图标能力)
    const avatarUrl = targetUser.displayAvatarURL({ extension: 'png', size: 128 });
    const avatarBuffer = await fetch(avatarUrl).then(res => res.arrayBuffer());
    const avatarImg = await ctx.loadImage(avatarBuffer);
    ctx.beginPath();
    ctx.arc(120, 120, 80, 0, Math.PI * 2);
    ctx.fillStyle = '#5865f2';
    ctx.fill();
    ctx.clip();
    // 按圆形裁切范围绘制头像
    ctx.drawImage(avatarImg, 40, 40, 160, 160);

    // 3. 绘制动态文本内容(使用自定义字体)
    ctx.font = '32px CardCustomFont';
    ctx.fillStyle = '#ffffff';
    ctx.fillText(targetUser.globalName ?? targetUser.username, 230, 100);
    ctx.font = '20px CardCustomFont';
    ctx.fillStyle = '#b9bbbe';
    ctx.fillText(`等级 ${userLevel}`, 230, 125);

    // 4. 绘制动态经验进度条
    const progressRatio = Math.min(currentExp / expRequired, 1);
    ctx.fillStyle = '#36393f';
    ctx.fillRect(230, 145, 520, 18);
    ctx.fillStyle = '#5865f2';
    ctx.fillRect(230, 145, 520 * progressRatio, 18);
    ctx.fillStyle = '#b9bbbe';
    ctx.fillText(`${currentExp} / ${expRequired}`, 230, 190);

    // 把画布内容转成可发送的PNG Buffer
    const outputBuffer = canvas.toBuffer('image/png');
    return new AttachmentBuilder(outputBuffer, { name: 'level-card.png' });
  },

  // 命令触发后的执行逻辑,直接对接现有命令处理流即可
  async execute(interaction) {
    const targetUser = interaction.options.getUser('目标用户') ?? interaction.user;
    // 替换为自有存储的用户等级、经验值查询逻辑
    const userStats = { level: 12, currentExp: 450, expToNext: 800 };
    const cardAttachment = await this.generateCard(
      targetUser,
      userStats.level,
      userStats.currentExp,
      userStats.expToNext
    );
    // 直接回复带附件的消息,不需要依赖embed承载图片
    await interaction.reply({ files: [cardAttachment] });
  }
}

实现注意事项

  • 性能优化:可以把固定使用的装饰素材、字体、背景图在服务启动时预加载到内存,避免每次绘制都重新读取磁盘文件;对高频请求的卡片可以做5-10秒的短时间缓存,降低重复绘制的CPU消耗
  • 兼容性处理:拉取用户头像时要做无头像的兜底逻辑,加载本地字体时要做路径校验,避免生产环境出现绘制失败的问题
  • 特效扩展:如果要实现辉光、投影、渐变描边这类酷炫效果,直接在canvas的上下文设置对应参数即可,不需要引入重型的前端渲染框架

内容的提问来源于stack exchange,提问作者Tyrcnex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.03 03:27:05