DEV Community

Cover image for Advanced Discord.js: Custom embeds using attachments
Sorin Curescu
Sorin Curescu

Posted on • Updated on

Advanced Discord.js: Custom embeds using attachments

Table Of Contents


Looking for a Discord.JS bot? Check out Hans! Now it's open-sourced 🥳!


While dealing with sending messages with our Discord Bot, we can do it in multiple ways such as text, attachments and the most common way, embeds.

One example could be something like the following command that will display the weather based on a location:

MessageEmbed

Sometimes using the MessageEmbed class it is enough, but we may want to represent the data in a different layout/design:

Custom Image Embed - Discord Js

I was seeing several others bots making use of custom ways to show things like stats, users profiles, etc., but I wasn't sure how that can be done and I couldn't find any examples. Some ideas came to my mind and it was time to try things out :P

The following examples are built on top of Discord.js, a Node.JS wrapper to interact with the Discord API. For more information related to Discord.js and how you can start building your own BOT, you can follow along with this amazing series created by Daniel Shiffman.

Using SVG

When I first started looking into this, it came to my mind that having an SVG-to-PNG library could work pretty well since we can have the SVG in a template string and replace the placeholders with our data (user input, API calls, etc...) and then make use of MessageAttachment to attach the output.

For the following SVG image,

svg_template

I was able to find a library that would take an SVG as a string value and return a buffer

npm install svg-png-converter

and then we could have a function to handle all the functionality, in this case we'll have it in its own file and we call it svgToPng.js (Discord only allows attachments of images as JPEG/PNG formats):

// svgToPng.js
const { MessageAttachment } = require('discord.js')
const { svg2png } = require('svg-png-converter')


module.exports = async (msg, name) => {

  const outputBuffer = await svg2png({
    input: `<svg xmlns="http://www.w3.org/2000/svg" width="350" height="136" viewBox="0 0 350 136">
  <g id="template" transform="translate(-208 -209)">
    <rect id="background" width="350" height="136" transform="translate(208 209)" fill="#232323"/>
    <text id="_usr_" data-name="${name}" transform="translate(326 286)" fill="#fff" font-size="20" font-family="SegoeUI, Segoe UI"><tspan x="0" y="0">Hello ${name}</tspan></text>
    <path id="icon" d="M7.5-16.68,15-13.32v5a10.351,10.351,0,0,1-2.148,6.348A9.33,9.33,0,0,1,7.5,1.68,9.33,9.33,0,0,1,2.148-1.973,10.351,10.351,0,0,1,0-8.32v-5Zm1.758,4A2.435,2.435,0,0,0,7.5-13.4a2.435,2.435,0,0,0-1.758.723A2.361,2.361,0,0,0,5-10.918a2.425,2.425,0,0,0,.742,1.777A2.4,2.4,0,0,0,7.5-8.4a2.4,2.4,0,0,0,1.758-.742A2.425,2.425,0,0,0,10-10.918,2.361,2.361,0,0,0,9.258-12.676ZM7.5-6.836a8.754,8.754,0,0,0-2.031.273,6.19,6.19,0,0,0-2.051.9A1.74,1.74,0,0,0,2.5-4.258,6.007,6.007,0,0,0,4.707-2.383,5.947,5.947,0,0,0,7.5-1.6a5.947,5.947,0,0,0,2.793-.781A6.007,6.007,0,0,0,12.5-4.258a1.486,1.486,0,0,0-.547-1.094,4.2,4.2,0,0,0-1.348-.82A10.513,10.513,0,0,0,8.984-6.66,7.147,7.147,0,0,0,7.5-6.836Z" transform="translate(302 286)" fill="#fff"/>
  </g>
</svg>
`,
    encoding: 'buffer',
    format: 'png',
    quality: 1
  })
// for more configuration options refer to the library

  return msg.channel.send(`This is a test:`, new MessageAttachment(outputBuffer, '${name}.png'))

 }
}
Enter fullscreen mode Exit fullscreen mode

The result:

SVG Example - GIF

This was working great with small illustrations where there are just some small changes in the text. For other things, it was not good enough (when you want to dynamically show an image based on some value, such as a list of different icons for each different user role). While this still could be achieved with the SVG, it felt that it's too much work and it wasn't the right fit.

It was time to go back to the drawing board and to think about some other ways to achieve this.

Using HTML & CSS

In the same way, we deal with SVG to PNG conversion, it would be possible to render a webpage with all our data, assets and take a screenshot of it (using puppeteer or some other libraries) and finally attach the screenshot? This would solve many issues and creating a layout in the front-end it's something that I understand and I feel comfortable with, so it sounded like a great idea!

It ends up that you can! After a quick search in the infinitely large npm registry and I was able to find a library that does exactly that (node-html-to-image).

Now the only thing we have to do is to build the layout for our example (HTML & CSS) and to put everything together. We'll make use of the avatar API to dynamically generate an avatar based on the user's name input.

Let start building a quick mockup for our card:

Now that we have our HTML & CSS, we can start working on generating an image from it using the NPM package:

npm install node-html-to-image

and in our htmlToPng.js file we have:

//  htmlToPng.js
const { MessageAttachment } = require('discord.js')
const nodeHtmlToImage = require('node-html-to-image')

module.exports = async (msg, name) => {

  const _htmlTemplate = `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <style>
      body {
        font-family: "Poppins", Arial, Helvetica, sans-serif;
        background: rgb(22, 22, 22);
        color: #fff;
        max-width: 300px;
      }

      .app {
        max-width: 300px;
        padding: 20px;
        display: flex;
        flex-direction: row;
        border-top: 3px solid rgb(16, 180, 209);
        background: rgb(31, 31, 31);
        align-items: center;
      }

      img {
        width: 50px;
        height: 50px;
        margin-right: 20px;
        border-radius: 50%;
        border: 1px solid #fff;
        padding: 5px;
      }
    </style>
  </head>
  <body>
    <div class="app">
      <img src="https://avatars.dicebear.com/4.5/api/avataaars/${name}.svg" />

      <h4>Welcome ${name}</h4>
    </div>
  </body>
</html>
`

  const images = await nodeHtmlToImage({
    html: _htmlTemplate,
    quality: 100,
    type: 'jpeg',
    puppeteerArgs: {
      args: ['--no-sandbox'],
    },
    encoding: 'buffer',
  })
// for more configuration options refer to the library

  return msg.channel
    .send(new MessageAttachment(images, `${name}.jpeg`))
}
Enter fullscreen mode Exit fullscreen mode

The result:

HTML Example - GIF

Conclusion

Now we have two new ways to generate more complex and good looking cards for your Discord users to consume. One good example of this would be to display some game stats for example. My brother and his friends play World of Warcraft, so I took a little time to create a command, that will display the main stats for their characters using the HTML & CSS technique, here is the result of it:

WOW Stats - Discord.js

I would suggest using the SVG method while dealing with a static design/elements but only some text changes and use the HTML one while dealing with dynamic lists or images.

In the previous example, we might be able to archive the same result using an SVG but I find it hard since each element (the item icon and his value) are coming from an API in a different format (JPG/PNG for the images). Appending the item to a list it's way easier than applying a block of code to the SVG bases on some condition. Also styling things like images wrappers (round images with round borders) it's way easier in HTML & CSS than with SVG using things like masks.

I hope that this guide will open new ways for your creativity, which I would love to see them.

You can find the BOT code with all examples at https://github.com/en3sis/discord-guides

Any feedback, questions or suggestions are welcome!
Thanks for reading! ~ https://twitter.com/en3sis

Oldest comments (5)

Collapse
 
kbhutani0001 profile image
Kartikay Bhutani

Very Informative!👏

Collapse
 
simonholdorf profile image
Simon Holdorf

Great post, thank you! I'm a huge fan of discord bots, we recently started explaining popular ones like carl bot, groovy bot, or rhythm bot but I also like the lesser-known ones :)

Collapse
 
en3sis profile image
Sorin Curescu

Thank you, Simon!

Rhythm bot is a must-have in any server :P
The first bot I used it was Nadeko. It has a lot of tools for admins/mods and some other commands just for fun :P

Collapse
 
yodalightsabr profile image
YodaLightsabr

Really cool! node-canvas came to my mind first, but I never thought of using SVG or HTML. I'll use HTML in my next bot to get a nice polished effect that isn't the boring embed.

Collapse
 
en3sis profile image
Sorin Curescu

Glad to hear that it helped! =)