通过Discord远程启动Minecraft服务器

这篇文章是筑波NS未来实验室2023年倒数第四天文章。

 

首先

我是@ramenha0141,就读于N·S高中的通勤课程。
在这篇文章中,我将介绍我开发的Minecraft服务器管理应用程序。

目标

    • 本来Minecraftのサーバーを構築するにはある程度の知識(ポート解放など)が必要なため、知識がなくても簡単にサーバーを構築して管理できるアプリケーションが欲しい

 

    必要な時だけサーバーを起動しておきたいが、誰かがプレイする際に毎回起動を行うのが大変な場合に、管理者以外が遠隔でサーバーを起動できるようにしたい

制作中的东西

服务器创建界面:

スクリーンショット

管理界面:

スクリーンショット

技术选择

电子

 

    • ReactなどのWeb技術を用いてデスクトップアプリケーションを開発することができるフレームワーク

 

    • メインプロセス用にNode.jsと、レンダラープロセス用にChromiumを内蔵している

 

    • VSCode, Discord などで採用されている

 

    • 当初、Chromiumの代わりにOS組み込みのWebviewを用いることでアプリケーションのサイズを軽量化したフレームワークである Tauri を採用していたが、JavaScript・Rust間の通信が複雑だったり、そもそもRustの理解度が足りなかったりしたため、Electronに移行した

 

    メインプロセス側のランタイムがNode.jsなので、Discord.jsと組み合わせやすいのも利点

响应

 

    宣言的UIを実現するための描画ライブラリ

React 路由器

 

    • クライアント側でのルーティングを実現するライブラリ

 

    当初はNext.jsやRemixも検討していたが、Electronと組み合わせる場合クライアント側で完結した方が楽そうだったので採用

TailwindCSS 风靡一时

 

    • クラス名でスタイルを指定するCSSライブラリ

 

    後述するshadcn/uiとでも用いられているため、相性を考えて採用

随机抽选一个选项:阴影框/用户界面

 

    • TailwindCSSとRadixUIをベースとしたコンポーネントライブラリ(厳密には違う)

 

    最近かなり勢いのあるライブラリで、Vercelなどで採用例が多い(開発者がVercelに入社した)

反应钩子表单 zi

 

    • React用のフォーム管理ライブラリ

 

    フォームの処理を簡素化するために使用

祖德

 

    • Typescriptファーストのスキーマ宣言・検証ライブラリ

 

    コンフィグファイルの検証に使用している

Discord.js Discord机器人框架

 

    JavaScriptでDiscordBotを操作するためのライブラリ

功能

创建服务器

我已经实施了从零开始设置服务器的方法和导入现有服务器的方法。本次将介绍设置的过程。

安装程序

スクリーンショット

当您指定服务器名称、路径和版本并同意使用条款后,将会下载相应版本的服务器Jar文件。

服务器Jar的下载URL可以从https://launchermeta.mojang.com/mc/game/version_manifest.json获取。

Discord 协议集成

スクリーンショット

通过Discord开发者门户创建一个机器人,并设置应用ID和令牌,可以使机器人能够在所加入的Discord服务器上实现远程启动、停止和状态确认的命令。

スクリーンショット

只需一种选项,将以下内容用中文进行释义:
通过注册Discord.js的SlashCommand,并订阅InteractionCreate事件以返回与命令相对应的响应来实现。

const rest = new REST({ version: '10' }).setToken(config.discord.token);

const status = new SlashCommandBuilder()
    .setName('mc-status')
    .setDescription(`Get status of ${info.name}`);
const start = new SlashCommandBuilder().setName('mc-start').setDescription(`Start ${info.name}`);
const stop = new SlashCommandBuilder().setName('mc-stop').setDescription(`Stop ${info.name}`);
const commands = [status, start, stop];

for (const guild of client.guilds.cache) {
    try {
        await rest.put(Routes.applicationGuildCommands(config.discord.applicationId, guild[1].id), {
            body: commands
        });
        console.log(`discord: registered commands for ${guild[1].name}`);
    } catch (e) {
        console.error(e);
    }
}

function handleInteraction(server: Server) {
    return async (interaction: Interaction) => {
        if (!interaction.isChatInputCommand()) return;

        switch (interaction.commandName) {
            case 'mc-status': {
                await interaction.reply(
                    `Status of ${server.info.name}: ${server.status}`,
                );
                break;
            }
            case 'mc-start': {
                if (server.status === 'idle') {
                    await interaction.reply(`Starting ${server.info.name}...`);
                    if (await server.start()) {
                        await interaction.followUp(
                            `Started ${server.info.name}.`,
                        );
                    } else {
                        await interaction.followUp(
                            `Failed to start ${server.info.name}.`,
                        );
                    }
                } else {
                    await interaction.reply(
                        `${server.info.name} is already running.`,
                    );
                }
                break;
            }
            case 'mc-stop': {
                if (server.status === 'running') {
                    await interaction.reply(`Stopping ${server.info.name}...`);
                    if (await server.stop()) {
                        await interaction.followUp(
                            `Stopped ${server.info.name}.`,
                        );
                    } else {
                        await interaction.followUp(
                            `Failed to stop ${server.info.name}.`,
                        );
                    }
                } else {
                    await interaction.reply(
                        `${server.info.name} is not running.`,
                    );
                }
                break;
            }
        }
    };
}

未来计划

我們計劃在未來實現控制台、性能監測和備份等功能,使其成為一個實用的應用程式。此外,我們目前使用Discord進行遠端操作,但由於此應用程式主要使用Web技術開發,因此我們也考慮使用WebSocket等在瀏覽器上實現相同GUI操作的方法。

在最后

在这次的圣诞日历中,我本来打算写关于自制语言编译器的内容,但是一直没有动力。后来决定开发我当时想要的东西,于是就开发了Minecraft的服务器管理应用程序。
在开发过程中,我不得不迁移一些在Tauri中实现的代码,还将一些状态管理从渲染器进程移到了明确的前端和后端分离的地方,这导致了架构的改变。虽然还未完全完成,但我很满意能把它做到可以写成文章的阶段。

另外,我要感谢那些给予我机会的N·S高等学校和筑波大学信息学群信息科学类的同学们。

明天是@Ryoga-exe先生的“仅使用CSS的蒙特卡洛方法”。

bannerAds