DynamicServerMessages

  • community
  • community-management
  • by Limon
  • Takaro main
  • all
Version
View export JSON

Dynamic CronJob Manager: Automated Game Server Command Scheduler


🎥 SETUP TUTORIAL
â–º Watch the Complete Guide
Learn how to set up and configure dynamic cronjobs

Create and manage dynamic cronjobs for executing game server commands based on flexible schedules. Define commands and their cron expressions to automate tasks.

  • Dynamic Updates: The module's cronJobGenerator cronjob automatically checks for and adapts to changes in the user configuration. This ensures that any modifications to commands or schedules are applied dynamically.
  • Cron Syntax Validation: The module validates the syntax of cron expressions. If an invalid expression is provided, that specific cronjob will be skipped, preventing errors and ensuring only valid schedules are applied. For assistance with cron syntax, you can use a tool like crontab.guru.
  • Command Sequencing: You can define multiple commands within a single cronjob entry, separated by semicolons. These commands will be executed sequentially according to the defined schedule.
  • Game Server Specificity: It is crucial to install the latest version of this module and create a separate copy for each game server. This ensures that cronjobs are executed on the intended server and avoids unintended cross-server execution.
  • Automated Management: The module handles the creation, updating, and cleanup of cronjobs. It removes any cronjobs that are no longer present in the configuration, maintaining a clean and accurate schedule.
  • Internal Synchronization: To ensure proper synchronization of cron schedules, the module may perform internal operations to update its configuration. This process is automated and does not require manual intervention from the user.

Configuration 1

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

SettingTypeDefaultDescription
cronjobs Cronjobs array — List of cronjobs and their schedules
Raw config schema
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "cronjobs": {
      "type": "array",
      "title": "Cronjobs",
      "description": "List of cronjobs and their schedules",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Optional name for the cronjob",
            "minLength": 1
          },
          "command": {
            "type": "string",
            "description": "Command to execute",
            "minLength": 1
          },
          "temporalValue": {
            "type": "string",
            "description": "Cron expression for execution schedule",
            "minLength": 1
          }
        },
        "required": [
          "command",
          "temporalValue"
        ]
      }
    }
  },
  "additionalProperties": false
}
Raw UI schema
{}

Cron jobs 1

Work the module runs on a schedule.

  • ServerMessagesGenerator

    Cron job source
    import { takaro, data } from '@takaro/helpers';
    
    async function main() {
        const { gameServerId, module: mod } = data;
        const cronjobs = mod.userConfig.cronjobs || [];
    
        // First get existing cronjobs for this module 
        const existingCronjobs = (await takaro.cronjob.cronJobControllerSearch({
            filters: {
                versionId: [mod.versionId]
            }
        })).data.data;
    
        // Track what we've processed to handle cleanup later
        const processedJobs = new Set();
    
        // Create or update cronjobs from config
        for (let i = 0; i < cronjobs.length; i++) {
            const job = cronjobs[i];
            // Use the provided name if available, otherwise use default number
            const jobNameSuffix = job.name ? `-${job.name}` : '';
            const jobName = `sm-${i + 1}${jobNameSuffix}`; // Start counting from 1
    
            processedJobs.add(jobName);
    
            const existingJob = existingCronjobs.find(j => j.name === jobName);
    
            const jobFunction = `
          import { takaro, data } from '@takaro/helpers';
          async function main() {
            const { gameServerId } = data;
            
            // Get online players through PlayerOnGameServer search
            const currentPlayers = (await takaro.playerOnGameserver.playerOnGameServerControllerSearch({
                filters: {
                    gameServerId: [gameServerId],
                    online: [true]
                }
            })).data.meta;
    
            // If no players online, exit early
            if (currentPlayers.total === 0) {
                return;
            }
            
            await takaro.gameserver.gameServerControllerSendMessage(gameServerId, {
              message: \`${job.command}\`,
            });
          }
          await main();
        `.trim();
    
            try {
                if (existingJob) {
                    // Update without versionId
                    await takaro.cronjob.cronJobControllerUpdate(existingJob.id, {
                        name: jobName,
                        temporalValue: job.temporalValue,
                        function: jobFunction
                    });
                } else {
                    try {
                        // Create includes versionId
                        await takaro.cronjob.cronJobControllerCreate({
                            name: jobName,
                            temporalValue: job.temporalValue,
                            versionId: mod.versionId,
                            function: jobFunction
                        });
                    } catch (createError) {
                        if (createError.response?.status === 409) {
                            const conflictJobSearch = await takaro.cronjob.cronJobControllerSearch({
                                filters: {
                                    name: [jobName]
                                }
                            });
    
                            if (conflictJobSearch.data.data.length > 0) {
                                const conflictJob = conflictJobSearch.data.data[0];
                                // Update without versionId
                                await takaro.cronjob.cronJobControllerUpdate(conflictJob.id, {
                                    name: jobName,
                                    temporalValue: job.temporalValue,
                                    function: jobFunction
                                });
                            } else {
                                throw createError;
                            }
                        } else {
                            throw createError;
                        }
                    }
                }
            } catch (error) {
                // Error handling with no logging
            }
        }
    
        // Clean up old cronjobs that are no longer in config
        for (const existingJob of existingCronjobs) {
            // Check if the name matches our pattern and if it's not in the processed list
            if (/^sm-\d+(-.*)?$/.test(existingJob.name) && !processedJobs.has(existingJob.name)) {
                try {
                    await takaro.cronjob.cronJobControllerRemove(existingJob.id);
                } catch (error) {
                    // Error handling with no logging
                }
            }
        }
    }
    
    await main();