diff --git a/src/main/kotlin/cn/rtast/fancybot/FancyBot.kt b/src/main/kotlin/cn/rtast/fancybot/FancyBot.kt index e264a7d..5430669 100644 --- a/src/main/kotlin/cn/rtast/fancybot/FancyBot.kt +++ b/src/main/kotlin/cn/rtast/fancybot/FancyBot.kt @@ -27,6 +27,7 @@ import cn.rtast.fancybot.commands.misc.AntiRevokeCommand import cn.rtast.fancybot.commands.misc.CompilerCommand import cn.rtast.fancybot.commands.misc.FKXQSCommand import cn.rtast.fancybot.commands.misc.HitokotoCommand +import cn.rtast.fancybot.commands.misc.JueCommand import cn.rtast.fancybot.commands.misc.LikeMeCommand import cn.rtast.fancybot.commands.misc.RUACommand import cn.rtast.fancybot.commands.misc.RemakeCommand @@ -63,7 +64,6 @@ import cn.rtast.rob.entity.FileEvent import cn.rtast.rob.entity.GetMessage import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupRevokeMessage -import cn.rtast.rob.entity.PrivateMessage import cn.rtast.rob.enums.ArrayMessageType import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.OneBotListener @@ -86,8 +86,10 @@ class FancyBot : OneBotListener { val messageId = message.messageId println("$sender($senderId: $groupId >>> $messageId): $msg") - val calculateResult = CalculateCommand.parse(message.rawMessage) - calculateResult?.let { message.reply(calculateResult) } + if (message.rawMessage.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) { + val calculateResult = CalculateCommand.parse(message.rawMessage) + calculateResult?.let { message.reply(calculateResult) } + } coroutineScope.launch { message.message.forEach { @@ -220,7 +222,8 @@ val commands = listOf( MusicPlayUrlCommand(), DomainPriceCommand(), SendMailCommand(), ZiBiCommand(), UnsetZiBiCommand(), WikipediaCommand(), - AsciiArtCommand(), StatusCommand() + AsciiArtCommand(), StatusCommand(), + JueCommand() ) val START_UP_TIME = Instant.now().epochSecond diff --git a/src/main/kotlin/cn/rtast/fancybot/commands/misc/JueCommand.kt b/src/main/kotlin/cn/rtast/fancybot/commands/misc/JueCommand.kt new file mode 100644 index 0000000..151b9c8 --- /dev/null +++ b/src/main/kotlin/cn/rtast/fancybot/commands/misc/JueCommand.kt @@ -0,0 +1,65 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/9/21 + */ + + +package cn.rtast.fancybot.commands.misc + +import cn.rtast.fancybot.util.Resources +import cn.rtast.fancybot.util.drawCircularImage +import cn.rtast.fancybot.util.makeGif +import cn.rtast.fancybot.util.str.encodeToBase64 +import cn.rtast.fancybot.util.toBufferedImage +import cn.rtast.rob.entity.GroupMessage +import cn.rtast.rob.enums.ArrayMessageType +import cn.rtast.rob.util.BaseCommand +import cn.rtast.rob.util.ob.MessageChain +import cn.rtast.rob.util.ob.OneBotListener +import com.madgag.gif.fmsware.GifDecoder +import java.awt.image.BufferedImage +import java.net.URI + +class JueCommand : BaseCommand() { + override val commandNames = listOf("撅") + + private val avatarUrl = "https://q1.qlogo.cn/g?b=qq&nk=#{}&s=640" + + private fun createJueGIF(source: String, target: String): String { + val baseGIF = Resources.loadFromResources("misc/jue.gif") + val sourceAvatar = URI(avatarUrl.replace("#{}", source)).toURL().readBytes().toBufferedImage() + val targetAvatar = URI(avatarUrl.replace("#{}", target)).toURL().readBytes().toBufferedImage() + val decoder = GifDecoder() + decoder.read(baseGIF) + val frames = mutableListOf() + + fun drawFrame(index: Int, sourceX: Int, sourceY: Int, targetX: Int, targetY: Int) { + val frame = decoder.getFrame(index) + val g2d = frame.createGraphics() + g2d.drawCircularImage(sourceAvatar, sourceX, sourceY, 126.0, 126.0) + g2d.drawCircularImage(targetAvatar, targetX, targetY, 126.0, 126.0) + g2d.dispose() + frames.add(frame) + } + + (0 until decoder.frameCount).forEach { + when (it) { + 0 -> drawFrame(it, 115, -12, 3, 175) + 1 -> drawFrame(it, 109, 2, 10, 170) + 2 -> drawFrame(it, 130, -13, 5, 154) + } + } + val gifBytes = decoder.makeGif(frames) + val gif = gifBytes.encodeToBase64() + return gif + } + + override suspend fun executeGroup(listener: OneBotListener, message: GroupMessage, args: List) { + val target = message.message.find { it.type == ArrayMessageType.at }?.data?.qq.toString() + val source = message.sender.userId.toString() + val gif = this.createJueGIF(source, target) + val msg = MessageChain.Builder().addImage(gif, true).build() + listener.sendGroupMessage(message.groupId, msg) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cn/rtast/fancybot/commands/parse/ReverseGIFCommand.kt b/src/main/kotlin/cn/rtast/fancybot/commands/parse/ReverseGIFCommand.kt index e6d51a5..374e8d8 100644 --- a/src/main/kotlin/cn/rtast/fancybot/commands/parse/ReverseGIFCommand.kt +++ b/src/main/kotlin/cn/rtast/fancybot/commands/parse/ReverseGIFCommand.kt @@ -7,6 +7,7 @@ package cn.rtast.fancybot.commands.parse +import cn.rtast.fancybot.util.makeGif import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.rob.entity.GetMessage import cn.rtast.rob.enums.ArrayMessageType @@ -24,16 +25,7 @@ object ReverseGIFCommand { val decoder = GifDecoder() decoder.read(gifStream) val frames = (0 until decoder.frameCount).map { decoder.getFrame(it) }.reversed() - val byteArrayOutputStream = ByteArrayOutputStream() - val encoder = AnimatedGifEncoder() - encoder.start(byteArrayOutputStream) - encoder.setRepeat(0) - for (i in frames.indices) { - encoder.setDelay(decoder.getDelay(decoder.frameCount - i - 1)) - encoder.addFrame(frames[i]) - } - encoder.finish() - val gifBytes = byteArrayOutputStream.toByteArray() + val gifBytes = decoder.makeGif(frames) return gifBytes.encodeToBase64() } diff --git a/src/main/kotlin/cn/rtast/fancybot/util/GIF.kt b/src/main/kotlin/cn/rtast/fancybot/util/GIF.kt new file mode 100644 index 0000000..efdbaea --- /dev/null +++ b/src/main/kotlin/cn/rtast/fancybot/util/GIF.kt @@ -0,0 +1,26 @@ +/* + * Copyright © 2024 RTAkland + * Author: RTAkland + * Date: 2024/9/21 + */ + + +package cn.rtast.fancybot.util + +import com.madgag.gif.fmsware.AnimatedGifEncoder +import com.madgag.gif.fmsware.GifDecoder +import java.awt.image.BufferedImage +import java.io.ByteArrayOutputStream + +fun GifDecoder.makeGif(frames: List): ByteArray { + val byteArrayOutputStream = ByteArrayOutputStream() + val encoder = AnimatedGifEncoder() + encoder.start(byteArrayOutputStream) + encoder.setRepeat(0) + for (i in frames.indices) { + encoder.setDelay(this.getDelay(this.frameCount - i - 1)) + encoder.addFrame(frames[i]) + } + encoder.finish() + return byteArrayOutputStream.toByteArray() +} \ No newline at end of file diff --git a/src/main/kotlin/cn/rtast/fancybot/util/Image.kt b/src/main/kotlin/cn/rtast/fancybot/util/Image.kt index 9a5b461..cdb8af8 100644 --- a/src/main/kotlin/cn/rtast/fancybot/util/Image.kt +++ b/src/main/kotlin/cn/rtast/fancybot/util/Image.kt @@ -9,12 +9,25 @@ package cn.rtast.fancybot.util import cn.rtast.fancybot.configManager import java.awt.Graphics2D +import java.awt.geom.Ellipse2D import java.awt.geom.RoundRectangle2D import java.awt.image.BufferedImage import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import javax.imageio.ImageIO + +private fun BufferedImage.getScaledWidth(maxWidth: Double, maxHeight: Double): Pair { + val originalWidth = this.getWidth(null) + val originalHeight = this.getHeight(null) + val widthScale = maxWidth / originalWidth + val heightScale = maxHeight / originalHeight + val scale = minOf(widthScale, heightScale) + val targetWidth = (originalWidth * scale).toInt() + val targetHeight = (originalHeight * scale).toInt() + return targetWidth to targetHeight +} + fun Graphics2D.drawCustomImage( image: BufferedImage, x: Int, @@ -23,13 +36,7 @@ fun Graphics2D.drawCustomImage( maxHeight: Double, clip: Boolean = false ) { - val originalWidth = image.getWidth(null) - val originalHeight = image.getHeight(null) - val widthScale = maxWidth / originalWidth - val heightScale = maxHeight / originalHeight - val scale = minOf(widthScale, heightScale) - val targetWidth = (originalWidth * scale).toInt() - val targetHeight = (originalHeight * scale).toInt() + val (targetWidth, targetHeight) = image.getScaledWidth(maxWidth, maxHeight) if (clip) { this.clip = RoundRectangle2D.Float( x.toFloat(), @@ -41,6 +48,21 @@ fun Graphics2D.drawCustomImage( ) } this.drawImage(image, x, y, targetWidth, targetHeight, null) + this.clip = null +} + +fun Graphics2D.drawCircularImage( + image: BufferedImage, + x: Int, + y: Int, + maxWidth: Double, + maxHeight: Double, +) { + val (targetWidth, targetHeight) = image.getScaledWidth(maxWidth, maxHeight) + 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 } fun BufferedImage.scaleImage(size: Pair): BufferedImage { diff --git a/src/main/resources/misc/jue.gif b/src/main/resources/misc/jue.gif new file mode 100644 index 0000000..26e66d3 Binary files /dev/null and b/src/main/resources/misc/jue.gif differ