Minecraft 服务器的安全性始终是个难题。对于离线服务器,可以通过配置皮肤站和登录插件来增强安全性,防止被爬虫暴力破解。但正版验证服务器只能依赖白名单来主动控制可进入的玩家列表,虽然保障了安全,却给管理带来了不便。

无论是服主亲自添加白名单,还是通过 MCSManager 等面板让其他管理员协同操作,终究都需要手动处理。因此,我们需要寻找某种自动化方案,让玩家能够自助通过白名单。考虑到群友大多是可信的,只需部署一个 QQ 机器人来实现白名单自动化审核,或者允许管理员在群内直接向服务器发送指令,就能大大方便服务器的管理。

申请QQ官方机器人权限并接入Koishi

非官方机器人虽然自由灵活、部署方便,但稳定性往往不佳。考虑到管理服务器的功能并不涉及 AI 等复杂需求,申请官方机器人权限反而是最省事的选择。

它的缺点在于上线时需要等待腾讯审核,通常要好几天,并且 QQ 官方机器人要求配置 IP 白名单,这意味着你需要一台拥有固定公网 IP 的服务器(这应该不算难事)。

申请和接入流程本身没有门槛,只要按步骤操作,任何人都可以顺利完成。Koishi 论坛上已有详尽教程:
QQ/QQ 频道 登录指南 (布丁) - 技术分享 - Koishi Forum

配置MC服务器

MC的rcon虽然年久失修,有些小bug,但是无伤大雅,用来远程管理服务器再合适不过了

我们需要配置并开启rcon,找到服务器的server.properties 并编辑

rcon的配置就几行,根据实际情况修改即可,同时别忘了开启服务器的whitelist

但是rcon端口直接暴露在公网并不安全,请尽量使用Wireguard组网并在虚拟局域网中进行访问

enable-rcon=true
rcon.password=XXXXXXXXXXXXXXXXX
rcon.port=25575
white-list=true

Koishi插件配置

接下来的工作其实很简单了,Koishi插件市场中有大量的关于MC rcon和whitelist的插件,找到一个能用的安装就行

当然也可以自己写一个插件,不论是模板项目还是上传到插件市场都挺方便

我的需求是可以支持配置多个服务器进行管理

使用npm的rcon-client项目:rcon-client - npm

大致的方案如下

// 引用
const { Rcon } = require("rcon-client");
// 配置项定义
exports.Config = koishi_1.Schema.object({
    servers: koishi_1.Schema.array(koishi_1.Schema.object({
        name: koishi_1.Schema.string().required().description('服务器名 (用于指令匹配)'),
        address: koishi_1.Schema.string().required().description('RCON地址 (格式 IP:端口)'),
        password: koishi_1.Schema.string().role('secret').required().description('RCON密码')
    })).description('服务器配置组列表'),
});
// 具体实现
ctx.command('whitelist <serverName> <mcId>', '添加Minecraft白名单', { authority: 1 })
	.action(async ({ session }, serverName, mcId) => {
		if (!serverName) {
			const serverList = config.servers && config.servers.length > 0 ?
				config.servers.map((s, index) => `${index + 1}、${s.name}`).join('\n') :
				'暂无配置的服务器';

			return `请输入服务器名\n当前服务器列表\n${serverList}`;
		}


		const serverConf = config.servers?.find(s => s.name === serverName);
		if (!serverConf) {
			return `未找到名为 "${serverName}" 的服务器配置,请检查插件配置。`;
		}

		if (!mcId) return '请输入玩家MC ID';

		let rcon;
		try {
			await session.send(`正在连接到 ${serverName} ...`);

			rcon = await connectRcon(serverConf);

			const response = await rcon.send(`whitelist add ${mcId}`);

			return `[${serverName}] 执行结果:\n${response}`;

		} catch (error) {
			console.error(error);
			return `操作失败: ${error.message}`;
		} finally {
			if (rcon) {
				try {
					await rcon.end();
				} catch (e) {}
			}
		}
	});


ctx.command('rcon <serverName> <mcCommand:text>', '通过RCON向指定服务器发送任意Minecraft指令', { authority: 4 })
	.action(async ({ session }, serverName, mcCommand) => {
		if (!serverName) {
			const serverList = config.servers && config.servers.length > 0 ?
				config.servers.map((s, index) => `${index + 1}、${s.name}`).join('\n') :
				'暂无配置的服务器';

			return `请输入服务器名\n当前服务器列表\n${serverList}`;
		}

		const serverConf = config.servers?.find(s => s.name === serverName);
		if (!serverConf) {
			return `未找到名为 "${serverName}" 的服务器配置,请检查插件配置。`;
		}
		if (!mcCommand || !mcCommand.trim()) {
			return '请输入要执行的MC指令';
		}
		const commandToSend = mcCommand.trim().replace(/^\/+/, '');
		if (!commandToSend) {
			return '指令内容不能为空';
		}
		let rcon;
		try {
			await session.send(`正在连接到 ${serverName} ...`);
			rcon = await connectRcon(serverConf);
			const response = await rcon.send(commandToSend);
			return `[${serverName}] 命令执行结果 
> ${commandToSend} 
${response ? `\n${response}` : '\n无返回内容'}`;
		} catch (error) {
			console.error(error);
			return `执行失败: ${error.message}`;
		} finally {
			if (rcon) {
				try {
					await rcon.end();
				} catch (e) {}
			}
		}
	});