feat: support parse github repo(project)

This commit is contained in:
2024-09-12 15:43:44 +08:00
parent a537850560
commit cc5d288588
15 files changed
+269 -55

No files matched your search

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+2 -1
View File
@@ -26,7 +26,8 @@ val DEFAULT_CONFIG = Config(
accessToken = "114514", accessToken = "114514",
port = 6760, port = 6760,
listeningGroups = listOf(114514, 1919810), listeningGroups = listOf(114514, 1919810),
qweatherKey = "114514" qweatherKey = "114514",
githubKey = "1919810"
) )
val ADMINS = listOf( val ADMINS = listOf(
@@ -9,6 +9,7 @@ package cn.rtast.fancybot
import cn.rtast.fancybot.commands.* import cn.rtast.fancybot.commands.*
import cn.rtast.fancybot.commands.misc.BVParseCommand import cn.rtast.fancybot.commands.misc.BVParseCommand
import cn.rtast.fancybot.commands.misc.GitHubParseCommand
import cn.rtast.fancybot.commands.misc.ImageURLCommand import cn.rtast.fancybot.commands.misc.ImageURLCommand
import cn.rtast.fancybot.commands.misc.ReverseGIFCommand import cn.rtast.fancybot.commands.misc.ReverseGIFCommand
import cn.rtast.fancybot.entity.bili.CardShare import cn.rtast.fancybot.entity.bili.CardShare
@@ -38,6 +39,14 @@ class FancyBot : OBMessage {
val msg = message.rawMessage val msg = message.rawMessage
val groupId = message.groupId val groupId = message.groupId
println("$sender($senderId: $groupId): $msg") println("$sender($senderId: $groupId): $msg")
if (message.rawMessage.startsWith("https://github.com/") ||
message.rawMessage.startsWith("git@github.com:") ||
message.rawMessage.split("/").size == 2
) {
GitHubParseCommand.parse(this, message)
}
if (message.rawMessage.startsWith("BV") || if (message.rawMessage.startsWith("BV") ||
message.rawMessage.startsWith("https://www.bilibili.com") || message.rawMessage.startsWith("https://www.bilibili.com") ||
message.rawMessage.contains("https://b23.tv/") || message.rawMessage.contains("https://b23.tv/") ||
@@ -10,7 +10,9 @@ package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.entity.bili.BVID import cn.rtast.fancybot.entity.bili.BVID
import cn.rtast.fancybot.util.Http import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Resources import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.str.encodeToBase64 import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.setTruncat
import cn.rtast.rob.entity.GroupMessage import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.ob.MessageChain import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OBMessage import cn.rtast.rob.util.ob.OBMessage
@@ -18,8 +20,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import java.awt.Color import java.awt.Color
import java.awt.Font import java.awt.Font
import java.awt.Graphics2D
import java.awt.geom.RoundRectangle2D
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.net.URI import java.net.URI
@@ -59,35 +59,6 @@ object BVParseCommand {
} }
} }
private fun drawImage(
image: BufferedImage,
g2d: Graphics2D,
x: Int,
y: Int,
maxWidth: Double,
maxHeight: Double,
clip: Boolean
) {
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()
if (clip) {
g2d.clip = RoundRectangle2D.Float(
x.toFloat(),
y.toFloat(),
targetWidth.toFloat(),
targetHeight.toFloat(),
30.toFloat(),
30.toFloat()
)
}
g2d.drawImage(image, x, y, targetWidth, targetHeight, null)
}
private fun createResponseImage( private fun createResponseImage(
title: String, title: String,
author: String, author: String,
@@ -119,35 +90,24 @@ object BVParseCommand {
g2d.drawString(reply, 300, 290) g2d.drawString(reply, 300, 290)
g2d.drawString(share, 300, 200) g2d.drawString(share, 300, 200)
// draw video title // draw video title
val maxWidth = 500 val truncatedText = setTruncat(title, g2d)
val fontMetrics = g2d.fontMetrics
val textWidth = fontMetrics.stringWidth(title)
val truncatedText = if (textWidth > maxWidth) {
val ellipsisWidth = fontMetrics.stringWidth("...")
val width = maxWidth - ellipsisWidth
var endIndex = title.length
while (fontMetrics.stringWidth(title.substring(0, endIndex)) > width && endIndex > 0) {
endIndex--
}
title.substring(0, endIndex) + "..."
} else title
g2d.font = titleFont g2d.font = titleFont
g2d.drawString(truncatedText, 20, 70) g2d.drawString(truncatedText, 20, 70)
// draw author name // draw author name
g2d.drawString(author, 40, 580) g2d.drawString(author, 40, 580)
// draw icons // draw icons
this.drawImage(favoriteIcon, g2d, 230, 340, 50.0, 50.0, false) g2d.drawCustomImage(favoriteIcon, 230, 340, 50.0, 50.0, false)
this.drawImage(replyIcon, g2d, 230, 250, 50.0, 50.0, false) g2d.drawCustomImage(replyIcon, 230, 250, 50.0, 50.0, false)
this.drawImage(shareIcon, g2d, 230, 160, 50.0, 50.0, false) g2d.drawCustomImage(shareIcon, 230, 160, 50.0, 50.0, false)
this.drawImage(viewIcon, g2d, 40, 340, 50.0, 50.0, false) g2d.drawCustomImage(viewIcon, 40, 340, 50.0, 50.0, false)
this.drawImage(coinIcon, g2d, 40, 250, 50.0, 50.0, false) g2d.drawCustomImage(coinIcon, 40, 250, 50.0, 50.0, false)
this.drawImage(likeIcon, g2d, 40, 160, 50.0, 50.0, false) g2d.drawCustomImage(likeIcon, 40, 160, 50.0, 50.0, false)
// draw 22 logo // draw 22 logo
this.drawImage(twoTwoLogo, g2d, 800, 450, 90.0, 160.0, false) g2d.drawCustomImage(twoTwoLogo, 800, 450, 90.0, 160.0, false)
// draw author face // draw author face
this.drawImage(faceImage, g2d, 40, 480, 60.0, 60.0, true) g2d.drawCustomImage(faceImage, 40, 480, 60.0, 60.0, true)
// draw cover image // draw cover image
this.drawImage(coverImage, g2d, 430, 140, 600.0, 300.0, true) g2d.drawCustomImage(coverImage, 430, 140, 600.0, 300.0, true)
g2d.dispose() g2d.dispose()
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(backgroundImage, "png", byteArrayOutputStream) ImageIO.write(backgroundImage, "png", byteArrayOutputStream)
@@ -0,0 +1,125 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/12
*/
package cn.rtast.fancybot.commands.misc
import cn.rtast.fancybot.configManager
import cn.rtast.fancybot.entity.github.RepoInfo
import cn.rtast.fancybot.util.Http
import cn.rtast.fancybot.util.Resources
import cn.rtast.fancybot.util.drawCustomImage
import cn.rtast.fancybot.util.str.encodeToBase64
import cn.rtast.fancybot.util.str.setTruncat
import cn.rtast.rob.entity.GroupMessage
import cn.rtast.rob.util.ob.MessageChain
import cn.rtast.rob.util.ob.OBMessage
import java.awt.Color
import java.awt.Font
import java.awt.RenderingHints
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.net.URI
import javax.imageio.ImageIO
object GitHubParseCommand {
private const val REPO_INFO_API = "https://api.github.com/repos"
private const val CANVAS_WIDTH = 1620
private const val CANVAS_HEIGHT = 1000
private val githubLogo = ImageIO.read(Resources.loadFromResources("github/favicon.png"))
private val starIcon = ImageIO.read(Resources.loadFromResources("github/star.png"))
private val issueIcon = ImageIO.read(Resources.loadFromResources("github/issue.png"))
private val forkIcon = ImageIO.read(Resources.loadFromResources("github/fork.png"))
private val customFont = Font("Serif", Font.ITALIC, 50).deriveFont(Font.ITALIC).deriveFont(Font.BOLD)
private val titleCustomFont = Font("Serif", Font.ITALIC, 75).deriveFont(Font.ITALIC).deriveFont(Font.BOLD)
private val descriptionCustomFont = Font("Serif", Font.ITALIC, 50).deriveFont(Font.ITALIC).deriveFont(Font.BOLD)
private val languageColors = mapOf(
"Kotlin" to Color(169, 123, 255),
"Java" to Color(176, 114, 25),
"Python" to Color(53, 114, 165),
"JavaScript" to Color(241, 224, 90),
"C++" to Color(243, 75, 125),
"Go" to Color(0, 173, 216),
"Ruby" to Color(112, 21, 22),
"PHP" to Color(79, 93, 149),
"TypeScript" to Color(43, 116, 137),
"Swift" to Color(240, 81, 56),
"Unknown" to Color(211, 211, 211)
)
private fun Int.formatNumber(): String {
return when {
this >= 1_000_000 -> String.format("%.1fM", this / 1_000_000.0)
this >= 10_000 -> String.format("%.1fk", this / 1_000.0)
this >= 1_000 -> String.format("%dk", this / 1_000)
else -> this.toString()
}
}
private fun getLanguageColor(language: String): Color {
return languageColors[language] ?: languageColors["Unknown"]!!
}
private fun getRepoStat(username: String, repo: String): RepoInfo {
return Http.get<RepoInfo>(
"$REPO_INFO_API/$username/$repo",
headers = mapOf("Authorization" to "Bearer ${configManager.githubKey}")
)
}
private fun createImage(repoStat: RepoInfo): String {
val canvas = BufferedImage(CANVAS_WIDTH, CANVAS_HEIGHT, BufferedImage.TYPE_INT_RGB)
val g2d = canvas.createGraphics()
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2d.font = customFont
g2d.color = Color.WHITE
g2d.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
g2d.color = this.getLanguageColor(repoStat.language)
g2d.fillRect(0, CANVAS_HEIGHT - 20, CANVAS_WIDTH, 20)
g2d.drawCustomImage(githubLogo, 1300, 780, 100.0, 100.0, false) // draw github logo
g2d.drawCustomImage(starIcon, 100, 730, 70.0, 70.0, false) // draw star icon
g2d.drawCustomImage(issueIcon, 500, 730, 70.0, 70.0, false) // draw issue icon
g2d.drawCustomImage(forkIcon, 900, 730, 70.0, 70.0, false) // draw fork icon
g2d.color = Color(60, 60, 60)
g2d.drawString("Stars", 160, 840)
g2d.drawString("Issues", 560, 840)
g2d.drawString("Forks", 960, 840)
g2d.drawString(repoStat.starsCount.formatNumber(), 200, 780)
g2d.drawString(repoStat.openIssueCount.formatNumber(), 600, 780)
g2d.drawString(repoStat.forksCount.formatNumber(), 1000, 780)
g2d.font = titleCustomFont
val truncatedFullNameText = setTruncat(repoStat.fullName, g2d, 1000)
g2d.drawString(truncatedFullNameText, 80, 280)
g2d.font = descriptionCustomFont
val truncatedDescription = setTruncat(repoStat.description, g2d, 1000)
g2d.drawString(truncatedDescription, 20, 450)
val avatarImage = ImageIO.read(URI(repoStat.owner.avatarUrl).toURL())
g2d.drawCustomImage(avatarImage, 1200, 300, 300.0, 300.0, true)
val byteArrayOutputStream = ByteArrayOutputStream()
ImageIO.write(canvas, "png", byteArrayOutputStream)
val imageBytes = byteArrayOutputStream.toByteArray()
return imageBytes.encodeToBase64()
}
suspend fun parse(listener: OBMessage, message: GroupMessage) {
val path = message.rawMessage
.replace(".git", "")
.replace("https://github.com/", "")
.replace("git@github.com:", "")
.split("/")
val user = path.first()
val repo = path.last()
val repoStat = this.getRepoStat(user, repo)
val image = this.createImage(repoStat)
val msg = MessageChain.Builder()
.addReply(message.messageId)
.addImage(image, true)
.build()
listener.sendGroupMessage(message.groupId, msg)
}
}
@@ -16,5 +16,6 @@ data class Config(
val accessToken: String, val accessToken: String,
val port: Int, val port: Int,
val listeningGroups: List<Long>, val listeningGroups: List<Long>,
val qweatherKey: String val qweatherKey: String,
val githubKey: String,
) )
@@ -0,0 +1,29 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/12
*/
package cn.rtast.fancybot.entity.github
import com.google.gson.annotations.SerializedName
data class RepoInfo(
val owner: Owner,
@SerializedName("full_name")
val fullName: String,
val description: String,
@SerializedName("forks_count")
val forksCount: Int,
val language: String,
@SerializedName("stargazers_count")
val starsCount: Int,
@SerializedName("open_issues_count")
val openIssueCount: Int
) {
data class Owner(
@SerializedName("avatar_url")
val avatarUrl: String,
)
}
@@ -0,0 +1,64 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/12
*/
package cn.rtast.fancybot.util
import java.awt.Graphics2D
import java.awt.geom.RoundRectangle2D
import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import javax.imageio.ImageIO
fun Graphics2D.drawCustomImage(
image: BufferedImage,
x: Int,
y: Int,
maxWidth: Double,
maxHeight: Double,
clip: Boolean
) {
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()
if (clip) {
this.clip = RoundRectangle2D.Float(
x.toFloat(),
y.toFloat(),
targetWidth.toFloat(),
targetHeight.toFloat(),
30.toFloat(),
30.toFloat()
)
}
this.drawImage(image, x, y, targetWidth, targetHeight, null)
}
fun BufferedImage.scaleImage(size: Pair<Int, Int>): BufferedImage {
val scaledImage = BufferedImage(size.first, size.second, this.type)
val graphics2d = scaledImage.createGraphics()
graphics2d.drawImage(this, 0, 0, size.first, size.second, null)
graphics2d.dispose()
return scaledImage
}
fun ByteArray.toBufferedImage(): BufferedImage {
ByteArrayInputStream(this).use { inputStream ->
return ImageIO.read(inputStream)
}
}
fun BufferedImage.toByteArray(): ByteArray {
ByteArrayOutputStream().use { outputStream ->
ImageIO.write(this, "png", outputStream)
return outputStream.toByteArray()
}
}
@@ -18,4 +18,5 @@ class ConfigManager : JsonFileHandler<Config>("config.json") {
val wsType get() = this.read<Config>().wsType val wsType get() = this.read<Config>().wsType
val listeningGroups get() = this.read<Config>().listeningGroups val listeningGroups get() = this.read<Config>().listeningGroups
val qweatherKey get() = this.read<Config>().qweatherKey val qweatherKey get() = this.read<Config>().qweatherKey
val githubKey get() = this.read<Config>().githubKey
} }
@@ -0,0 +1,24 @@
/*
* Copyright © 2024 RTAkland
* Author: RTAkland
* Date: 2024/9/12
*/
package cn.rtast.fancybot.util.str
import java.awt.Graphics2D
fun setTruncat(origin: String, g2d: Graphics2D, maxWidth: Int = 500): String {
val fontMetrics = g2d.fontMetrics
val textWidth = fontMetrics.stringWidth(origin)
return if (textWidth > maxWidth) {
val ellipsisWidth = fontMetrics.stringWidth("...")
val width = maxWidth - ellipsisWidth
var endIndex = origin.length
while (fontMetrics.stringWidth(origin.substring(0, endIndex)) > width && endIndex > 0) {
endIndex--
}
origin.substring(0, endIndex) + "..."
} else origin
}
+1 -1
View File
@@ -9,5 +9,5 @@
1919810 1919810
], ],
"qweatherKey": "114514", "qweatherKey": "114514",
"qweatherPlan": "Free" "githubKey": "1919810"
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB