quake3 server connector | test specific channel | discord bot | Search

The code is a Discord bot that responds to challenge commands, which trigger the launch of a Quake III Arena server with customized settings based on environment variables and command input. The bot uses regular expressions to parse challenge commands, connects to a Quake III Arena server via the serverApi module, and handles errors by displaying a message when no servers are available.

Run example

npm run import -- "challenge discord command"

challenge discord command

var importer = require('../Core')
var discordApi = importer.import("discord api")
var serverApi = importer.import("quake 3 server commands")

var CHALLENGE = /(@[^:@\s]+\s*chall?[ae]nge|chall?[ae]nge\s*@[^:@\s]+)\s*([^:@\s]*?)\s*([^:@\s]*?)/ig
var DEFAULT_HOST = process.env.DEFAULT_HOST || 'http://quakeiiiarena.com/play/'
var MODS = typeof process.env.DEFAULT_MODS == 'string'
    ? JSON.parse(process.env.DEFAULT_MODS)
    : [
        'baseq3',
        'freon'
    ]

async function challengeCommand(command) {
    if(!command.private && (!command.mentions || command.mentions.length === 0))
        return
    var options = CHALLENGE.exec(command.content)
    var launch = (options ? options[2] : '') || ''
    var map = (options ? options[3] : '') || ''
    var message = 'I read you'
    var instruction = ''
    if(!MODS.includes(launch) && map.length === 0) {
        map = launch
        launch = ''
    }
    if(map.length === 0) {
        map = 'q3dm17'
    }
    if(launch.length == 0) {
        instruction += ', assuming baseq3 on map ' + map
    } else if(command.launching) {
        instruction += ' ' + launch + ' on map ' + map
    }
    if(!command.launching && !command.content.match(/:thumbsup:/ig)) {
        message = 'Waiting for reaction'
        instruction += ', react with :thumbsup: to launch'
    }
    if(command.launching) {
        message = 'Launching'
        await discordApi.createMessage(message + instruction + '\n```BOT'+command.id+'L\nbeep boop\n```\n', command.channel_id)
        await discordApi.triggerTyping(command.channel_id)
        var masters = await serverApi.listMasters(void 0, void 0, false)
        if(masters.length === 0) {
            await discordApi.createMessage(`Boo hoo, no servers available. :cry:` 
                + '\n```BOT'+command.id+'L\nbeep boop\n```\n', command.channel_id)
            return
        }
        await serverApi.sendRcon(masters[0].ip, masters[0].port, '\exec ' + launch + '.cfg')
        await serverApi.sendRcon(masters[0].ip, masters[0].port, '\map ' + map)
        await new Promise(resolve => setTimeout(resolve, 1000))
        await discordApi.createMessage(`Match is ready ${DEFAULT_HOST}?connect%20${masters[0].ip}:${masters[0].port} (${masters[0].ip}:${masters[0].port})`
                                       + '\n```BOT'+command.id+'L\nbeep boop\n```\n', command.channel_id)
    } else if (instruction.length > 0) {
        await discordApi.createMessage(message + instruction + '\n```BOT'+command.id+'\nbeep boop\n```\n', command.channel_id)
    }
}

module.exports = challengeCommand

What the code could have been:

const { DiscordAPI } = require('../Core');
const { serverCommands, api } = new DiscordAPI();
const { Quake3Server } = require('../Quake3Server');

const CHALLENGE_REGEX = /(@[^:@\s]+\s*chall?[ae]nge|chall?[ae]nge\s*@[^:@\s]+)\s*([^:@\s]*?)\s*([^:@\s]*?)/ig;
const DEFAULT_HOST = process.env.DEFAULT_HOST || 'http://quakeiiiarena.com/play/';
const DEFAULT_MODS = process.env.DEFAULT_MODS || ['baseq3', 'freon'];
const MAP_DEFAULT = 'q3dm17';

class ChallengeCommand {
  async execute(command) {
    if (!command.private && (!command.mentions || command.mentions.length === 0)) return;
    const { content } = command;
    const match = CHALLENGE_REGEX.exec(content);

    if (!match) return;

    const [,, launch, map] = match;
    const { message, instruction } = this.parseLaunch(launch, map);

    if (!command.launching) {
      await this.createMessage(command.channel_id, message, instruction);
      return;
    }

    await this.launchMap(command.channel_id, launch, map);
  }

  parseLaunch(launch, map) {
    if (!DEFAULT_MODS.includes(launch) && map.length === 0) {
      map = launch;
      launch = '';
    }

    if (map.length === 0) map = MAP_DEFAULT;

    let message = 'I read you';
    let instruction = '';

    if (launch.length === 0) {
      instruction += `, assuming ${DEFAULT_MODS[0]} on map ${map}`;
    } else {
      instruction += ` ${launch} on map ${map}`;
    }

    return { message, instruction };
  }

  async createMessage(channelId, message, instruction) {
    const instructionText = instruction? `, react with :thumbsup: to launch` : '';
    await api.createMessage(message + instruction + '\n```BOT' + channelId + 'L\nbeep boop\n```\n', channelId);
    if (instructionText) await api.createMessage(`Waiting for reaction${instructionText} \n```BOT' + channelId + 'L\nbeep boop\n````, channelId);
  }

  async launchMap(channelId, launch, map) {
    this.triggerTyping(channelId);
    const masters = await Quake3Server.listMasters();
    if (masters.length === 0) {
      await api.createMessage(`Boo hoo, no servers available. :cry:` + '\n```BOT' + channelId + 'L\nbeep boop\n```\n', channelId);
      return;
    }

    const master = masters[0];
    await Quake3Server.sendRcon(master.ip, master.port, `\\exec ${launch}.cfg`);
    await Quake3Server.sendRcon(master.ip, master.port, `\\map ${map}`);

    await new Promise(resolve => setTimeout(resolve, 1000));
    await api.createMessage(`Match is ready ${DEFAULT_HOST}?connect%20${master.ip}:${master.port} (${master.ip}:${master.port})\n```BOT' + channelId + 'L\nbeep boop\n```\n`, channelId);
  }

  triggerTyping(channelId) {
    api.triggerTyping(channelId);
  }
}

module.exports = new ChallengeCommand();

Breakdown of the Code

Importing Modules

The code starts by importing two modules using the require function:

var importer = require('../Core')
var discordApi = importer.import('discord api')
var serverApi = importer.import('quake 3 server commands')

These modules are likely custom modules for interacting with the Discord API and a Quake III Arena server.

Regular Expressions

The code defines a regular expression CHALLENGE to match challenge commands:

var CHALLENGE = /(@[^:@\s]+\s*chall?[ae]nge|chall?[ae]nge\s*@[^:@\s]+)\s*([^:@\s]*?)\s*([^:@\s]*?)/ig

This regular expression matches commands in the format @username chall[ae]nge [launch] [map], where launch and map are optional.

Environment Variables

The code retrieves environment variables for the default host and mods:

var DEFAULT_HOST = process.env.DEFAULT_HOST || 'http://quakeiiiarena.com/play/'
var MODS = typeof process.env.DEFAULT_MODS =='string'
   ? JSON.parse(process.env.DEFAULT_MODS)
    : [
        'baseq3',
        'freon'
    ]

These variables are used to determine the default host and mods for the Quake III Arena server.

Challenge Command Function

The code defines an asynchronous function challengeCommand to handle challenge commands:

async function challengeCommand(command) {
    //...
}

This function takes a command object as an argument and uses the CHALLENGE regular expression to parse the command content. It then determines the launch and map values based on the command content and environment variables.

Launching the Quake III Arena Server

The code uses the serverApi module to connect to a Quake III Arena server and send rcon commands to launch the game:

if(command.launching) {
    //...
    await serverApi.sendRcon(masters[0].ip, masters[0].port, '\exec'+ launch + '.cfg')
    await serverApi.sendRcon(masters[0].ip, masters[0].port, '\map'+ map)
}

This code assumes that the serverApi module provides a way to connect to a Quake III Arena server and send rcon commands to launch the game.

Error Handling

The code includes error handling to display a message when no servers are available:

if(masters.length === 0) {
    await discordApi.createMessage(`Boo hoo, no servers available. :cry:` 
        + '\n```BOT'+command.id+'L\nbeep boop\n```\n', command.channel_id)
    return
}

This code displays a message when no servers are available and returns from the function.