javascriptnode.jsdiscord.jsimage-manipulationjimp

discord.js argument separator issue


I have been trying to get my args to work and they do but I am not sure how to make a separator for it so I don't have to use the command twice. Here's my code.

const discord = require('discord.js');
const Jimp = require('jimp');
const fs = require('fs')

module.exports = {
  name: "cat",
  aliases: [],
  run: async(client, message, args) => {
    //const top = args[0]
    //const bottom = args[1]
    let [top, bottom] = args

    Jimp.read("assets/cat.jpg").then(function(image) {
      Jimp.loadFont(Jimp.FONT_SANS_32_WHITE).then(function(font) {
        image.print(font, 60, 22, top, 5);
        image.print(font, 60, 350, bottom);
        image.write('output-image.jpg');
      });
    });
    await message.channel.send({
      files: [
        "output-image.jpg"
      ]
    });
    fs.unlinkSync('output-image.jpg');
  }
};

And here's the output I get.

output image here


Solution

  • The problem is that you want to send the file outside of the promise's .then() method. When you call await message.channel.send() the file is just being read by jimp and the output-image.jpg is nowhere near ready.

    Use async-await

    As your run() method is already an async function, you could use async-await to make your code more readable. That way you can get rid of the callbacks and make it easier to follow what's happening and in what order.

    Buffer instead of files

    As Skulaurun Mrusal mentioned in their comment, you don't even need to save the file, you could just send a buffer. Jimp has a .getBuffer() method you can use to generate a binary buffer of an image.

    You can send this buffer as an attachment. Discord.js has a MessageAttachment object that accepts a buffer as the first argument. You can send this attachment by passing it to message.channel.send(new MessageAttachment(buffer)).

    Accept text longer than a single word

    Currently, as your args is an array of space-separated strings, you can only add one word as the text on the top and another one as the bottom text. It's probably easier to accept two longer text that's inside double quotes (").

    So the command should be used like this:

    !cat "This is the text on the top" "And we are down here"
    

    You can use a "simple" regex that matches strings that are inside double quotes. In the next example, you can see that matches is an array of the two substring inside the double quotes. They include the quotes too, so you'll need to get rid of that with a String#replace() method.

    let content = '!cat "This is the text on the top" "And we are down here"'
    let matches = content.match(/"(.*?)"/g)
    
    console.log({ matches })

    Text alignment

    At the moment you're using "magic numbers" (60, 22, 350, 5, etc.) to position the text. It won't work and (depending on the text submitted through the command) it will be all over the image, and chances are it will never be centred.

    Jimp has different align modes you can take advantage of. To align the text to the centre horizontally, you can use HORIZONTAL_ALIGN_CENTER, to align to the top, you can use VERTICAL_ALIGN_TOP, etc. Make sure to set the max width and max height to the image's width and height. You can get it using image.bitmap.width and image.bitmap.height

    Working code

    const { MessageAttachment } = require('discord.js');
    const Jimp = require('jimp');
    
    module.exports = {
      name: 'cat',
      aliases: [],
      run: async (client, message, args) => {
        let matches = message.content.match(/"(.*?)"/g);
        let [topText, bottomText] = matches
          ? matches.map((m) => m.replace(/"/g, '').toUpperCase())
          : args;
    
        if (!topText)
          return message.channel.send(`Don't forget to add the text you want to print`);
    
        try {
          let image = await Jimp.read('cat.jpg');
          let font = await Jimp.loadFont(Jimp.FONT_SANS_64_WHITE);
          let padding = 40;
          let pos = {
            x: 0,
            yTop: padding,
            yBottom: padding * -1,
            maxWidth: image.bitmap.width,
            maxHeight: image.bitmap.height,
          };
    
          image.print(
            font,
            pos.x,
            pos.yTop,
            {
              text: topText,
              alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
              alignmentY: Jimp.VERTICAL_ALIGN_TOP,
            },
            pos.maxWidth,
            pos.maxHeight,
          );
    
          if (bottomText)
            image.print(
              font,
              pos.x,
              pos.yBottom,
              {
                text: bottomText,
                alignmentX: Jimp.HORIZONTAL_ALIGN_CENTER,
                alignmentY: Jimp.VERTICAL_ALIGN_BOTTOM,
              },
              pos.maxWidth,
              pos.maxHeight,
            );
    
          let buffer = await image.getBufferAsync(Jimp.MIME_JPEG);
    
          message.channel.send(new MessageAttachment(buffer));
        } catch (err) {
          console.log(err);
          message.channel.send('Oops, there was an error. Maybe try again later?!');
        }
      },
    };
    

    Result

    enter image description here

    enter image description here

    The image of the cat is available on Unsplash.