ravenscott-blog/markdown/Creating a User Installable Version of My-MC.Link.md
2024-10-02 01:41:09 -04:00

12 KiB
Raw Permalink Blame History

Enabling users to use My-MC.Link within any Channel on Discord.

The My-MC.Link Discord bot offers a comprehensive and user-friendly interface for Minecraft server management, enabling users to control server operations from any Discord server. This deep dive explores the technical aspects of the bot, including its architecture, command handling, token management, API integration, and the strategies employed to deliver seamless user interactions. By combining Discords API with the powerful features of the My-MC.Link service, this bot provides an extensive range of server functionalities in a highly accessible and easily deployable format.

Project Structure and Key Dependencies

The bot leverages several essential libraries and APIs to deliver its functionality:

import { Client, GatewayIntentBits, SlashCommandBuilder, REST, Routes, EmbedBuilder } from 'discord.js';
import jsonfile from 'jsonfile';
import MyMCLib from 'mymc-lib';
import unirest from 'unirest';
import { readFileSync } from 'fs';
import cmd from 'cmd-promise';

Source

https://git.ssh.surf/hypermc/hypermc-api-user-install-bot

Breakdown of Key Dependencies:

  • Discord.js: This is the backbone of the bot, providing classes like Client, SlashCommandBuilder, REST, and EmbedBuilder. These components enable interaction with Discord's API, handling everything from registering commands to managing user interactions and generating rich embeds for responses.

  • jsonfile: This package manages reading and writing user-specific tokens in JSON format. Storing tokens in a file allows the bot to persist authentication information between sessions, making it unnecessary for users to re-authenticate repeatedly.

  • MyMCLib: A custom library that acts as a wrapper around the My-MC.Link API, simplifying the process of interacting with the services various endpoints, such as starting or stopping servers, fetching logs, and managing mods.

  • unirest: Used to make HTTP requests to the My-MC.Link API, specifically to handle token generation and validation.

  • cmd-promise: A library that facilitates the execution of shell commands in a promise-based format, used for running server checks and other operational commands, ensuring that server status can be verified before certain actions are performed.

Discord Client Initialization

The bot initializes the Discord client with a focus on Guilds, which makes it a server-centric bot that exclusively handles commands and interactions within Discord servers (as opposed to direct messages):

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

By limiting the bots scope to Guilds, it ensures that the bot can manage interactions specific to Minecraft server administration in a controlled environment, reducing unnecessary overhead from other Discord intents.

Token Management and API Authentication

Loading and Saving Tokens

One of the most critical aspects of the bots design is token management, which authenticates user interactions with the My-MC.Link API. The bot stores tokens in a JSON file (tokens.json) and retrieves or refreshes these tokens as necessary.

Token Loading:

function loadTokens() {
  try {
    return jsonfile.readFileSync(tokensFile);
  } catch (error) {
    console.error('Error reading tokens file:', error);
    return {};
  }
}

The loadTokens() function reads the tokens.json file and returns an object containing user tokens. If the file cannot be read (e.g., it doesnt exist or has been corrupted), an empty object is returned, and the bot can request a new token.

Token Saving:

function saveTokens(tokens) {
  jsonfile.writeFileSync(tokensFile, tokens, { spaces: 2 });
}

The saveTokens() function writes the token data back to the tokens.json file, ensuring that any new or refreshed tokens are persisted for future use.

Automatic Token Retrieval

If a user doesnt have a valid token or their token has expired, the bot automatically requests a new one from the My-MC.Link service using the fetchAndSaveToken() function:

async function fetchAndSaveToken(userId, interaction) {
  return unirest
    .post(config.endpoint.toString())
    .headers({ 'Accept': 'application/json', 'Content-Type': 'application/json' })
    .send({ "username": `mc_${userId}`, "password": config.password.toString()})
    .then((tokenInfo) => {
      const tokens = loadTokens();
      tokens[userId] = tokenInfo.body.token;  // Save the new token
      saveTokens(tokens);
      return tokenInfo.body.token;
    })
    .catch((error) => {
      console.error('Error fetching token:', error);
      sendSexyEmbed("Error", "An error occurred while fetching your API token.", interaction);
      throw error;
    });
}

This function performs several key steps:

  1. It makes a POST request to the My-MC.Link API, sending the users credentials to request a new token.
  2. On success, it loads the existing tokens, updates the token for the user, and saves the updated tokens to the tokens.json file.
  3. If theres an error (e.g., the API is down or the request fails), it logs the error and provides feedback to the user via a rich embed.

Token Re-Validation and Re-Fetching

Once a token is stored, the bot checks its validity and, if necessary, automatically fetches a new token when making API calls:

async function getToken(userId, interaction) {
  const tokens = loadTokens();
  if (!tokens[userId]) {
    return await fetchAndSaveToken(userId, interaction);
  }
  return tokens[userId];
}

async function handleApiCall(apiCall, userId, interaction) {
  try {
    return await apiCall();
  } catch (error) {
    console.error('Token error, re-fetching token...');
    await fetchAndSaveToken(userId, interaction);
    return await apiCall();
  }
}

Heres what happens:

  1. getToken: This function checks if a token exists for the user in tokens.json. If no token is found, it calls fetchAndSaveToken() to retrieve and save a new one.
  2. handleApiCall: Wraps any API call to handle invalid tokens by retrying the request after fetching a new token. If a token has expired or there is any issue with authentication, the bot fetches a fresh token and retries the request.

Command Registration with Discord

The bot uses Discords SlashCommandBuilder to define a series of commands that allow users to interact with their Minecraft servers. These commands are registered with Discord using the REST API:

const commands = [
  new SlashCommandBuilder().setName('server-stats').setDescription('Get the server statistics'),
  new SlashCommandBuilder().setName('server-log').setDescription('Get the server log'),
  new SlashCommandBuilder().setName('start-server').setDescription('Start the Minecraft server'),
  new SlashCommandBuilder().setName('stop-server').setDescription('Stop the Minecraft server'),
  new SlashCommandBuilder().setName('restart-server').setDescription('Restart the Minecraft server'),
  // Additional commands...
];

// Register commands with Discord
const rest = new REST({ version: '10' }).setToken(config.token);
(async () => {
  try {
    console.log('Started refreshing application (/) commands.');
    await rest.put(Routes.applicationCommands(config.clientId), { body: JSONCommands });
    console.log('Successfully reloaded application (/) commands.');
  } catch (error) {
    console.error(error);
  }
})();

Each command is defined with a name and description using SlashCommandBuilder, which simplifies the process of adding new commands. These commands are then registered with Discord's API, ensuring they are available for use within the server.

Handling User Interactions

When users invoke commands, the bot listens for interaction events and routes the request to the appropriate function based on the command name:

client.on('interactionCreate', async interaction => {
  if (!interaction.isCommand()) return;

  const userId = interaction.user.id;
  const apiToken = await getToken(userId, interaction);
  const MyMC = new MyMCLib(apiToken);

  switch (interaction.commandName) {
    case 'server-stats':
      const stats = await handleApiCall(() => MyMC.getStats(), userId, interaction);
      handleResponse(stats, interaction);
      break;

    case 'start-server':
      const startResult = await handleApiCall(() => MyMC.startServer(), userId, interaction);
      handleResponse(startResult, interaction);
      break;

    case 'stop-server':
      const stopResult = await handleApiCall(() => MyMC.stopServer(), userId, interaction);
      handleResponse(stopResult, interaction);
      break;

    // Other commands...
  }
});

Each command is mapped to an API call using the MyMCLib library. The bot interacts with the Minecraft server via authenticated requests, and responses are processed and displayed back to the user.

Sending Responses and Embeds

One of the standout features of this bot is its use of rich embeds for displaying information to users. These embeds provide a visually appealing way to present data such as server statistics, logs, or mod information.

Simple Embeds

For single-field responses, the bot sends a simple embed with a title and description:

function sendSexyEmbed(title, description, interaction, ephemeral = false) {
  const embed = new EmbedBuilder()
    .setColor("#3498DB")
    .setTitle(title)
    .setDescription(description)
    .setTimestamp()
    .setFooter({
      text: `Requested by ${interaction.user.username}`,
      iconURL: `${interaction.user.displayAvatarURL()}`
    });
  interaction

.reply({
    embeds: [embed],
    ephemeral: ephemeral
  });
}

This function ensures that every response is styled with consistent colors, timestamps, and user information.

Complex Embeds with Multiple Fields

For more complex responses (such as server stats or mod lists), the bot generates an embed with multiple fields:

function sendSexyEmbedWithFields(title, description, fields, interaction, ephemeral = false) {
  const embed = new EmbedBuilder()
    .setColor("#3498DB")
    .setTitle(title)
    .setDescription(description !== "N/A" ? description : undefined)
    .addFields(fields)
    .setTimestamp()
    .setFooter({
      text: `Requested by ${interaction.user.username}`,
      iconURL: `${interaction.user.displayAvatarURL()}`
    });
  interaction.reply({
    embeds: [embed],
    ephemeral: ephemeral
  });
}

This method allows the bot to handle more detailed responses, such as server resource usage, mod lists, and player data.

Error Handling and Resilience

A critical aspect of the bots design is its resilience in the face of errors, particularly around token validation and API requests. The bot gracefully handles errors by attempting to fetch a new token and retrying the request. Additionally, the bot provides users with feedback through embeds when something goes wrong, keeping them informed without the need for manual intervention.

Final Thoughts: A Comprehensive Minecraft Server Management Bot

The My-MC.Link Discord bot is a sophisticated and powerful tool for Minecraft server management, offering a seamless integration with Discord that enables users to perform server tasks through a familiar interface. Its use of modern technologies such as discord.js, MyMCLib, and JSON-based token management ensures that the bot is both scalable and user-friendly. The automatic token handling, rich embeds, and wide range of server management commands make the bot an invaluable tool for any My-MC.Link user.

From a technical standpoint, the bot demonstrates how effective integration of Discord with external services can lead to a highly functional and interactive user experience. With the ability to automate token management, handle complex API interactions, and deliver visually appealing feedback, this bot sets a high standard for user-installable applications within the Discord ecosystem.