Currency / Reward System

This guide will show you how to create a basic currency/rewards system for your Discord bot using Discord.js and some relatively basic Node.js.

A currency/rewards system is a great way to keep your users engaged and active on your Discord server. It can be used to reward users for being active, participating in events, or just for fun. In this guide, I'll show you how to create a simple currency/rewards system for your Discord bot using Discord.js and some basic Node.js.

For example, let's create a simple currency/reward system that gives points to users who are currently in a voice channel.

See which users are currently active:

const { Events } = require("discord.js");
const UserUtil = require("user.util.js");


class VoiceStateUpdateListener {
    /**
     * **Initialize the `Events.VoiceStateUpdate` listener.**  
     * @returns {void} Void.  
     */
    static init() {
        client.on(Events.VoiceStateUpdate, (oldState, newState) => {
            try {
                if (newState.member.user.bot) { return; }
                if (!oldState.channel && newState.channel) {
                    UserUtil.addUser(newState.id, newState.member);
                } else if (!newState.channel) {
                    UserUtil.removeUser(oldState.id);
                }
            } catch (err) {
                console.error(err);
            }
        });
    }
}

module.exports = VoiceStateUpdateListener;

In this example, we have created a listener called VoiceStateUpdateListener. This listener listens for the VoiceStateUpdate event, which is triggered whenever a user's voice state changes (e.g., they join or leave a voice channel). When a user joins a voice channel, we call the addUser function from our UserUtil utility to add the user to our list of active users. When a user leaves a voice channel, we call the removeUser function to remove the user from our list of active users.

Now that our listener is set up, we need to add points to users who are currently in a voice channel. We can do this with an asynchronous interval that runs every X seconds. If you don't need it to be asynchronous, you can simply create an interval.

Start the interval:

// Async Interval
const { setInterval } = require("node:timers/promises");
const ActiveVoiceTask = require("activevoice.task.js");


let proccessing = false;

for await (const _ of setInterval(60_000)) {
    if (proccessing) {
        console.warn("ActiveVoiceTask.updateCurrency() did not completed");
        continue;
    }

    proccessing = true;

    ActiveVoiceTask.updateCurrency()
        .then(() => {
            proccessing = false;
        })
        .catch((err) => {
            console.error(err);
            proccessing = false;
        });


// Sync interval
const updateInterval = setInterval(() => {
    try {
        ActiveVoiceTask.updateCurrency()
    } catch(err) {
        console.error(err);
    }
}, 60_000);
}

In this example, we use the setInterval function to run the updateUserPoints function every 60 seconds. We use a boolean variable called proccessing to prevent the function from running multiple times concurrently. If the function is already running, we log a warning message and skip the current iteration.

The updateUserPoints function is responsible for adding points to the users who are currently in a voice channel. This function can be as simple or as complex as you want, depending on your requirements. If it is a simple synchronous function, you can call it directly from the interval. If it is an asynchronous function, you can use the for await loop to wait for the function to complete before running the next iteration.

Depending on your setup, you need to create users who do not already exist and then add the currency.

Now, all you have to do is update the user's currency in your database. For me, that is done in SQLite. You can do it like this.

Add points to active users:

const UserUtil = require("user.util.js");
const DatabaseRepository = require("database.repository.js");
const RewardTaskRepository = require("rewardtask.repository.js");


class ActiveVoiceTask {
    /**
     * **Update the currency for all active user that are currently in a voice channel.**  
     * @returns {void} Void.  
     */
    static updateCurrency() {
        const activeUsers = Array.from(UserUtil.activeUsers.keys());

        if (activeUsers.length) {
            const existingUsers = RewardTaskRepository.getExistingUsers(activeUsers);
            const existingUserIds = existingUsers.map((user) => user.id);
            const nonExistingUsers = activeUsers.filter((userId) => !existingUserIds.includes(userId));

            if (existingUserIds.length) {
                RewardTaskRepository.updateVoiceCallRewards(existingUserIds);
            }

            for (const userId of nonExistingUsers) {
                new DatabaseRepository(
                    userId,
                    UserUtil.activeUsers.get(userId).member.user.username
                );
            }
        }
    }
}

module.exports = ActiveVoiceTask;

In my case, the database logic, i.e., the SQL statements, happen in the RewardTaskRepository. You can implement your own logic; it's just there to handle long-term currency storage and management.

You can now create your own currency/rewards system for your Discord bot using Discord.js and Node.js. You can customize the system and add commands to buy items, check your balance, and more.