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(),
|
QQMusicCommand(), BullShitGenerateCommand(),
|
||||||
NiuziRankCommand(), NiuziBankRankCommand(),
|
NiuziRankCommand(), NiuziBankRankCommand(),
|
||||||
ShotSelfCommand(), TenSetuCommand(),
|
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,
|
maxHeight: Double,
|
||||||
) {
|
) {
|
||||||
val (targetWidth, targetHeight) = image.getScaledWidth(maxWidth, maxHeight)
|
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.clip = circle
|
||||||
this.drawImage(image, x, y, targetWidth, targetHeight, null)
|
this.drawImage(image, x, y, targetWidth, targetHeight, null)
|
||||||
this.clip = 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)
|
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