我想让Minecraft服务器的Op权限与Discord机器人的命令执行权限相对应

前提 tí) – premise, assumption, prerequisite

这是一篇关于使用Discord机器人操控Minecraft服务器的圣诞日历文章。

这篇文章是考虑将Minecraft服务器的操作权限对应到Discord机器人的命令执行权限的文章。我们已经实现了对应的程序,但并不代表只需按照该程序执行即可。请事先知悉。

此外,環境也

    WSL, Ubuntu 22.04.1
    minecraft server 1.19.2
    Python 3.10.6

正在进行中。

假设从机器人中使用命令的情况

首先,我们要考虑从 Discord 机器人执行指令到 Minecraft 服务器上的问题。

在中文中,可以使用的命令可能是list、op和msg/tell等。这些命令可以

    list

    現在ログインしている人数と,誰がログインしているかの表示

    op

    serverに対するoperater権限を与える
    私のpapermcの環境だと,ops.jsonのlevelを変更してもlevel4相当の権限になりました

    ほかに設定があるかもしれませんが,papermcのドキュメントそれっぽいやつがなかったので諦めました

    公式のjarファイルだとops.jsonを変更しserverを再起動することで権限が更新されます

    msg/tell

    誰かにメッセージを送る

这些都是类似的东西。只有op需要权限才能执行这些命令,所以可能没有必要。但实际上,本来stop()也需要权限,所以可以应用在那里。

将Minecraft的用户名和Discord的名称匹配。

可以使用discord bot来检查Minecraft服务器的权限,最好的方法是检查服务器所在目录中的ops.json文件。ops.json文件中包含用户ID、用户名以及相应的权限级别等信息。然而,ops.json文件中记录的用户名是用于玩Minecraft时的用户名,不一定与Discord上的名称相匹配。因此,需要进行对应处理。

这里,ops.json在初始状态下是空的,所以一开始需要手动执行op命令。因此,请先执行op命令。

怎么办?

考虑到具体要做什么,本文中我们将创建一个文件,使用类似于ops.json中所描述的格式,其中包含Minecraft用户ID、Minecraft用户名和Discord用户ID的JSON数组。此外,我们将在discord上右键点击自己并运行应用程序时更新该JSON文件。我在一篇简短的文章中对context_menu()进行了说明,请您能够查看一下,我会很高兴的。

编程

获取ops.json文件的内容

首先,介绍的是如何获取Minecraft服务器所在目录下的ops.json文件的内容,并将其转换为自己要使用的JSON格式,并保存到文件中的部分说明。这里将自己要使用的JSON文件命名为ops.json。为了区分,我们称之为「discord的ops.json」等等。请忽略其他部分,因为这个地方是唯一有冲突的地方。

代码如下:

from MCServer import MCServer
from MinecraftCommand import MinecraftCmd
from discord import Intents, Client
from discord.app_commands import CommandTree
import json
import os


class MCClient(Client):
    def __init__(self, intents: Intents) -> None:
        super().__init__(intents=intents)
        self.tree = CommandTree(self)
        self.server = MCServer()
        self.ops: dict = self.get_ops()

    async def setup_hook(self) -> None:
        self.tree.add_command(MinecraftCmd(self.server, self))
        await self.tree.sync()

    async def on_ready(self):
        print(f"login: {self.user.name} [{self.user.id}]")
        self.channel = self.get_channel(1047700283712622643)

    def get_ops(self) -> dict:
        json_file_name = "ops.json"
        if os.path.exists(f"./{json_file_name}"):
            ops = {}
            with open(json_file_name, "r") as file:
                ops = json.load(file)

            return ops

        ops = []
        ops_json = None
        with open("../mcserver/ops.json") as file:
            ops_json = json.load(file)

        for user in ops_json:
            ops.append(
                {"uuid": user["uuid"],
                 "name": user["name"],
                 "id": None}
            )

        with open("ops.json", "w") as write_file:
            print("dump")
            json.dump(ops, write_file, indent=2)

        return ops

获取内容的位置是get_ops()方法。
如果discord的ops.json文件已经存在,则假设已经读取了minecraft的ops.json并进行了早期返回。原因是之后的代码使用了id来初始化。现在我意识到,当id的值已经存在时,什么也不做的条件分叉更好。请忽略这个错误。
然后,只需简单地读取文件内容,转换为我要使用的形式,并以JSON格式输出,然后返回discord的ops.json文件。

打开上下文菜单()部

接下来是context_menu()部分的代码如下所示。

import os
import json
from discord import Intents, Client, Interaction, Member
from discord.ui import Select, View
from discordbot import MCClient
from dotenv import load_dotenv
load_dotenv()


class UnregisteredNameSelect(Select):
    def __init__(self, *, placeholder: str = None, client: Client) -> None:
        super().__init__(placeholder=placeholder)
        self.client = client

    async def callback(self, interaction: Interaction):
        self.client.ops[int(self.values[0])]["id"] = interaction.user.id
        with open("ops.json", "w") as write_file:
            print("dump")
            json.dump(self.client.ops, write_file, indent=2)
        await interaction.response.send_message("registered!", ephemeral=True)


intents = Intents.default()
client = MCClient(intents=intents)


@client.tree.context_menu(name="register name")
async def registerName(interaction: Interaction, member: Member):
    if not interaction.user == member:
        await interaction.response.send_message("sorry. please select yourself", ephemeral=True)
        return

    unregisteredName = []
    for index, user in enumerate(client.ops):
        if user["id"] is None:
            unregisteredName.append([user["name"], index])

    if not unregisteredName:
        await interaction.response.send_message("all user registered", ephemeral=True)
        return
 
    unregisteredNameSelector = UnregisteredNameSelect(client=client)
    for item in unregisteredName:
        unregisteredNameSelector.add_option(
            label=item[0], value=item[1]
        )

    select_view = View()
    select_view.add_item(unregisteredNameSelector)
    await interaction.response.send_message(
        f"hi, {member.mention}. select your name.", ephemeral=True, view=select_view
    )


client.run(os.getenv("TOKEN"))

暂不考虑class的部分,我将解释context_menu()函数的部分。

首先,为了确保只有自己能够对其他用户进行注册,我们提前终止。此外,如果没有未注册的用户,也就是说所有拥有权限的人都已经注册了,我们会传达这一信息并提前终止。

之后,为了进行注册操作,将显示未注册的人员名单并要求在下拉列表中进行选择。为了描述在选择主下拉列表时要执行的操作,我们创建了UnregisteredNameSelect类。关于Select,请先阅读文档。暂时可以理解为正在编写选中时的处理。当注册id后,将写入JSON文件中。

确认动作

我们试着去执行一下。

undefined

在试图注册他人时无法成功,但当尝试选择自己时,可以操作下拉列表,并且选择后显示注册成功。换言之,我认为期望的操作已成功实现。
但是,由于选择后仍可操作下拉列表,这并不太好。我认为再次从列表中选择将重新注册。由于Select有一个名为disabled的参数,只需将其设置为True即可在选择后禁用。

下一步,我们来看一下生成的discord的ops.json文件的内容。

[
  {
    "uuid": "uuid",
    "name": "itousagi",
    "id": 000000000000000000
  }
]

实际上,UUID或ID中会包含自己的身份信息。

因此,我认为可以将Minecraft用户和Discord用户进行关联。

上述内容表示了从想要将Minecraft服务器的OP权限与Discord机器人的命令执行权限相对应的方针,并做出了各种考虑。希望对您有所参考。辛苦了。

请查看此处的max-players项目以了解bypassesPlayerLimit的详细信息。

bannerAds