diff --git a/build.gradle.kts b/build.gradle.kts index 478624f..5218eb9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -54,6 +54,7 @@ dependencies { implementation(libs.aws.s3.sdk) implementation(libs.rconlib) implementation(libs.mcprotocollib) + implementation("dnsjava:dnsjava:3.6.2") } tasks.build { diff --git a/src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt b/src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt index 215f586..1c835e5 100644 --- a/src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt +++ b/src/main/kotlin/cn/rtast/fancybot/commands/misc/MinecraftCommand.kt @@ -30,9 +30,9 @@ import cn.rtast.fancybot.enums.mc.MCVersionType import cn.rtast.fancybot.rconManager import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Logger -import cn.rtast.fancybot.util.mcbot.MCBot import cn.rtast.fancybot.util.mcbot.MCClient import cn.rtast.fancybot.util.misc.isFullyTransparent +import cn.rtast.fancybot.util.misc.resolveMinecraftSrv import cn.rtast.fancybot.util.misc.scaleImage import cn.rtast.fancybot.util.misc.toByteArray import cn.rtast.fancybot.util.misc.toURL @@ -54,7 +54,6 @@ import cn.rtast.rob.util.ob.OneBotListener import cn.rtast.rob.util.ob.asMessageChain import cn.rtast.rob.util.ob.asNode import okhttp3.FormBody -import org.geysermc.mcprotocollib.network.tcp.TcpClientSession import java.awt.AlphaComposite import java.awt.Color import java.awt.image.BufferedImage @@ -602,41 +601,45 @@ class RCONCommand : BaseCommand() { class MCBotCommand : BaseCommand() { override val commandNames = listOf("/mcbot") - private val userClientMap = mutableMapOf>() + private val userClientMap = mutableMapOf>() override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List) { - val clients = mutableListOf() + val clients = mutableListOf() val action = args.first() when (action) { - "s" -> { + "start" -> { val executor = Executors.newFixedThreadPool(100) - val host = args[1] - val port = args[2].toInt() - val count = args[3].toInt() - val msgContent = args.drop(4).joinToString(" ").trim() - val delay = if (args.size == 5) 2000L else args[5].toLong() - val continueSendMessage = msgContent.contains("!!while!!") + val address = args[1] + val host = address.split(":").first() + val (newAddress, newPort) = host.resolveMinecraftSrv() + val count = args[2].toInt() (1..count).forEach { - val botName = generateRandomString() - val client = MCClient(host, port, botName).createClient() - clients.add(client) - } - clients.forEach { - executor.execute { - val bot = MCBot(msgContent.replace("!!while!!", ""), continueSendMessage, delay, it) - bot.run() - } + val client = MCClient(newAddress, newPort, generateRandomString()) + .also { it.createClient() } + executor.execute { clients.add(client.runBot()) } } userClientMap[message.sender.userId] = clients + message.reply("成功开启任务: $address, $count") } - "c" -> { + "cancel" -> { if (userClientMap.containsKey(message.sender.userId)) { - userClientMap[message.sender.userId]!!.forEach { it.disconnect("Bye~") } - userClientMap.remove(message.sender.userId) + userClientMap.entries.forEach { + if (it.key == message.sender.userId) { + it.value.forEach { it.disconnect() } + userClientMap.remove(it.key) + } + } message.reply("成功取消任务") + } else { + message.reply("你还没开启任务呢") } } + + "message" -> { + val content = args.drop(1).joinToString(" ").trim() + userClientMap[message.sender.userId]!!.forEach { it.sendChat(content) } + } } } } \ No newline at end of file diff --git a/src/main/kotlin/cn/rtast/fancybot/util/mcbot/MCBot.kt b/src/main/kotlin/cn/rtast/fancybot/util/mcbot/MCBot.kt deleted file mode 100644 index 52f724f..0000000 --- a/src/main/kotlin/cn/rtast/fancybot/util/mcbot/MCBot.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright © 2024 RTAkland - * Author: RTAkland - * Date: 2024/10/17 - */ - - -package cn.rtast.fancybot.util.mcbot - -import org.geysermc.mcprotocollib.network.Session -import org.geysermc.mcprotocollib.network.event.session.SessionAdapter -import org.geysermc.mcprotocollib.network.packet.Packet -import org.geysermc.mcprotocollib.network.tcp.TcpClientSession -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundLoginPacket -import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket -import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatPacket -import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket -import java.time.Instant -import java.util.BitSet - -class MCBot( - private val content: String = "FancyBot喵~", - private val continueSendMessage: Boolean = false, - private val sendDelay: Long = 2000L, - private val client: TcpClientSession -) : Runnable { - - private fun sendMessage() = - client.send(ServerboundChatPacket(content, Instant.now().toEpochMilli(), 0L, null, 0, BitSet())) - - override fun run() { - client.addListener(object : SessionAdapter() { - override fun packetReceived(session: Session, packet: Packet) { - if (packet is ClientboundLoginPacket) { - if (continueSendMessage) { - while (true) { - try { - sendMessage() - Thread.sleep(sendDelay) - } catch (_: InterruptedException) { - Thread.currentThread().interrupt() - } - } - } else { - sendMessage() - } - } else if (packet is ClientboundPlayerPositionPacket) { - val p = packet - client.send(ServerboundAcceptTeleportationPacket(p.teleportId)); - } - } - }) - client.connect() - } -} \ No newline at end of file diff --git a/src/main/kotlin/cn/rtast/fancybot/util/mcbot/MCClient.kt b/src/main/kotlin/cn/rtast/fancybot/util/mcbot/MCClient.kt index 897710a..d004455 100644 --- a/src/main/kotlin/cn/rtast/fancybot/util/mcbot/MCClient.kt +++ b/src/main/kotlin/cn/rtast/fancybot/util/mcbot/MCClient.kt @@ -7,17 +7,72 @@ package cn.rtast.fancybot.util.mcbot +import cn.rtast.fancybot.util.Logger +import org.geysermc.mcprotocollib.network.Session +import org.geysermc.mcprotocollib.network.event.session.SessionAdapter +import org.geysermc.mcprotocollib.network.packet.Packet import org.geysermc.mcprotocollib.network.tcp.TcpClientSession import org.geysermc.mcprotocollib.protocol.MinecraftProtocol +import org.geysermc.mcprotocollib.protocol.data.game.ClientCommand +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundPlayerChatPacket +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundSystemChatPacket +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerCombatKillPacket +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.player.ClientboundPlayerPositionPacket +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatCommandPacket +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundChatPacket +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.ServerboundClientCommandPacket +import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.level.ServerboundAcceptTeleportationPacket +import java.time.Instant +import java.util.BitSet class MCClient( private val serverHost: String, private val serverPort: Int, private val botName: String ) { - fun createClient(): TcpClientSession { + private lateinit var client: TcpClientSession + private val logger = Logger.getLogger() + + fun createClient() { val protocol = MinecraftProtocol(botName) - val client = TcpClientSession(serverHost, serverPort, protocol) - return client + client = TcpClientSession(serverHost, serverPort, protocol) } + + fun runBot(): MCClient { + client.addListener(object : SessionAdapter() { + override fun packetReceived(session: Session, packet: Packet) { + if (packet is ClientboundPlayerPositionPacket) { + client.send(ServerboundAcceptTeleportationPacket(packet.teleportId)); + } + if (packet is ClientboundSystemChatPacket) { + logger.trace("系统 >>> {}", packet.content) + } + if (packet is ClientboundPlayerChatPacket) { + logger.trace("{} >>> {}", packet.name, packet.content) + } + if (packet is ClientboundPlayerCombatKillPacket) { + logger.info("Bot已死亡自动复活中") + client.send(ServerboundClientCommandPacket(ClientCommand.RESPAWN)) + } + } + }) + client.connect() + return this + } + + private fun sendMessage(content: String) = + client.send(ServerboundChatPacket(content, Instant.now().toEpochMilli(), 0L, null, 0, BitSet())) + + private fun executeCommand(command: String) = client.send(ServerboundChatCommandPacket(command.substring(1))) + + + fun sendChat(text: String) { + if (text.startsWith("/")) { + this.executeCommand(text) + } else { + this.sendMessage(text) + } + } + + fun disconnect() = client.disconnect("Bye~") } \ No newline at end of file diff --git a/src/main/kotlin/cn/rtast/fancybot/util/misc/DNS.kt b/src/main/kotlin/cn/rtast/fancybot/util/misc/DNS.kt new file mode 100644 index 0000000..ab20c84 --- /dev/null +++ b/src/main/kotlin/cn/rtast/fancybot/util/misc/DNS.kt @@ -0,0 +1,25 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/10/18 + */ + + +package cn.rtast.fancybot.util.misc + +import org.xbill.DNS.Lookup +import org.xbill.DNS.SRVRecord +import org.xbill.DNS.Type + +fun String.resolveMinecraftSrv(): Pair { + val srvDomain = "_minecraft._tcp.$this" + val lookup = Lookup(srvDomain, Type.SRV) + val result = lookup.run() + if (result != null && result.isNotEmpty()) { + val srvRecord = result[0] as SRVRecord + val target = srvRecord.target.toString().trimEnd('.') + val port = srvRecord.port + return Pair(target, port) + } + return Pair(this, 25565) +} diff --git a/src/main/kotlin/cn/rtast/fancybot/util/str/String.kt b/src/main/kotlin/cn/rtast/fancybot/util/str/String.kt index de9c97c..ffe353b 100644 --- a/src/main/kotlin/cn/rtast/fancybot/util/str/String.kt +++ b/src/main/kotlin/cn/rtast/fancybot/util/str/String.kt @@ -10,7 +10,7 @@ package cn.rtast.fancybot.util.str import kotlin.random.Random fun generateRandomString(): String { - val allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_" + val allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" val nameLength = Random.nextInt(3, 17) return (1..nameLength) .map { allowedChars.random() }