7dtd_ChatBridge
- community
- integration
- by limon
- Takaro v0.0.24
- 7 Days to Die
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:

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

- 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.
- Command Filtering: Prevent in-game command messages (e.g.,

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

Configuration Options:
sendPlayerConnected: Enable player connect notificationssendPlayerDisconnected: Enable player disconnect notificationsonlyGlobalChat: 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 messagesfilterSystemMessages: Filter out system messagesuseMonitoring: Enable a dedicated monitoring channel for all messagesshowPlayerDetails: 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.
| Setting | Type | Default | Description |
|---|---|---|---|
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`; } }