Morning Daily Routine Bot for Your Discord Server
Tags: javascript, discord, circleci, tutorial
Published: 2024-01-20 13:47:30

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.

```javascript
// 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:

![Canvas Image Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17esy3dbryj26htmnk9g.png)

After that, it's just a matter of adding more text and positioning it in the right place.

```javascript
// 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:

![Canvas Image Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7xz9y146qbx7zkhtpg9e.png)

Okay, now that we have finished drawing our custom image, it's time to send our image to the Discord channel using `discord.js`.

```javascript
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.

```yaml
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.

```yaml
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.


![Circle CI Dashboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2cmgp1zldftao002qbok.png)


**Full Source Code**
https://github.com/TitusEfferian/discord-cron-daily-ci