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

+2 -1
View File
@@ -26,7 +26,8 @@ val DEFAULT_CONFIG = Config(
accessToken = "114514",
port = 6760,
listeningGroups = listOf(114514, 1919810),
qweatherKey = "114514"
qweatherKey = "114514",
githubKey = "1919810"
)
val ADMINS = listOf(
@@ -9,6 +9,7 @@ package cn.rtast.fancybot
import cn.rtast.fancybot.commands.*
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.ReverseGIFCommand
import cn.rtast.fancybot.entity.bili.CardShare
@@ -38,6 +39,14 @@ class FancyBot : OBMessage {
val msg = message.rawMessage
val groupId = message.groupId
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") ||
message.rawMessage.startsWith("https://www.bilibili.com") ||
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.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
@@ -18,8 +20,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import java.awt.Color
import java.awt.Font
import java.awt.Graphics2D
import java.awt.geom.RoundRectangle2D
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
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(
title: String,
author: String,
@@ -119,35 +90,24 @@ object BVParseCommand {
g2d.drawString(reply, 300, 290)
g2d.drawString(share, 300, 200)
// draw video title
val maxWidth = 500
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
val truncatedText = setTruncat(title, g2d)
g2d.font = titleFont
g2d.drawString(truncatedText, 20, 70)
// draw author name
g2d.drawString(author, 40, 580)
// draw icons
this.drawImage(favoriteIcon, g2d, 230, 340, 50.0, 50.0, false)
this.drawImage(replyIcon, g2d, 230, 250, 50.0, 50.0, false)
this.drawImage(shareIcon, g2d, 230, 160, 50.0, 50.0, false)
this.drawImage(viewIcon, g2d, 40, 340, 50.0, 50.0, false)
this.drawImage(coinIcon, g2d, 40, 250, 50.0, 50.0, false)
this.drawImage(likeIcon, g2d, 40, 160, 50.0, 50.0, false)
g2d.drawCustomImage(favoriteIcon, 230, 340, 50.0, 50.0, false)
g2d.drawCustomImage(replyIcon, 230, 250, 50.0, 50.0, false)
g2d.drawCustomImage(shareIcon, 230, 160, 50.0, 50.0, false)
g2d.drawCustomImage(viewIcon, 40, 340, 50.0, 50.0, false)
g2d.drawCustomImage(coinIcon, 40, 250, 50.0, 50.0, false)
g2d.drawCustomImage(likeIcon, 40, 160, 50.0, 50.0, false)
// 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
this.drawImage(faceImage, g2d, 40, 480, 60.0, 60.0, true)
g2d.drawCustomImage(faceImage, 40, 480, 60.0, 60.0, true)
// 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()
val byteArrayOutputStream = 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 port: Int,
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 listeningGroups get() = this.read<Config>().listeningGroups
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
}