7dtd_ChatBridge

  • community
  • integration
  • by limon
  • Takaro v0.0.24
  • 7 Days to Die
Version
View export JSON

Limon_chatbridge: Discord Chat Integration for Your Game Server

This module bridges your game server's chat and a Discord channel, enabling two-way communication with powerful, customizable filtering options.

Key Features:

game chat

  • Two-Way Chat Relay: Forward messages between your game and Discord in real-time. Improve player interaction and build a stronger community.
  • Customizable Channel Filtering: Control exactly which chat channels get relayed to Discord:
    • Global chat for server-wide messages
    • Optional Party/Team chat integration
    • Optional Friends chat integration
  • Player Connection/Disconnection Notifications: Get automated Discord messages when players join or leave your server.

player connection

  • Advanced Filtering Options:
    • Command Filtering: Prevent in-game command messages (e.g., /ban) from cluttering your Discord.
    • System Message Filtering: Keep your Discord clean by blocking system (non-player) messages.

discord chat

Channel Prefixing:
Messages from different chat channels are clearly labeled in Discord:

  • Global chat appears normally
  • Party chat is prefixed with [Party]
  • Friends chat is prefixed with [Friends]

Server Status Tracking:

  • Track player connections with links to Steam profiles
  • Monitor server status events
  • Support for 7 Days to Die bloodmoon countdown tracking

server status

Configuration Options:

  • sendPlayerConnected: Enable player connect notifications
  • sendPlayerDisconnected: Enable player disconnect notifications
  • onlyGlobalChat: Restrict to global chat only (can be fine-tuned below)
  • includePartyChat: Include party/team chat in Discord (overrides onlyGlobalChat restriction)
  • includeFriendsChat: Include friends chat in Discord (overrides onlyGlobalChat restriction)
  • filterCommands: Filter out in-game command messages
  • filterSystemMessages: Filter out system messages
  • useMonitoring: Enable a dedicated monitoring channel for all messages
  • showPlayerDetails: Show Steam profile and Takaro profile links in notifications

Perfect for:

  • Communities that want to bridge in-game and Discord chat
  • Servers with active Discord communities
  • Admins who want to monitor server activity from Discord
  • Multi-server networks with centralized Discord channels
  • 7 Days to Die servers wanting to share bloodmoon information

Configuration 10

Settings you fill in when installing the module on a server.

SettingTypeDefaultDescription
sendPlayerConnected Send Player Connected boolean true Send a notification to Discord when a player connects to the server.
sendPlayerDisconnected Send Player Disconnected boolean true Send a notification to Discord when a player disconnects from the server.
includePartyChat Include Party Chat boolean false When enabled (true), party/team chat messages will be sent to Discord.
includeFriendsChat Include Friends Chat boolean false When enabled (true), friends chat messages will be sent to Discord.
filterCommands Filter Commands boolean true When enabled (true), command messages starting with command prefixes will NOT be sent to Discord.
commandPrefixes Command Prefixes array ["/"] List of prefixes that identify commands to filter (e.g., '/', '$', '#'). Only used if Filter Commands is enabled.
filterSystemMessages Filter System Messages boolean false When enabled, messages that don't come from actual players (such as server announcements, automated notifications, death messages, and other game-generated text) will NOT be sent to the main Discord channel. These system messages will still be visible in the monitoring channel if monitoring is enabled.
useMonitoring Enable Monitoring Channel boolean false When enabled (true), filtered messages will be sent to a separate monitoring channel.
monitoringChannelId Monitoring Channel ID string Discord channel ID for monitoring messages. Required if monitoring is enabled.
showPlayerDetails Show Player Details boolean false Show Steam profile and Takaro profile links in join/leave notifications.
Raw config schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": [],
  "additionalProperties": false,
  "properties": {
    "sendPlayerConnected": {
      "title": "Send Player Connected",
      "description": "Send a notification to Discord when a player connects to the server.",
      "default": true,
      "type": "boolean"
    },
    "sendPlayerDisconnected": {
      "title": "Send Player Disconnected",
      "description": "Send a notification to Discord when a player disconnects from the server.",
      "default": true,
      "type": "boolean"
    },
    "includePartyChat": {
      "title": "Include Party Chat",
      "description": "When enabled (true), party/team chat messages will be sent to Discord.",
      "default": false,
      "type": "boolean"
    },
    "includeFriendsChat": {
      "title": "Include Friends Chat",
      "description": "When enabled (true), friends chat messages will be sent to Discord.",
      "default": false,
      "type": "boolean"
    },
    "filterCommands": {
      "title": "Filter Commands",
      "description": "When enabled (true), command messages starting with command prefixes will NOT be sent to Discord.",
      "default": true,
      "type": "boolean"
    },
    "commandPrefixes": {
      "title": "Command Prefixes",
      "description": "List of prefixes that identify commands to filter (e.g., '/', '$', '#'). Only used if Filter Commands is enabled.",
      "type": "array",
      "items": {
        "type": "string"
      },
      "default": [
        "/"
      ]
    },
    "filterSystemMessages": {
      "title": "Filter System Messages",
      "description": "When enabled, messages that don't come from actual players (such as server announcements, automated notifications, death messages, and other game-generated text) will NOT be sent to the main Discord channel. These system messages will still be visible in the monitoring channel if monitoring is enabled.",
      "default": false,
      "type": "boolean"
    },
    "useMonitoring": {
      "title": "Enable Monitoring Channel",
      "description": "When enabled (true), filtered messages will be sent to a separate monitoring channel.",
      "default": false,
      "type": "boolean"
    },
    "monitoringChannelId": {
      "title": "Monitoring Channel ID",
      "description": "Discord channel ID for monitoring messages. Required if monitoring is enabled.",
      "type": "string"
    },
    "showPlayerDetails": {
      "title": "Show Player Details",
      "description": "Show Steam profile and Takaro profile links in join/leave notifications.",
      "default": false,
      "type": "boolean"
    }
  },
  "dependencies": {
    "useMonitoring": {
      "oneOf": [
        {
          "properties": {
            "useMonitoring": {
              "enum": [
                false
              ]
            }
          }
        },
        {
          "properties": {
            "useMonitoring": {
              "enum": [
                true
              ]
            },
            "monitoringChannelId": {
              "type": "string"
            }
          },
          "required": [
            "monitoringChannelId"
          ]
        }
      ]
    },
    "filterCommands": {
      "oneOf": [
        {
          "properties": {
            "filterCommands": {
              "enum": [
                false
              ]
            }
          }
        },
        {
          "properties": {
            "filterCommands": {
              "enum": [
                true
              ]
            },
            "commandPrefixes": {
              "type": "array"
            }
          }
        }
      ]
    }
  }
}
Raw UI schema
{}

Hooks 6

Code that runs in reaction to a game or Takaro event.

  • serverStatusChange

    Hook for server-status-changed events

    Hook source
    import { takaro, data } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, eventData } = data;
        const discordChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;
    
        // Get the server name for a more informative message
        const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);
        const serverName = serverInfo.data.data.name;
    
        // Determine if the server is online or offline from the event data
        const isOnline = eventData.status === 'online';
    
        // Create appropriate emoji and message based on status
        const statusEmoji = isOnline ? '🟢' : '🔴';
        const statusText = isOnline ? 'online' : 'offline';
    
        // Create the message
        const message = `**[${statusEmoji} Server ${statusText}]**: ${serverName} is now ${statusText}`;
    
        // Send the message to Discord
        await takaro.discord.discordControllerSendMessage(discordChannel, {
            message: message
        });
    }
    
    await main();
  • PlayerDisconnected

    Hook for player-disconnected events

    Hook source
    import { takaro, data } from '@takaro/helpers';
    
    async function main() {
        const { player } = data;
        const config = data.module.userConfig;
        const discordChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;
    
        // Check if showPlayerDetails is explicitly set to false
        // If the option doesn't exist, default to showing details for backward compatibility
        const showDetails = config.showPlayerDetails !== false;
    
        let message = `**[👋 Disconnected]**: ${player.name} has left the server`;
    
        // Only add links if showPlayerDetails is true
        if (showDetails) {
            message += `\nSteam: https://steamcommunity.com/profiles/${player.steamId}\n` +
                `Takaro: https://dashboard.takaro.io/player/${player.id}/info`;
        }
    
        await takaro.discord.discordControllerSendMessage(discordChannel, {
            message: message
        });
    }
    
    await main();
  • resetRegions

    Hook for log events

    Hook source
    import { takaro, data } from '@takaro/helpers';
    
    async function main() {
        try {
            // Check for required data
            if (!data || !data.gameServerId) {
                return;
            }
    
            const gameServerId = data.gameServerId;
    
            // Get Discord channel ID
            const discordChannel = data.module?.systemConfig?.hooks?.['DiscordToGame']?.discordChannelId;
    
            if (!discordChannel) {
                return;
            }
    
            const message = data.message || "";
    
            // Get server name
            let serverName = "Server";
            try {
                const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);
                serverName = serverInfo?.data?.data?.name || "Server";
            } catch (error) {
                // Silently handle error
            }
    
            // Extract reset information
            const resetInfo = message.match(/Reset (\d+) chunks in (\d+) regions/);
            const chunks = resetInfo?.[1] || "0";
            const regions = resetInfo?.[2] || "0";
    
            const discordMessage = `**[🔄 Region Reset]**: ${serverName} has completed a region reset. Reset ${chunks} chunks in ${regions} regions.`;
    
            // Send Discord notification
            await takaro.discord.discordControllerSendMessage(discordChannel, {
                message: discordMessage
            });
        } catch (error) {
            // Silently handle any errors
        }
    }
    
    await main();
  • GameToDiscord

    Hook for chat-message events

    Hook source
    import { takaro, data } from '@takaro/helpers';
    
    async function main() {
        const config = data.module.userConfig;
    
        // Get message details
        const sender = data.player ? data.player.name : 'Non-player';
        const message = data.eventData.msg;
        const channel = data.eventData.channel;
    
        // Get command prefixes (default to ['/'] if not defined)
        const commandPrefixes = config.commandPrefixes || ['/'];
    
        // Check if message starts with any of the command prefixes
        const isCommand = commandPrefixes.some(prefix => message.startsWith(prefix));
    
        // Check if the message is from Discord (starts with [D])
        const isDiscordMessage = message.startsWith('[D] ');
    
        // If this is a Discord echo message, skip it completely to prevent loops
        if (isDiscordMessage) {
            return;
        }
    
        // Format message based on channel type
        let formattedMessage;
        if (channel === "global") {
            formattedMessage = `**${sender}**: ${message}`;
        } else if (channel === "team") {
            formattedMessage = `[Party] **${sender}**: ${message}`;
        } else {
            formattedMessage = `[Friends] **${sender}**: ${message}`;
        }
    
        // Always send to monitoring channel if enabled (before any filtering)
        if (config.useMonitoring && config.monitoringChannelId) {
            await takaro.discord.discordControllerSendMessage(config.monitoringChannelId, {
                message: formattedMessage
            });
        }
    
        // FILTER SECTION FOR MAIN CHANNEL
    
        // 1. Filter commands if enabled
        if (isCommand && config.filterCommands) {
            return; // Don't send commands to main channel
        }
    
        // 2. Filter system messages if enabled
        if (sender === 'Non-player' && config.filterSystemMessages) {
            return; // Don't send system messages to main channel
        }
    
        // 3. Filter chat types based on configuration
        // Check party/team chat
        if (channel === 'team' && config.includePartyChat !== true) {
            return; // Skip if party chat not explicitly enabled
        }
    
        // Check friends chat
        if (channel !== 'global' && channel !== 'team' && config.includeFriendsChat !== true) {
            return; // Skip if friends chat not explicitly enabled
        }
    
        // If we reach this point, the message passes all filters - send to main channel
        const mainChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;
        if (mainChannel) {
            await takaro.discord.discordControllerSendMessage(mainChannel, {
                message: formattedMessage
            });
        }
    }
    
    await main();
  • PlayerConnected

    Hook for player-connected events

    Hook source
    import { takaro, data } from '@takaro/helpers';
    
    async function main() {
        const { player } = data;
        const config = data.module.userConfig;
        const discordChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;
    
        // Check if showPlayerDetails exists and is explicitly false
        const showDetails = config.showPlayerDetails !== false;
    
        let message = `**[⚡ Connected]**: ${player.name} has joined the server`;
    
        // Only add links if showPlayerDetails is true
        if (showDetails) {
            message += `\nSteam: https://steamcommunity.com/profiles/${player.steamId}\n` +
                `Takaro: https://dashboard.takaro.io/player/${player.id}/info`;
        }
    
        await takaro.discord.discordControllerSendMessage(discordChannel, {
            message: message
        });
    }
    
    await main();
  • DiscordToGame

    Hook for discord-message events

    Hook source
    import { takaro, data } from '@takaro/helpers';
    import { getServerStatusMessage } from './utils.js';
    
    async function main() {
        try {
            const config = data.module.userConfig;
    
            // Skip messages from bots
            if (data.eventData.author.isBot)
                return;
    
            // Get Discord channel from module system config
            const discordChannelId = data.discordChannelId ||
                data.module.systemConfig.hooks['DiscordToGame']?.discordChannelId;
    
            if (!discordChannelId) {
                console.error("Discord channel ID is missing from both data context and module config");
                return;
            }
    
            // Format the message for monitoring
            const formattedMessage = `[Discord → Game] **${data.eventData.author.displayName}**: ${data.eventData.msg}`;
    
            // If monitoring is enabled, send to monitoring channel
            if (config.useMonitoring && config.monitoringChannelId) {
                await takaro.discord.discordControllerSendMessage(config.monitoringChannelId, {
                    message: formattedMessage
                });
            }
    
            // Check if the message is the serverstatus command
            if (data.eventData.msg.toLowerCase().endsWith('serverstatus')) {
                // Use a fixed server start time for demonstration
                const serverStartTime = new Date("2025-04-12T11:54:01.547Z");
    
                // Get server status message
                const message = await getServerStatusMessage(data.gameServerId, serverStartTime);
    
                // Send to Discord
                await takaro.discord.discordControllerSendMessage(discordChannelId, {
                    message: message
                });
    
                // Return early to avoid forwarding the command to the game
                return;
            }
    
            // Forward regular messages to the game
            await takaro.gameserver.gameServerControllerSendMessage(data.gameServerId, {
                message: `[D] ${data.eventData.author.displayName}:  ${data.eventData.msg}`,
            });
        }
        catch (error) {
            console.error(error);
    
            // Try to get Discord channel ID if we haven't already
            const discordChannelId = data.discordChannelId ||
                data.module.systemConfig.hooks['DiscordToGame']?.discordChannelId;
    
            if (discordChannelId) {
                await takaro.discord.discordControllerSendMessage(discordChannelId, {
                    message: 'Failed to forward your message to the game. Please try again later.',
                });
            } else {
                console.error("Could not send error message because Discord channel ID is missing");
            }
        }
    }
    
    await main();

Cron jobs 1

Work the module runs on a schedule.

  • serverStatus

    Cron job source
    import { takaro, data } from '@takaro/helpers';
    import { getServerStatusMessage } from './utils.js';
    
    async function main() {
        try {
            const { gameServerId, module: mod } = data;
            const discordChannel = mod.systemConfig.hooks['DiscordToGame']?.discordChannelId;
    
            if (!discordChannel) {
                console.error("Discord channel ID not configured");
                return { success: false, reason: "Discord channel ID not configured" };
            }
    
            // Use a fixed server start time for demonstration
            const serverStartTime = new Date("2025-04-12T11:54:01.547Z");
    
            // Get the complete server status message
            const message = await getServerStatusMessage(gameServerId, serverStartTime);
    
            // Send the message to Discord
            await takaro.discord.discordControllerSendMessage(discordChannel, {
                message: message
            });
    
            // Return success result
            return { success: true };
        } catch (error) {
            console.error("Error in serverStatus cronjob:", error);
            return {
                success: false,
                reason: error.message || "Unknown error in serverStatus cronjob"
            };
        }
    }
    
    await main();

Functions 1

Shared helpers the module's commands, hooks and cron jobs import.

  • utils

    Function source
    import { takaro, data } from '@takaro/helpers';
    
    /**
     * Gets the server start time from the latest server-status-changed event with status "online"
     * @param {string} gameServerId - The ID of the game server
     * @returns {Promise<Date>} The server start time
     */
    export async function getServerStartTime(gameServerId) {
        try {
            // First try to get server-status-changed events
            const statusEvents = await takaro.event.eventControllerSearch({
                filters: {
                    eventName: ['server-status-changed'],
                    gameserverId: [gameServerId]
                }
            });
    
            // Filter for online events and find the most recent one
            if (statusEvents.data.data.length > 0) {
                const onlineEvents = statusEvents.data.data.filter(event =>
                    event.meta && event.meta.status === 'online'
                );
    
                if (onlineEvents.length > 0) {
                    // Sort by timestamp (newest first)
                    onlineEvents.sort((a, b) =>
                        new Date(b.meta.timestamp).getTime() - new Date(a.meta.timestamp).getTime()
                    );
    
                    return new Date(onlineEvents[0].meta.timestamp);
                }
            }
    
            // If no server-status-changed events found, try gameserver-created as fallback
            const creationEvents = await takaro.event.eventControllerSearch({
                filters: {
                    eventName: ['gameserver-created'],
                    gameserverId: [gameServerId]
                }
            });
    
            if (creationEvents.data.data.length > 0) {
                // Just use the first one since we're not sorting
                return new Date(creationEvents.data.data[0].meta.timestamp);
            }
    
            // Final fallback - use server creation time from the gameServer object
            const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);
            if (serverInfo && serverInfo.data && serverInfo.data.data) {
                return new Date(serverInfo.data.data.createdAt);
            }
    
            // If all else fails, use 1 day ago as fallback
            const fallbackTime = new Date();
            fallbackTime.setDate(fallbackTime.getDate() - 1);
            return fallbackTime;
        } catch (error) {
            console.error("Error getting server start time:", error);
            // Return a fallback value if there's an error
            const fallbackTime = new Date();
            fallbackTime.setDate(fallbackTime.getDate() - 1);
            return fallbackTime;
        }
    }
    
    /**
     * Gets the server creation time from gameserver-created event
     * @param {string} gameServerId - The ID of the game server
     * @returns {Promise<Date>} The server creation time
     */
    export async function getServerCreationTime(gameServerId) {
        try {
            // Get gameserver-created events
            const creationEvents = await takaro.event.eventControllerSearch({
                filters: {
                    eventName: ['gameserver-created'],
                    gameserverId: [gameServerId]
                }
            });
    
            if (creationEvents.data.data.length > 0) {
                // Sort by timestamp (oldest first) to get the original creation time
                creationEvents.data.data.sort((a, b) =>
                    new Date(a.meta.timestamp).getTime() - new Date(b.meta.timestamp).getTime()
                );
    
                return new Date(creationEvents.data.data[0].meta.timestamp);
            }
    
            // Fallback to the server's creation time in the database
            const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);
            if (serverInfo && serverInfo.data && serverInfo.data.data) {
                return new Date(serverInfo.data.data.createdAt);
            }
    
            // If all else fails, use today as fallback
            return new Date();
        } catch (error) {
            console.error("Error getting server creation time:", error);
            return new Date(); // Return current time as fallback
        }
    }
    
    /**
     * Calculates server uptime from a given start time
     * @param {Date} serverStartTime - The time when the server started
     * @returns {string} Formatted uptime string (e.g. "2d 5h 30m")
     */
    export function calculateUptime(serverStartTime) {
        try {
            const currentTime = new Date();
            const uptimeMs = currentTime.getTime() - serverStartTime.getTime();
            const uptimeSeconds = Math.floor(uptimeMs / 1000);
            const days = Math.floor(uptimeSeconds / 86400);
            const hours = Math.floor((uptimeSeconds % 86400) / 3600);
            const minutes = Math.floor((uptimeSeconds % 3600) / 60);
    
            let serverUptime = "";
            if (days > 0) serverUptime += `${days}d `;
            if (hours > 0 || days > 0) serverUptime += `${hours}h `;
            serverUptime += `${minutes}m`;
    
            return serverUptime;
        } catch (error) {
            console.error("Error calculating server uptime:", error);
            return "Unknown";
        }
    }
    
    /**
     * Gets a list of online players
     * @param {string} gameServerId - The ID of the game server
     * @returns {Promise<Object>} Object containing player count and formatted player list
     */
    export async function getOnlinePlayers(gameServerId) {
        try {
            const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    online: [true]
                }
            });
    
            let playerList = "No players online";
            const playerCount = onlinePlayers.data.meta.total;
    
            if (playerCount > 0) {
                const playerNamePromises = onlinePlayers.data.data.map(async (pog) => {
                    const playerData = await takaro.player.playerControllerGetOne(pog.playerId);
                    return playerData.data.data.name;
                });
    
                const playerNames = await Promise.all(playerNamePromises);
                playerList = playerNames.join(", ");
            }
    
            return {
                count: playerCount,
                list: playerList
            };
        } catch (error) {
            console.error("Error getting online players:", error);
            return {
                count: 0,
                list: "Error retrieving player information"
            };
        }
    }
    
    /**
     * Gets bloodmoon information for 7 Days to Die
     * @param {string} gameServerId - The ID of the game server
     * @returns {Promise<string>} Formatted bloodmoon information
     */
    export async function getBloodmoonInfo(gameServerId) {
        try {
            // Get game time using gettime command
            const getTimeCommand = await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, {
                command: "gettime"
            });
    
            // Get bloodmoon frequency
            const bmFreqCommand = await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, {
                command: "getgamepref BloodMoonFrequency"
            });
    
            // Parse the current day and time
            let currentDay = 0;
            let currentTime = "";
    
            // Try to parse from gettime command (e.g. "Day 5, 02:02")
            const dayTimeMatch = getTimeCommand.data.data.rawResult?.match(/Day (\d+),\s+(\d+:\d+)/i);
            if (dayTimeMatch) {
                currentDay = parseInt(dayTimeMatch[1]);
                currentTime = dayTimeMatch[2];
            }
    
            // Parse bloodmoon frequency
            let bloodMoonFrequency = 7; // Default value
            const freqMatch = bmFreqCommand.data.data.rawResult?.match(/BloodMoonFrequency\s*=\s*(\d+)/i);
            if (freqMatch) {
                bloodMoonFrequency = parseInt(freqMatch[1]);
            }
    
            // Calculate days until bloodmoon
            if (currentDay > 0) {
                // Calculate next bloodmoon day
                const daysUntilBloodmoon = bloodMoonFrequency - (currentDay % bloodMoonFrequency);
                const nextBloodmoonDay = currentDay + daysUntilBloodmoon;
    
                // Special case: if today is bloodmoon day
                if (daysUntilBloodmoon === bloodMoonFrequency) {
                    return `📅 Day ${currentDay} (${currentTime}) | 🔴 **Bloodmoon: TONIGHT!** 🔴`;
                } else if (daysUntilBloodmoon === 1) {
                    return `📅 Day ${currentDay} (${currentTime}) | 🔴 Next Bloodmoon: Day ${nextBloodmoonDay} | ⚠️ **TOMORROW!** ⚠️`;
                } else {
                    return `📅 Day ${currentDay} (${currentTime}) | 🔴 Next Bloodmoon: Day ${nextBloodmoonDay} | ${daysUntilBloodmoon} days until bloodmoon`;
                }
            }
    
            // Fall back to old method if gettime doesn't work
            const bmDayCommand = await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, {
                command: "ggs BloodMoonDay"
            });
    
            let nextBloodmoonDay = 0;
            const bloodmoonMatch = bmDayCommand.data.data.rawResult?.match(/BloodMoonDay\s*=\s*(\d+)/i);
            if (bloodmoonMatch) {
                nextBloodmoonDay = parseInt(bloodmoonMatch[1]);
                return `🔴 Next Bloodmoon: Day ${nextBloodmoonDay}`;
            }
    
            return "Unknown";
        } catch (error) {
            console.error("Error getting bloodmoon information:", error);
            return "Error getting bloodmoon information";
        }
    }
    
    /**
     * Formats a date as a relative time string (e.g., "2 days ago")
     * @param {Date} date - The date to format
     * @returns {string} Formatted relative time string
     */
    export function formatRelativeTime(date) {
        const now = new Date();
        const diffMs = now.getTime() - date.getTime();
        const diffSeconds = Math.floor(diffMs / 1000);
        const diffMinutes = Math.floor(diffSeconds / 60);
        const diffHours = Math.floor(diffMinutes / 60);
        const diffDays = Math.floor(diffHours / 24);
    
        if (diffDays > 0) {
            return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;
        } else if (diffHours > 0) {
            return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;
        } else if (diffMinutes > 0) {
            return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`;
        } else {
            return 'just now';
        }
    }
    
    /**
     * Generates a complete server status message
     * @param {string} gameServerId - The ID of the game server
     * @param {Object} config - User configuration options
     * @returns {Promise<string>} Formatted server status message for Discord
     */
    export async function getServerStatusMessage(gameServerId, config = {}) {
        try {
            let message = "**Server Status**\n";
    
            // Get uptime if enabled in config
            if (config.showUptime !== false) {
                const serverStartTime = await getServerStartTime(gameServerId);
                const uptime = calculateUptime(serverStartTime);
                message += `⏱️ Uptime: ${uptime}\n`;
            }
    
            // Get server creation time if enabled in config
            if (config.showCreationTime !== false) {
                const serverCreationTime = await getServerCreationTime(gameServerId);
                const createdRelative = formatRelativeTime(serverCreationTime);
                message += `🆕 Server created: ${createdRelative}\n`;
            }
    
            // Get bloodmoon info if enabled in config
            if (config.showBloodmoon !== false) {
                const bloodmoonInfo = await getBloodmoonInfo(gameServerId);
                message += `${bloodmoonInfo}\n`;
            }
    
            // Always show player info
            const players = await getOnlinePlayers(gameServerId);
            message += `👥 Players online (${players.count}): ${players.list}`;
    
            return message;
        } catch (error) {
            console.error("Error generating server status message:", error);
            return `**Server Status**\nError: Could not retrieve server information`;
        }
    }