Just after New Year's 2024, I suddenly had a simple idea to create a progress bar for every day until the 365th day of this year.
Currently, I am active on Discord in a server with my friends, and I want to try making a daily "good morning" message to greet all of my friends on Discord before we all start our day.
Here are the things:
- I want to create a bot that will send a message every morning.
- I don't want to pay any extra fees for the resources I will use.
I came up with using CircleCI for this, because CircleCI offers free tier automation.
Draw an image using nodejs canvas
To draw our custom image, we need to use
const Canvas = require('@napi-rs/canvas');
.
First, let's try to draw the progress bar, as this is the most challenging part. The rest of the content is just text, and we need to adjust the coordinates.
// Coordinates and dimensions for the rectangle
const rectX = 50;
const rectY = canvas.height / 2 - 25;
const rectWidth = canvas.width - 100;
const rectHeight = 80;
// Draw a white rectangle
context.fillStyle = "white";
context.fillRect(rectX, rectY, rectWidth, rectHeight);
// Calculate the percentage of the year that has passed
const now = new Date();
const start = new Date(now.getFullYear(), 0, 0);
const diff = now - start;
const oneDay = 1000 * 60 * 60 * 24;
const day = Math.floor(diff / oneDay);
const yearLength = (new Date(now.getFullYear(), 11, 31) - start) / oneDay + 1; // Account for leap year
const progress = (day / yearLength) * rectWidth;
// Draw the green background color inside the rect, filling the calculated percentage of the width
context.fillStyle = "green";
context.fillRect(rectX, rectY, progress, rectHeight);
// Set up stroke style
context.strokeStyle = "black"; // Stroke color
context.lineWidth = 8; // Stroke width
// Draw the stroke on top of the filled rectangles
context.strokeRect(rectX, rectY, rectWidth, rectHeight);
And the result will look like this:
After that, it's just a matter of adding more text and positioning it in the right place.
// Set up the font style for the text
context.font = "bold 48px NotoSansJP";
context.fillStyle = "white";
context.textAlign = "center"; // This will align the text centrally
context.textBaseline = "middle"; // This will align the text in the middle of the baseline
const ohayouText = `おはようございました`;
const ohayouTextX = canvas.width / 2;
const ohayouTextY = rectY - 52;
// Draw the Japanese text
context.fillText(ohayouText, ohayouTextX, ohayouTextY);
// Set up the font style for the text
context.font = "bold 40px NotoSansJP";
context.fillStyle = "white";
context.textAlign = "center"; // This will align the text centrally
context.textBaseline = "middle"; // This will align the text in the middle of the baseline
// Calculate the position for the percentage text
const text = `${((day / yearLength) * 100).toFixed(2)}% HAS PASSED FOR THIS YEAR`;
const textX = canvas.width / 2; // This will center the text in the x-axis
const textY = rectY + rectHeight + 48; // This will position the text below the rectangle
// Draw the percentage text
context.fillText(text, textX, textY);
// Set up the font style for the Japanese text
context.font = "bold 28px NotoSansJP";
context.textAlign = "center"; // This will align the text centrally
context.textBaseline = "middle"; // This will align the text in the middle of the baseline
const keepItUpText = `今日も頑張りましょう。`;
const keepItUpTextX = canvas.width / 2;
const keepItUpTextY = textY + 48;
// Draw the Japanese text
context.fillText(keepItUpText, keepItUpTextX, keepItUpTextY);
And it will look like this:
Okay, now that we have finished drawing our custom image, it's time to send our image to the Discord channel using discord.js
.
const {
Client,
Events,
GatewayIntentBits,
AttachmentBuilder,
} = require("discord.js");
const Canvas = require("@napi-rs/canvas");
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
const TARGETED_CHANNEL = process.env.DISCORD_BOT_TARGETED_CHANNEL;
const token = process.env.DISCORD_TOKEN;
client.once(Events.ClientReady, async (readyClient) => {
const canvas = Canvas.createCanvas(1000, 500);
const context = canvas.getContext("2d");
// ...Our previous canvas drawing code.
try {
// Convert canvas to buffer
const buffer = canvas.toBuffer("image/png");
// Create an attachment and send it
const attachment = new AttachmentBuilder(buffer, { name: "progress.png" });
readyClient.channels.cache
.get(TARGETED_CHANNEL)
.send({ files: [attachment] });
} catch (error) {
console.error("Error creating buffer:", error);
readyClient.channels.cache
.get(TARGETED_CHANNEL)
.send("An error occurred while creating the image.");
}
client.destroy();
});
client.login(token);
CircleCI Configuration
Now, we are going to run our code inside the Node.js cimg container runtime.
version: 2.1
jobs:
main_script:
docker:
- image: 'cimg/node:21.5.0'
steps:
- checkout
- run:
name: Install System Fonts
command: |
sudo apt-get update
sudo apt-get install -y fontconfig
sudo apt-get install -y libfontconfig1
- run:
name: Install Dependencies
command: npm install
- run:
name: Run Node.js Script
command: node main.js
workflows:
version: 2
build:
jobs:
- main_script
We need additional installations so our font can run inside the Docker container, as we are currently running our canvas inside a Docker container.
run:
name: Install System Fonts
command: |
sudo apt-get update
sudo apt-get install -y fontconfig
sudo apt-get install -y libfontconfig1
Now, you just need to set up the scheduler using the CircleCI console.
Full Source Code
https://github.com/TitusEfferian/discord-cron-daily-ci
Top comments (0)