optimize(bilibili): separate packages

This commit is contained in:
源文雨
2025-09-27 01:02:53 +08:00
parent c22e4b543c
commit 1e2d425cf4
12 changed files with 49 additions and 491 deletions

View File

@@ -0,0 +1,291 @@
// Package bilibiliparse bilibili卡片解析
package bilibiliparse
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
"os/exec"
"regexp"
"strings"
"time"
bz "github.com/FloatTech/AnimeAPI/bilibili"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/web"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/pkg/errors"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
enableVideoSummary = int64(0x10)
disableVideoSummary = ^enableVideoSummary
enableVideoDownload = int64(0x20)
disableVideoDownload = ^enableVideoDownload
bilibiliparseReferer = "https://www.bilibili.com"
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
)
var (
limit = ctxext.NewLimiterManager(time.Second*10, 1)
searchVideo = `bilibili.com\\?/video\\?/(?:av(\d+)|([bB][vV][0-9a-zA-Z]+))`
searchDynamic = `(t.bilibili.com|m.bilibili.com\\?/dynamic)\\?/(\d+)`
searchArticle = `bilibili.com\\?/read\\?/(?:cv|mobile\\?/)(\d+)`
searchLiveRoom = `live.bilibili.com\\?/(\d+)`
searchVideoRe = regexp.MustCompile(searchVideo)
searchDynamicRe = regexp.MustCompile(searchDynamic)
searchArticleRe = regexp.MustCompile(searchArticle)
searchLiveRoomRe = regexp.MustCompile(searchLiveRoom)
cachePath string
cfg = bz.NewCookieConfig("data/Bilibili/config.json")
)
// 插件主体
func init() {
en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "b站链接解析",
Help: "例:- t.bilibili.com/642277677329285174\n- bilibili.com/read/cv17134450\n- bilibili.com/video/BV13B4y1x7pS\n- live.bilibili.com/22603245 ",
})
cachePath = en.DataFolder() + "cache/"
_ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
en.OnRegex(`((b23|acg).tv|bili2233.cn)\\?/[0-9a-zA-Z]+`).SetBlock(true).Limit(limit.LimitByGroup).
Handle(func(ctx *zero.Ctx) {
u := ctx.State["regex_matched"].([]string)[0]
u = strings.ReplaceAll(u, "\\", "")
realurl, err := bz.GetRealURL("https://" + u)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
switch {
case searchVideoRe.MatchString(realurl):
ctx.State["regex_matched"] = searchVideoRe.FindStringSubmatch(realurl)
handleVideo(ctx)
case searchDynamicRe.MatchString(realurl):
ctx.State["regex_matched"] = searchDynamicRe.FindStringSubmatch(realurl)
handleDynamic(ctx)
case searchArticleRe.MatchString(realurl):
ctx.State["regex_matched"] = searchArticleRe.FindStringSubmatch(realurl)
handleArticle(ctx)
case searchLiveRoomRe.MatchString(realurl):
ctx.State["regex_matched"] = searchLiveRoomRe.FindStringSubmatch(realurl)
handleLive(ctx)
}
})
en.OnRegex(`^(开启|打开|启用|关闭|关掉|禁用)视频总结$`, zero.AdminPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid <= 0 {
// 个人用户设为负数
gid = -ctx.Event.UserID
}
option := ctx.State["regex_matched"].([]string)[1]
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("找不到服务!"))
return
}
data := c.GetData(ctx.Event.GroupID)
switch option {
case "开启", "打开", "启用":
data |= enableVideoSummary
case "关闭", "关掉", "禁用":
data &= disableVideoSummary
default:
return
}
err := c.SetData(gid, data)
if err != nil {
ctx.SendChain(message.Text("出错啦: ", err))
return
}
ctx.SendChain(message.Text("已", option, "视频总结"))
})
en.OnRegex(`^(开启|打开|启用|关闭|关掉|禁用)视频上传$`, zero.AdminPermission).SetBlock(true).
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid <= 0 {
// 个人用户设为负数
gid = -ctx.Event.UserID
}
option := ctx.State["regex_matched"].([]string)[1]
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if !ok {
ctx.SendChain(message.Text("找不到服务!"))
return
}
data := c.GetData(ctx.Event.GroupID)
switch option {
case "开启", "打开", "启用":
data |= enableVideoDownload
case "关闭", "关掉", "禁用":
data &= disableVideoDownload
default:
return
}
err := c.SetData(gid, data)
if err != nil {
ctx.SendChain(message.Text("出错啦: ", err))
return
}
ctx.SendChain(message.Text("已", option, "视频上传"))
})
en.OnRegex(searchVideo).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleVideo)
en.OnRegex(searchDynamic).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleDynamic)
en.OnRegex(searchArticle).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleArticle)
en.OnRegex(searchLiveRoom).SetBlock(true).Limit(limit.LimitByGroup).Handle(handleLive)
}
func handleVideo(ctx *zero.Ctx) {
id := ctx.State["regex_matched"].([]string)[1]
if id == "" {
id = ctx.State["regex_matched"].([]string)[2]
}
card, err := bz.GetVideoInfo(id)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
msg, err := card.ToVideoMessage()
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
if ok && c.GetData(ctx.Event.GroupID)&enableVideoSummary == enableVideoSummary {
summaryMsg, err := getVideoSummary(cfg, card)
if err != nil {
msg = append(msg, message.Text("ERROR: ", err))
} else {
msg = append(msg, summaryMsg...)
}
}
ctx.SendChain(msg...)
if ok && c.GetData(ctx.Event.GroupID)&enableVideoDownload == enableVideoDownload {
downLoadMsg, err := getVideoDownload(cfg, card, cachePath)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(downLoadMsg...)
}
}
func handleDynamic(ctx *zero.Ctx) {
msg, err := cfg.GetDetailMessage(ctx.State["regex_matched"].([]string)[2])
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(msg...)
}
func handleArticle(ctx *zero.Ctx) {
card, err := bz.GetArticleInfo(ctx.State["regex_matched"].([]string)[1])
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(card.ToArticleMessage(ctx.State["regex_matched"].([]string)[1])...)
}
func handleLive(ctx *zero.Ctx) {
cookie, err := cfg.Load()
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
card, err := bz.GetLiveRoomInfo(ctx.State["regex_matched"].([]string)[1], cookie)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
ctx.SendChain(card.ToMessage()...)
}
// getVideoSummary AI视频总结
func getVideoSummary(cookiecfg *bz.CookieConfig, card bz.Card) (msg []message.Segment, err error) {
var (
data []byte
videoSummary bz.VideoSummary
)
data, err = web.RequestDataWithHeaders(web.NewDefaultClient(), bz.SignURL(fmt.Sprintf(bz.VideoSummaryURL, card.BvID, card.CID, card.Owner.Mid)), "GET", func(req *http.Request) error {
if cookiecfg != nil {
cookie := ""
cookie, err = cookiecfg.Load()
if err != nil {
return err
}
req.Header.Add("cookie", cookie)
}
req.Header.Set("User-Agent", ua)
return nil
}, nil)
if err != nil {
return
}
err = json.Unmarshal(data, &videoSummary)
msg = make([]message.Segment, 0, 16)
msg = append(msg, message.Text("已为你生成视频总结\n\n"))
msg = append(msg, message.Text(videoSummary.Data.ModelResult.Summary, "\n\n"))
for _, v := range videoSummary.Data.ModelResult.Outline {
msg = append(msg, message.Text("● ", v.Title, "\n"))
for _, p := range v.PartOutline {
msg = append(msg, message.Text(fmt.Sprintf("%d:%d %s\n", p.Timestamp/60, p.Timestamp%60, p.Content)))
}
msg = append(msg, message.Text("\n"))
}
return
}
func getVideoDownload(cookiecfg *bz.CookieConfig, card bz.Card, cachePath string) (msg []message.Segment, err error) {
var (
data []byte
videoDownload bz.VideoDownload
stderr bytes.Buffer
)
today := time.Now().Format("20060102")
videoFile := fmt.Sprintf("%s%s%s.mp4", cachePath, card.BvID, today)
if file.IsExist(videoFile) {
msg = append(msg, message.Video("file:///"+file.BOTPATH+"/"+videoFile))
return
}
data, err = web.RequestDataWithHeaders(web.NewDefaultClient(), bz.SignURL(fmt.Sprintf(bz.VideoDownloadURL, card.BvID, card.CID)), "GET", func(req *http.Request) error {
if cookiecfg != nil {
cookie := ""
cookie, err = cookiecfg.Load()
if err != nil {
return err
}
req.Header.Add("cookie", cookie)
}
req.Header.Set("User-Agent", ua)
return nil
}, nil)
if err != nil {
return
}
err = json.Unmarshal(data, &videoDownload)
if err != nil {
return
}
headers := fmt.Sprintf("User-Agent: %s\nReferer: %s", ua, bilibiliparseReferer)
// 限制最多下载8分钟视频
cmd := exec.Command("ffmpeg", "-ss", "0", "-t", "480", "-headers", headers, "-i", videoDownload.Data.Durl[0].URL, "-c", "copy", videoFile)
cmd.Stderr = &stderr
err = cmd.Run()
if err != nil {
err = errors.Errorf("未配置ffmpeg%v", stderr)
return
}
msg = append(msg, message.Video("file:///"+file.BOTPATH+"/"+videoFile))
return
}