feat: add query mc skin command
This commit is contained in:
5 files changed
+146
-2
No files matched your search
@@ -105,5 +105,5 @@ val commands = listOf(
|
||||
QQMusicCommand(), BullShitGenerateCommand(),
|
||||
NiuziRankCommand(), NiuziBankRankCommand(),
|
||||
ShotSelfCommand(), TenSetuCommand(),
|
||||
ReactionCommand()
|
||||
ReactionCommand(), MCSkinCommand()
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/2
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.commands.lookup
|
||||
|
||||
import cn.rtast.fancybot.entity.mc.DecodedSkin
|
||||
import cn.rtast.fancybot.entity.mc.Skin
|
||||
import cn.rtast.fancybot.entity.mc.Username
|
||||
import cn.rtast.fancybot.util.Http
|
||||
import cn.rtast.fancybot.util.isFullyTransparent
|
||||
import cn.rtast.fancybot.util.scaleImage
|
||||
import cn.rtast.fancybot.util.str.decodeToString
|
||||
import cn.rtast.fancybot.util.str.encodeToBase64
|
||||
import cn.rtast.fancybot.util.str.fromJson
|
||||
import cn.rtast.fancybot.util.toByteArray
|
||||
import cn.rtast.rob.entity.GroupMessage
|
||||
import cn.rtast.rob.util.BaseCommand
|
||||
import cn.rtast.rob.util.ob.MessageChain
|
||||
import cn.rtast.rob.util.ob.OneBotListener
|
||||
import java.awt.AlphaComposite
|
||||
import java.awt.image.BufferedImage
|
||||
import java.net.URI
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
class MCSkinCommand : BaseCommand() {
|
||||
override val commandNames = listOf("/skin")
|
||||
|
||||
private val uuidQueryUrl = "https://api.mojang.com/users/profiles/minecraft"
|
||||
private val skinUrl = "https://sessionserver.mojang.com/session/minecraft/profile"
|
||||
|
||||
private fun String.isUsername(): Boolean {
|
||||
val usernamePattern = Regex("^[a-zA-Z][a-zA-Z0-9_]{2,15}$")
|
||||
return usernamePattern.matches(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取整张皮肤中的头部部分, 如果是单层皮肤则直接截取脸部
|
||||
* 如果是双层皮肤也就将脸部作为背景图层, 将头发绘制在脸部上
|
||||
* 代码来自`YeeeesMOTD`
|
||||
* `https://github.com/RTAkland/YeeeesMOTD/blob/main/core/src/main/kotlin/cn/rtast/yeeeesmotd/utils/ImageUtil.kt`
|
||||
*/
|
||||
private fun getSkinHead(skinUrl: String): String {
|
||||
val url = URI(skinUrl).toURL()
|
||||
val image = ImageIO.read(url)
|
||||
var subImage = image.getSubimage(8, 8, 8, 8)
|
||||
|
||||
if (subImage.isFullyTransparent()) {
|
||||
subImage = image.getSubimage(40, 8, 8, 8)
|
||||
} else {
|
||||
val hairLayer = image.getSubimage(40, 8, 8, 8)
|
||||
val combined = BufferedImage(subImage.width, subImage.height, BufferedImage.TYPE_INT_ARGB)
|
||||
val g2d = combined.createGraphics()
|
||||
g2d.drawImage(subImage, 0, 0, null)
|
||||
g2d.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)
|
||||
g2d.drawImage(hairLayer, 0, 0, null)
|
||||
g2d.dispose()
|
||||
subImage = combined
|
||||
}
|
||||
val zoom = subImage.scaleImage(128 to 128)
|
||||
return zoom.toByteArray().encodeToBase64()
|
||||
}
|
||||
|
||||
override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List<String>) {
|
||||
if (args.isEmpty()) {
|
||||
message.reply("发送`/skin <uuid/游戏名称>`即可查询他的皮肤~ (仅限Java版)")
|
||||
return
|
||||
}
|
||||
try {
|
||||
val nameOrUUID = args.first().trim()
|
||||
val uuid = if (nameOrUUID.isUsername()) Http.get<Username>("$uuidQueryUrl/$nameOrUUID").id
|
||||
else nameOrUUID
|
||||
val skinUrl = Http.get<Skin>("$skinUrl/$uuid")
|
||||
.properties.first().value.decodeToString().fromJson<DecodedSkin>()
|
||||
.textures.skin.url
|
||||
val headBase64 = this.getSkinHead(skinUrl)
|
||||
val msg = MessageChain.Builder()
|
||||
.addImage(headBase64, true)
|
||||
.addText(skinUrl)
|
||||
.build()
|
||||
message.reply(msg)
|
||||
} catch (_: Exception) {
|
||||
message.reply("输入错误, 检查一下是否输入正确吧~")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/2
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Skin(
|
||||
val properties: List<Property>,
|
||||
) {
|
||||
data class Property(
|
||||
val value: String,
|
||||
)
|
||||
}
|
||||
|
||||
data class DecodedSkin(
|
||||
val textures: Texture,
|
||||
) {
|
||||
data class Texture(
|
||||
@SerializedName("SKIN")
|
||||
val skin: SKIN,
|
||||
)
|
||||
|
||||
data class SKIN(
|
||||
val url: String,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright © 2024 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2024/10/2
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.fancybot.entity.mc
|
||||
|
||||
data class Username(val id: String)
|
||||
@@ -59,7 +59,7 @@ fun Graphics2D.drawCircularImage(
|
||||
maxHeight: Double,
|
||||
) {
|
||||
val (targetWidth, targetHeight) = image.getScaledWidth(maxWidth, maxHeight)
|
||||
val circle = Ellipse2D.Double(x.toDouble(), y.toDouble(), targetWidth.toDouble(), targetHeight.toDouble());
|
||||
val circle = Ellipse2D.Double(x.toDouble(), y.toDouble(), targetWidth.toDouble(), targetHeight.toDouble())
|
||||
this.clip = circle
|
||||
this.drawImage(image, x, y, targetWidth, targetHeight, null)
|
||||
this.clip = null
|
||||
@@ -115,3 +115,17 @@ fun Graphics2D.drawString(text: String, x: Int, y: Int, maxWidth: Int) {
|
||||
this.drawString(line.toString(), x, curY)
|
||||
}
|
||||
}
|
||||
|
||||
fun BufferedImage.isFullyTransparent(): Boolean {
|
||||
val width = this.width
|
||||
val height = this.height
|
||||
for (y in 0 until height) {
|
||||
for (x in 0 until width) {
|
||||
val pixel = this.getRGB(x, y)
|
||||
if ((pixel shr 24) != 0x00) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user