{
  "name": "7dtd_ChatBridge",
  "author": "limon",
  "supportedGames": [
    "7 Days to Die"
  ],
  "takaroVersion": "v0.0.24",
  "versions": [
    {
      "tag": "latest",
      "description": "**Limon_chatbridge: Discord Chat Integration for Your Game Server**\n\nThis module bridges your game server's chat and a Discord channel, enabling two-way communication with powerful, customizable filtering options.\n\n**Key Features:**\n\n![game chat](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/chatbridge7dtd_chatbridgeingame.png)\n\n* **Two-Way Chat Relay:** Forward messages between your game and Discord in real-time. Improve player interaction and build a stronger community.\n* **Customizable Channel Filtering:** Control exactly which chat channels get relayed to Discord:\n  * Global chat for server-wide messages\n  * Optional Party/Team chat integration\n  * Optional Friends chat integration\n* **Player Connection/Disconnection Notifications:** Get automated Discord messages when players join or leave your server.\n\n![player connection](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/chatbridge7dtd_playerconnected.png)\n* **Advanced Filtering Options:**\n  * **Command Filtering:** Prevent in-game command messages (e.g., `/ban`) from cluttering your Discord.\n  * **System Message Filtering:** Keep your Discord clean by blocking system (non-player) messages.\n\n![discord chat](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/chatbridge7dtd_discordchat.png)\n\n**Channel Prefixing:**\nMessages from different chat channels are clearly labeled in Discord:\n* Global chat appears normally\n* Party chat is prefixed with [Party]\n* Friends chat is prefixed with [Friends]\n\n**Server Status Tracking:**\n\n* Track player connections with links to Steam profiles\n* Monitor server status events\n* Support for 7 Days to Die bloodmoon countdown tracking\n\n![server status](https://raw.githubusercontent.com/gettakaro/community-modules-viewer/refs/heads/main/images/chatbridge7dtd_serverstatus.png)\n\n**Configuration Options:**\n\n* `sendPlayerConnected`: Enable player connect notifications\n* `sendPlayerDisconnected`: Enable player disconnect notifications\n* `onlyGlobalChat`: Restrict to global chat only (can be fine-tuned below)\n* `includePartyChat`: Include party/team chat in Discord (overrides onlyGlobalChat restriction)\n* `includeFriendsChat`: Include friends chat in Discord (overrides onlyGlobalChat restriction)\n* `filterCommands`: Filter out in-game command messages\n* `filterSystemMessages`: Filter out system messages\n* `useMonitoring`: Enable a dedicated monitoring channel for all messages\n* `showPlayerDetails`: Show Steam profile and Takaro profile links in notifications\n\n**Perfect for:**\n* Communities that want to bridge in-game and Discord chat\n* Servers with active Discord communities\n* Admins who want to monitor server activity from Discord\n* Multi-server networks with centralized Discord channels\n* 7 Days to Die servers wanting to share bloodmoon information",
      "configSchema": "{\"$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\"}}}]}}}",
      "uiSchema": "{}",
      "commands": [],
      "hooks": [
        {
          "name": "GameToDiscord",
          "eventType": "chat-message",
          "description": "Hook for chat-message events",
          "regex": null,
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const config = data.module.userConfig;\n\n    // Get message details\n    const sender = data.player ? data.player.name : 'Non-player';\n    const message = data.eventData.msg;\n    const channel = data.eventData.channel;\n\n    // Get command prefixes (default to ['/'] if not defined)\n    const commandPrefixes = config.commandPrefixes || ['/'];\n\n    // Check if message starts with any of the command prefixes\n    const isCommand = commandPrefixes.some(prefix => message.startsWith(prefix));\n\n    // Check if the message is from Discord (starts with [D])\n    const isDiscordMessage = message.startsWith('[D] ');\n\n    // If this is a Discord echo message, skip it completely to prevent loops\n    if (isDiscordMessage) {\n        return;\n    }\n\n    // Format message based on channel type\n    let formattedMessage;\n    if (channel === \"global\") {\n        formattedMessage = `**${sender}**: ${message}`;\n    } else if (channel === \"team\") {\n        formattedMessage = `[Party] **${sender}**: ${message}`;\n    } else {\n        formattedMessage = `[Friends] **${sender}**: ${message}`;\n    }\n\n    // Always send to monitoring channel if enabled (before any filtering)\n    if (config.useMonitoring && config.monitoringChannelId) {\n        await takaro.discord.discordControllerSendMessage(config.monitoringChannelId, {\n            message: formattedMessage\n        });\n    }\n\n    // FILTER SECTION FOR MAIN CHANNEL\n\n    // 1. Filter commands if enabled\n    if (isCommand && config.filterCommands) {\n        return; // Don't send commands to main channel\n    }\n\n    // 2. Filter system messages if enabled\n    if (sender === 'Non-player' && config.filterSystemMessages) {\n        return; // Don't send system messages to main channel\n    }\n\n    // 3. Filter chat types based on configuration\n    // Check party/team chat\n    if (channel === 'team' && config.includePartyChat !== true) {\n        return; // Skip if party chat not explicitly enabled\n    }\n\n    // Check friends chat\n    if (channel !== 'global' && channel !== 'team' && config.includeFriendsChat !== true) {\n        return; // Skip if friends chat not explicitly enabled\n    }\n\n    // If we reach this point, the message passes all filters - send to main channel\n    const mainChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;\n    if (mainChannel) {\n        await takaro.discord.discordControllerSendMessage(mainChannel, {\n            message: formattedMessage\n        });\n    }\n}\n\nawait main();"
        },
        {
          "name": "DiscordToGame",
          "eventType": "discord-message",
          "description": "Hook for discord-message events",
          "regex": null,
          "function": "import { takaro, data } from '@takaro/helpers';\nimport { getServerStatusMessage } from './utils.js';\n\nasync function main() {\n    try {\n        const config = data.module.userConfig;\n\n        // Skip messages from bots\n        if (data.eventData.author.isBot)\n            return;\n\n        // Get Discord channel from module system config\n        const discordChannelId = data.discordChannelId ||\n            data.module.systemConfig.hooks['DiscordToGame']?.discordChannelId;\n\n        if (!discordChannelId) {\n            console.error(\"Discord channel ID is missing from both data context and module config\");\n            return;\n        }\n\n        // Format the message for monitoring\n        const formattedMessage = `[Discord → Game] **${data.eventData.author.displayName}**: ${data.eventData.msg}`;\n\n        // If monitoring is enabled, send to monitoring channel\n        if (config.useMonitoring && config.monitoringChannelId) {\n            await takaro.discord.discordControllerSendMessage(config.monitoringChannelId, {\n                message: formattedMessage\n            });\n        }\n\n        // Check if the message is the serverstatus command\n        if (data.eventData.msg.toLowerCase().endsWith('serverstatus')) {\n            // Use a fixed server start time for demonstration\n            const serverStartTime = new Date(\"2025-04-12T11:54:01.547Z\");\n\n            // Get server status message\n            const message = await getServerStatusMessage(data.gameServerId, serverStartTime);\n\n            // Send to Discord\n            await takaro.discord.discordControllerSendMessage(discordChannelId, {\n                message: message\n            });\n\n            // Return early to avoid forwarding the command to the game\n            return;\n        }\n\n        // Forward regular messages to the game\n        await takaro.gameserver.gameServerControllerSendMessage(data.gameServerId, {\n            message: `[D] ${data.eventData.author.displayName}:  ${data.eventData.msg}`,\n        });\n    }\n    catch (error) {\n        console.error(error);\n\n        // Try to get Discord channel ID if we haven't already\n        const discordChannelId = data.discordChannelId ||\n            data.module.systemConfig.hooks['DiscordToGame']?.discordChannelId;\n\n        if (discordChannelId) {\n            await takaro.discord.discordControllerSendMessage(discordChannelId, {\n                message: 'Failed to forward your message to the game. Please try again later.',\n            });\n        } else {\n            console.error(\"Could not send error message because Discord channel ID is missing\");\n        }\n    }\n}\n\nawait main();"
        },
        {
          "name": "PlayerDisconnected",
          "eventType": "player-disconnected",
          "description": "Hook for player-disconnected events",
          "regex": null,
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const { player } = data;\n    const config = data.module.userConfig;\n    const discordChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;\n\n    // Check if showPlayerDetails is explicitly set to false\n    // If the option doesn't exist, default to showing details for backward compatibility\n    const showDetails = config.showPlayerDetails !== false;\n\n    let message = `**[👋 Disconnected]**: ${player.name} has left the server`;\n\n    // Only add links if showPlayerDetails is true\n    if (showDetails) {\n        message += `\\nSteam: https://steamcommunity.com/profiles/${player.steamId}\\n` +\n            `Takaro: https://dashboard.takaro.io/player/${player.id}/info`;\n    }\n\n    await takaro.discord.discordControllerSendMessage(discordChannel, {\n        message: message\n    });\n}\n\nawait main();"
        },
        {
          "name": "resetRegions",
          "eventType": "log",
          "description": "reset regions hook",
          "regex": "Region reset complete",
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    try {\n        // Check for required data\n        if (!data || !data.gameServerId) {\n            return;\n        }\n\n        const gameServerId = data.gameServerId;\n\n        // Get Discord channel ID\n        const discordChannel = data.module?.systemConfig?.hooks?.['DiscordToGame']?.discordChannelId;\n\n        if (!discordChannel) {\n            return;\n        }\n\n        const message = data.message || \"\";\n\n        // Get server name\n        let serverName = \"Server\";\n        try {\n            const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);\n            serverName = serverInfo?.data?.data?.name || \"Server\";\n        } catch (error) {\n            // Silently handle error\n        }\n\n        // Extract reset information\n        const resetInfo = message.match(/Reset (\\d+) chunks in (\\d+) regions/);\n        const chunks = resetInfo?.[1] || \"0\";\n        const regions = resetInfo?.[2] || \"0\";\n\n        const discordMessage = `**[🔄 Region Reset]**: ${serverName} has completed a region reset. Reset ${chunks} chunks in ${regions} regions.`;\n\n        // Send Discord notification\n        await takaro.discord.discordControllerSendMessage(discordChannel, {\n            message: discordMessage\n        });\n    } catch (error) {\n        // Silently handle any errors\n    }\n}\n\nawait main();"
        },
        {
          "name": "serverStatusChange",
          "eventType": "server-status-changed",
          "description": "hook for server status",
          "regex": null,
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const { gameServerId, eventData } = data;\n    const discordChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;\n\n    // Get the server name for a more informative message\n    const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);\n    const serverName = serverInfo.data.data.name;\n\n    // Determine if the server is online or offline from the event data\n    const isOnline = eventData.status === 'online';\n\n    // Create appropriate emoji and message based on status\n    const statusEmoji = isOnline ? '🟢' : '🔴';\n    const statusText = isOnline ? 'online' : 'offline';\n\n    // Create the message\n    const message = `**[${statusEmoji} Server ${statusText}]**: ${serverName} is now ${statusText}`;\n\n    // Send the message to Discord\n    await takaro.discord.discordControllerSendMessage(discordChannel, {\n        message: message\n    });\n}\n\nawait main();"
        },
        {
          "name": "PlayerConnected",
          "eventType": "player-connected",
          "description": "Hook for player-connected events",
          "regex": null,
          "function": "import { takaro, data } from '@takaro/helpers';\n\nasync function main() {\n    const { player } = data;\n    const config = data.module.userConfig;\n    const discordChannel = data.module.systemConfig.hooks['DiscordToGame'].discordChannelId;\n\n    // Check if showPlayerDetails exists and is explicitly false\n    const showDetails = config.showPlayerDetails !== false;\n\n    let message = `**[⚡ Connected]**: ${player.name} has joined the server`;\n\n    // Only add links if showPlayerDetails is true\n    if (showDetails) {\n        message += `\\nSteam: https://steamcommunity.com/profiles/${player.steamId}\\n` +\n            `Takaro: https://dashboard.takaro.io/player/${player.id}/info`;\n    }\n\n    await takaro.discord.discordControllerSendMessage(discordChannel, {\n        message: message\n    });\n}\n\nawait main();"
        }
      ],
      "cronJobs": [
        {
          "name": "serverStatus",
          "temporalValue": "*/30 * * * *",
          "description": "the server status cronjob",
          "function": "import { takaro, data } from '@takaro/helpers';\nimport { getServerStatusMessage } from './utils.js';\n\nasync function main() {\n    try {\n        const { gameServerId, module: mod } = data;\n        const discordChannel = mod.systemConfig.hooks['DiscordToGame']?.discordChannelId;\n\n        if (!discordChannel) {\n            console.error(\"Discord channel ID not configured\");\n            return { success: false, reason: \"Discord channel ID not configured\" };\n        }\n\n        // Use a fixed server start time for demonstration\n        const serverStartTime = new Date(\"2025-04-12T11:54:01.547Z\");\n\n        // Get the complete server status message\n        const message = await getServerStatusMessage(gameServerId, serverStartTime);\n\n        // Send the message to Discord\n        await takaro.discord.discordControllerSendMessage(discordChannel, {\n            message: message\n        });\n\n        // Return success result\n        return { success: true };\n    } catch (error) {\n        console.error(\"Error in serverStatus cronjob:\", error);\n        return {\n            success: false,\n            reason: error.message || \"Unknown error in serverStatus cronjob\"\n        };\n    }\n}\n\nawait main();"
        }
      ],
      "functions": [
        {
          "name": "utils",
          "description": "",
          "function": "import { takaro, data } from '@takaro/helpers';\n\n/**\n * Gets the server start time from the latest server-status-changed event with status \"online\"\n * @param {string} gameServerId - The ID of the game server\n * @returns {Promise<Date>} The server start time\n */\nexport async function getServerStartTime(gameServerId) {\n    try {\n        // First try to get server-status-changed events\n        const statusEvents = await takaro.event.eventControllerSearch({\n            filters: {\n                eventName: ['server-status-changed'],\n                gameserverId: [gameServerId]\n            }\n        });\n\n        // Filter for online events and find the most recent one\n        if (statusEvents.data.data.length > 0) {\n            const onlineEvents = statusEvents.data.data.filter(event =>\n                event.meta && event.meta.status === 'online'\n            );\n\n            if (onlineEvents.length > 0) {\n                // Sort by timestamp (newest first)\n                onlineEvents.sort((a, b) =>\n                    new Date(b.meta.timestamp).getTime() - new Date(a.meta.timestamp).getTime()\n                );\n\n                return new Date(onlineEvents[0].meta.timestamp);\n            }\n        }\n\n        // If no server-status-changed events found, try gameserver-created as fallback\n        const creationEvents = await takaro.event.eventControllerSearch({\n            filters: {\n                eventName: ['gameserver-created'],\n                gameserverId: [gameServerId]\n            }\n        });\n\n        if (creationEvents.data.data.length > 0) {\n            // Just use the first one since we're not sorting\n            return new Date(creationEvents.data.data[0].meta.timestamp);\n        }\n\n        // Final fallback - use server creation time from the gameServer object\n        const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);\n        if (serverInfo && serverInfo.data && serverInfo.data.data) {\n            return new Date(serverInfo.data.data.createdAt);\n        }\n\n        // If all else fails, use 1 day ago as fallback\n        const fallbackTime = new Date();\n        fallbackTime.setDate(fallbackTime.getDate() - 1);\n        return fallbackTime;\n    } catch (error) {\n        console.error(\"Error getting server start time:\", error);\n        // Return a fallback value if there's an error\n        const fallbackTime = new Date();\n        fallbackTime.setDate(fallbackTime.getDate() - 1);\n        return fallbackTime;\n    }\n}\n\n/**\n * Gets the server creation time from gameserver-created event\n * @param {string} gameServerId - The ID of the game server\n * @returns {Promise<Date>} The server creation time\n */\nexport async function getServerCreationTime(gameServerId) {\n    try {\n        // Get gameserver-created events\n        const creationEvents = await takaro.event.eventControllerSearch({\n            filters: {\n                eventName: ['gameserver-created'],\n                gameserverId: [gameServerId]\n            }\n        });\n\n        if (creationEvents.data.data.length > 0) {\n            // Sort by timestamp (oldest first) to get the original creation time\n            creationEvents.data.data.sort((a, b) =>\n                new Date(a.meta.timestamp).getTime() - new Date(b.meta.timestamp).getTime()\n            );\n\n            return new Date(creationEvents.data.data[0].meta.timestamp);\n        }\n\n        // Fallback to the server's creation time in the database\n        const serverInfo = await takaro.gameserver.gameServerControllerGetOne(gameServerId);\n        if (serverInfo && serverInfo.data && serverInfo.data.data) {\n            return new Date(serverInfo.data.data.createdAt);\n        }\n\n        // If all else fails, use today as fallback\n        return new Date();\n    } catch (error) {\n        console.error(\"Error getting server creation time:\", error);\n        return new Date(); // Return current time as fallback\n    }\n}\n\n/**\n * Calculates server uptime from a given start time\n * @param {Date} serverStartTime - The time when the server started\n * @returns {string} Formatted uptime string (e.g. \"2d 5h 30m\")\n */\nexport function calculateUptime(serverStartTime) {\n    try {\n        const currentTime = new Date();\n        const uptimeMs = currentTime.getTime() - serverStartTime.getTime();\n        const uptimeSeconds = Math.floor(uptimeMs / 1000);\n        const days = Math.floor(uptimeSeconds / 86400);\n        const hours = Math.floor((uptimeSeconds % 86400) / 3600);\n        const minutes = Math.floor((uptimeSeconds % 3600) / 60);\n\n        let serverUptime = \"\";\n        if (days > 0) serverUptime += `${days}d `;\n        if (hours > 0 || days > 0) serverUptime += `${hours}h `;\n        serverUptime += `${minutes}m`;\n\n        return serverUptime;\n    } catch (error) {\n        console.error(\"Error calculating server uptime:\", error);\n        return \"Unknown\";\n    }\n}\n\n/**\n * Gets a list of online players\n * @param {string} gameServerId - The ID of the game server\n * @returns {Promise<Object>} Object containing player count and formatted player list\n */\nexport async function getOnlinePlayers(gameServerId) {\n    try {\n        const onlinePlayers = await takaro.playerOnGameserver.playerOnGameServerControllerSearch({\n            filters: {\n                gameServerId: [gameServerId],\n                online: [true]\n            }\n        });\n\n        let playerList = \"No players online\";\n        const playerCount = onlinePlayers.data.meta.total;\n\n        if (playerCount > 0) {\n            const playerNamePromises = onlinePlayers.data.data.map(async (pog) => {\n                const playerData = await takaro.player.playerControllerGetOne(pog.playerId);\n                return playerData.data.data.name;\n            });\n\n            const playerNames = await Promise.all(playerNamePromises);\n            playerList = playerNames.join(\", \");\n        }\n\n        return {\n            count: playerCount,\n            list: playerList\n        };\n    } catch (error) {\n        console.error(\"Error getting online players:\", error);\n        return {\n            count: 0,\n            list: \"Error retrieving player information\"\n        };\n    }\n}\n\n/**\n * Gets bloodmoon information for 7 Days to Die\n * @param {string} gameServerId - The ID of the game server\n * @returns {Promise<string>} Formatted bloodmoon information\n */\nexport async function getBloodmoonInfo(gameServerId) {\n    try {\n        // Get game time using gettime command\n        const getTimeCommand = await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, {\n            command: \"gettime\"\n        });\n\n        // Get bloodmoon frequency\n        const bmFreqCommand = await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, {\n            command: \"getgamepref BloodMoonFrequency\"\n        });\n\n        // Parse the current day and time\n        let currentDay = 0;\n        let currentTime = \"\";\n\n        // Try to parse from gettime command (e.g. \"Day 5, 02:02\")\n        const dayTimeMatch = getTimeCommand.data.data.rawResult?.match(/Day (\\d+),\\s+(\\d+:\\d+)/i);\n        if (dayTimeMatch) {\n            currentDay = parseInt(dayTimeMatch[1]);\n            currentTime = dayTimeMatch[2];\n        }\n\n        // Parse bloodmoon frequency\n        let bloodMoonFrequency = 7; // Default value\n        const freqMatch = bmFreqCommand.data.data.rawResult?.match(/BloodMoonFrequency\\s*=\\s*(\\d+)/i);\n        if (freqMatch) {\n            bloodMoonFrequency = parseInt(freqMatch[1]);\n        }\n\n        // Calculate days until bloodmoon\n        if (currentDay > 0) {\n            // Calculate next bloodmoon day\n            const daysUntilBloodmoon = bloodMoonFrequency - (currentDay % bloodMoonFrequency);\n            const nextBloodmoonDay = currentDay + daysUntilBloodmoon;\n\n            // Special case: if today is bloodmoon day\n            if (daysUntilBloodmoon === bloodMoonFrequency) {\n                return `📅 Day ${currentDay} (${currentTime}) | 🔴 **Bloodmoon: TONIGHT!** 🔴`;\n            } else if (daysUntilBloodmoon === 1) {\n                return `📅 Day ${currentDay} (${currentTime}) | 🔴 Next Bloodmoon: Day ${nextBloodmoonDay} | ⚠️ **TOMORROW!** ⚠️`;\n            } else {\n                return `📅 Day ${currentDay} (${currentTime}) | 🔴 Next Bloodmoon: Day ${nextBloodmoonDay} | ${daysUntilBloodmoon} days until bloodmoon`;\n            }\n        }\n\n        // Fall back to old method if gettime doesn't work\n        const bmDayCommand = await takaro.gameserver.gameServerControllerExecuteCommand(gameServerId, {\n            command: \"ggs BloodMoonDay\"\n        });\n\n        let nextBloodmoonDay = 0;\n        const bloodmoonMatch = bmDayCommand.data.data.rawResult?.match(/BloodMoonDay\\s*=\\s*(\\d+)/i);\n        if (bloodmoonMatch) {\n            nextBloodmoonDay = parseInt(bloodmoonMatch[1]);\n            return `🔴 Next Bloodmoon: Day ${nextBloodmoonDay}`;\n        }\n\n        return \"Unknown\";\n    } catch (error) {\n        console.error(\"Error getting bloodmoon information:\", error);\n        return \"Error getting bloodmoon information\";\n    }\n}\n\n/**\n * Formats a date as a relative time string (e.g., \"2 days ago\")\n * @param {Date} date - The date to format\n * @returns {string} Formatted relative time string\n */\nexport function formatRelativeTime(date) {\n    const now = new Date();\n    const diffMs = now.getTime() - date.getTime();\n    const diffSeconds = Math.floor(diffMs / 1000);\n    const diffMinutes = Math.floor(diffSeconds / 60);\n    const diffHours = Math.floor(diffMinutes / 60);\n    const diffDays = Math.floor(diffHours / 24);\n\n    if (diffDays > 0) {\n        return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;\n    } else if (diffHours > 0) {\n        return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`;\n    } else if (diffMinutes > 0) {\n        return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`;\n    } else {\n        return 'just now';\n    }\n}\n\n/**\n * Generates a complete server status message\n * @param {string} gameServerId - The ID of the game server\n * @param {Object} config - User configuration options\n * @returns {Promise<string>} Formatted server status message for Discord\n */\nexport async function getServerStatusMessage(gameServerId, config = {}) {\n    try {\n        let message = \"**Server Status**\\n\";\n\n        // Get uptime if enabled in config\n        if (config.showUptime !== false) {\n            const serverStartTime = await getServerStartTime(gameServerId);\n            const uptime = calculateUptime(serverStartTime);\n            message += `⏱️ Uptime: ${uptime}\\n`;\n        }\n\n        // Get server creation time if enabled in config\n        if (config.showCreationTime !== false) {\n            const serverCreationTime = await getServerCreationTime(gameServerId);\n            const createdRelative = formatRelativeTime(serverCreationTime);\n            message += `🆕 Server created: ${createdRelative}\\n`;\n        }\n\n        // Get bloodmoon info if enabled in config\n        if (config.showBloodmoon !== false) {\n            const bloodmoonInfo = await getBloodmoonInfo(gameServerId);\n            message += `${bloodmoonInfo}\\n`;\n        }\n\n        // Always show player info\n        const players = await getOnlinePlayers(gameServerId);\n        message += `👥 Players online (${players.count}): ${players.list}`;\n\n        return message;\n    } catch (error) {\n        console.error(\"Error generating server status message:\", error);\n        return `**Server Status**\\nError: Could not retrieve server information`;\n    }\n}"
        }
      ],
      "permissions": []
    }
  ]
}