Goal: Build a system that applies a watermark to uploaded images using Sharp.
In this lesson you will build a professional image watermark system for your Discord bot.
Users upload an image using /watermark, and the bot automatically applies a watermark overlay.
sharpUsers upload an image directly in Discord.
The bot downloads the image from Discord's CDN.
Processes and edits images efficiently.
The watermark is resized to match the image size.
Overlays the watermark onto the image.
npm install sharp
Create:
images/watermark.png
const { SlashCommandBuilder, AttachmentBuilder } = require('discord.js');
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const https = require('https');
module.exports = {
data: new SlashCommandBuilder()
.setName('watermark')
.setDescription('Add watermark to an image.')
.addAttachmentOption(option =>
option.setName('image')
.setDescription('The image to watermark')
.setRequired(true)
),
run: async ({ interaction }) => {
const attachment = interaction.options.getAttachment('image');
if (!attachment || !attachment.contentType.startsWith('image/')) {
return interaction.reply({ content: 'Please provide a valid image.', ephemeral: true });
}
await interaction.deferReply();
try {
const imageBuffer = await new Promise((resolve, reject) => {
https.get(attachment.url, res => {
const chunks = [];
res.on('data', chunk => chunks.push(chunk));
res.on('end', () => resolve(Buffer.concat(chunks)));
res.on('error', reject);
});
});
const watermarkPath = path.join(__dirname, 'images', 'watermark.png');
const watermarkBuffer = fs.readFileSync(watermarkPath);
const image = sharp(imageBuffer);
const metadata = await image.metadata();
const watermarkResized = await sharp(watermarkBuffer)
.resize(metadata.width, metadata.height)
.png()
.toBuffer();
const result = await sharp(imageBuffer)
.composite([{ input: watermarkResized }])
.png()
.toBuffer();
const attachmentOut = new AttachmentBuilder(result, { name: 'watermarked.png' });
await interaction.editReply({ files: [attachmentOut] });
} catch (err) {
console.error(err);
interaction.editReply({ content: 'Error processing image.' });
}
}
};
node index.js