feat: support parse douyin video
This commit is contained in:
3 files changed
+181
No files matched your search
@@ -66,6 +66,7 @@ class FancyBot : OneBotListener {
|
|||||||
ScanQRCodeCommand.callback(message)
|
ScanQRCodeCommand.callback(message)
|
||||||
BiliVideoParseCommand.parse(this, message)
|
BiliVideoParseCommand.parse(this, message)
|
||||||
BiliUserParseCommand.parse(this, message)
|
BiliUserParseCommand.parse(this, message)
|
||||||
|
DouyinVideoParseCommand.parse(message)
|
||||||
|
|
||||||
if (message.rawMessage.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) {
|
if (message.rawMessage.toList().any { it in arrayListOf('*', '-', '/', '+', '=') }) {
|
||||||
val calculateResult = CalculateCommand.parse(message.rawMessage)
|
val calculateResult = CalculateCommand.parse(message.rawMessage)
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2024/10/13
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.fancybot.commands.parse
|
||||||
|
|
||||||
|
import cn.rtast.fancybot.commands.misc.ShortLinkCommand.Companion.makeShortLink
|
||||||
|
import cn.rtast.fancybot.entity.douyin.DouyinVideo
|
||||||
|
import cn.rtast.fancybot.util.Http
|
||||||
|
import cn.rtast.fancybot.util.misc.toBufferedImage
|
||||||
|
import cn.rtast.fancybot.util.misc.toByteArray
|
||||||
|
import cn.rtast.fancybot.util.misc.toURL
|
||||||
|
import cn.rtast.fancybot.util.str.encodeToBase64
|
||||||
|
import cn.rtast.fancybot.util.str.formatNumber
|
||||||
|
import cn.rtast.fancybot.util.str.proxy
|
||||||
|
import cn.rtast.rob.entity.GroupMessage
|
||||||
|
import cn.rtast.rob.util.ob.MessageChain
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.awt.Color
|
||||||
|
import java.awt.Font
|
||||||
|
import java.awt.RenderingHints
|
||||||
|
import java.awt.geom.RoundRectangle2D
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
|
||||||
|
object DouyinVideoParseCommand {
|
||||||
|
|
||||||
|
private val tempHttpClient = OkHttpClient.Builder()
|
||||||
|
.followRedirects(true)
|
||||||
|
.followSslRedirects(true)
|
||||||
|
.build()
|
||||||
|
private val shortLinkRegex = Regex("https://v\\.douyin\\.com/[a-zA-Z0-9]+/")
|
||||||
|
private val singleVideoRegex = Regex("https://www\\.douyin\\.com/video/([0-9]+)")
|
||||||
|
private val douyinApiUrl = "https://douyin.wtf/api/douyin/web/fetch_one_video".proxy
|
||||||
|
private const val IMAGE_WIDTH = 1200
|
||||||
|
private const val IMAGE_HEIGHT = 800
|
||||||
|
|
||||||
|
private fun extractVideoId(origin: String) = singleVideoRegex.find(origin)?.groupValues?.get(1)!!
|
||||||
|
private fun createDouyinVideoCard(video: DouyinVideo): String {
|
||||||
|
val card = BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB)
|
||||||
|
val g2d = card.createGraphics()
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||||
|
g2d.color = Color(245, 245, 245)
|
||||||
|
g2d.fillRect(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT)
|
||||||
|
g2d.color = Color(230, 230, 230)
|
||||||
|
g2d.fill(RoundRectangle2D.Float(20f, 20f, (IMAGE_WIDTH - 40).toFloat(), (IMAGE_HEIGHT - 40).toFloat(), 50f, 50f))
|
||||||
|
g2d.color = Color.BLACK
|
||||||
|
g2d.font = Font("Serif", Font.BOLD, 48)
|
||||||
|
g2d.drawString(video.data.awemeDetail.author.nickname, 60, 100)
|
||||||
|
g2d.font = Font("Serif", Font.PLAIN, 32)
|
||||||
|
g2d.drawString(video.data.awemeDetail.desc, 60, 160)
|
||||||
|
val avatarImage = video.data.awemeDetail.music.avatarLarge.urlList.first().toURL().readBytes().toBufferedImage()
|
||||||
|
g2d.drawImage(avatarImage, 60, 200, 160, 160, null)
|
||||||
|
val coverImage = video.data.awemeDetail.video.cover.urlList.first().toURL().readBytes().toBufferedImage()
|
||||||
|
g2d.drawImage(coverImage, 300, 200, 600, 360, null)
|
||||||
|
g2d.color = Color.DARK_GRAY
|
||||||
|
g2d.font = Font("Serif", Font.PLAIN, 28)
|
||||||
|
g2d.drawString("点赞: ${video.data.awemeDetail.statistics.diggCount.formatNumber()}", 60, 600)
|
||||||
|
g2d.drawString("播放: ${video.data.awemeDetail.statistics.playCount.formatNumber()}", 60, 640)
|
||||||
|
g2d.drawString("评论: ${video.data.awemeDetail.statistics.commentCount.formatNumber()}", 60, 680)
|
||||||
|
g2d.drawString("收藏: ${video.data.awemeDetail.statistics.collectCount.formatNumber()}", 60, 720)
|
||||||
|
g2d.drawString("分享: ${video.data.awemeDetail.statistics.shareCount.formatNumber()}", 60, 760)
|
||||||
|
g2d.dispose()
|
||||||
|
return card.toByteArray().encodeToBase64()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun parse(message: GroupMessage) {
|
||||||
|
val id = if (shortLinkRegex.find(message.rawMessage) != null) {
|
||||||
|
val shortUrl = shortLinkRegex.find(message.rawMessage)?.value ?: ""
|
||||||
|
val request = Request.Builder().url(shortUrl).build()
|
||||||
|
val finalUrl = tempHttpClient.newCall(request).execute().request.url.toString()
|
||||||
|
extractVideoId(finalUrl)
|
||||||
|
} else if (singleVideoRegex.find(message.rawMessage) != null) extractVideoId(message.rawMessage) else ""
|
||||||
|
if (id.isNotBlank()) {
|
||||||
|
val response = Http.get<DouyinVideo>(
|
||||||
|
douyinApiUrl,
|
||||||
|
mapOf("aweme_id" to id)
|
||||||
|
)
|
||||||
|
val videoPlayUrlShortUrl = response.data.awemeDetail.video.bitRate
|
||||||
|
.random().playAddr.urlList.random().makeShortLink()
|
||||||
|
val shareUrlShortUrl = response.data.awemeDetail.shareUrl.makeShortLink()
|
||||||
|
val image = this.createDouyinVideoCard(response)
|
||||||
|
val msg= MessageChain.Builder()
|
||||||
|
.addImage(image, true)
|
||||||
|
.addText("视频地址: $shareUrlShortUrl")
|
||||||
|
.addNewLine()
|
||||||
|
.addText("下载地址: $videoPlayUrlShortUrl")
|
||||||
|
.build()
|
||||||
|
message.reply(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* Copyright © 2024 RTAkland
|
||||||
|
* Author: RTAkland
|
||||||
|
* Date: 2024/10/13
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package cn.rtast.fancybot.entity.douyin
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class DouyinVideo(
|
||||||
|
val data: Data
|
||||||
|
) {
|
||||||
|
data class Data(
|
||||||
|
@SerializedName("aweme_detail")
|
||||||
|
val awemeDetail: AwemeDetail
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AwemeDetail(
|
||||||
|
val author: Author,
|
||||||
|
val desc: String,
|
||||||
|
val music: Music,
|
||||||
|
val statistics: Statistics,
|
||||||
|
val video: Video,
|
||||||
|
@SerializedName("share_url")
|
||||||
|
val shareUrl: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Author(
|
||||||
|
val nickname: String,
|
||||||
|
@SerializedName("follower_count")
|
||||||
|
val followerCount: Int,
|
||||||
|
val signature: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Music(
|
||||||
|
@SerializedName("avatar_large")
|
||||||
|
val avatarLarge: AvatarLarge,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AvatarLarge(
|
||||||
|
@SerializedName("url_list")
|
||||||
|
val urlList: List<String>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Statistics(
|
||||||
|
// 收藏数
|
||||||
|
@SerializedName("collect_count")
|
||||||
|
val collectCount: Int,
|
||||||
|
// 评论数
|
||||||
|
@SerializedName("comment_count")
|
||||||
|
val commentCount: Int,
|
||||||
|
// 点赞数
|
||||||
|
@SerializedName("digg_count")
|
||||||
|
val diggCount: Int,
|
||||||
|
// 播放数
|
||||||
|
@SerializedName("play_count")
|
||||||
|
val playCount: Int,
|
||||||
|
// 分享数
|
||||||
|
@SerializedName("share_count")
|
||||||
|
val shareCount: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Video(
|
||||||
|
val cover: Cover,
|
||||||
|
@SerializedName("bit_rate")
|
||||||
|
val bitRate: List<BitRate>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BitRate(
|
||||||
|
@SerializedName("play_addr")
|
||||||
|
val playAddr: PlayAddr
|
||||||
|
)
|
||||||
|
|
||||||
|
data class PlayAddr(
|
||||||
|
@SerializedName("url_list")
|
||||||
|
val urlList: List<String>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Cover(
|
||||||
|
@SerializedName("url_list")
|
||||||
|
val urlList: List<String>,
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user