From 4b90a0659b3d218dd62e6fed4033da2f32736119 Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:33:15 +0800 Subject: [PATCH 01/51] =?UTF-8?q?feat(bilibili):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E4=B8=8B=E8=BD=BD=20(#1157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 4 +- plugin/bilibili/bilibili_parse.go | 65 ++++++++++++++++++++++++++++++- plugin/bilibili/card2msg.go | 24 +++++++++--- 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 88555b7db3..9031dd0e4e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/Baidu-AIP/golang-sdk v1.1.1 - github.com/FloatTech/AnimeAPI v1.7.1-0.20250217140215-4856397458c9 + github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024 github.com/FloatTech/gg v1.1.3 github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef diff --git a/go.sum b/go.sum index 57121051eb..318781510c 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw= github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250217140215-4856397458c9 h1:tI9GgG8fdMK2WazFiEbMXAXjwMCckIfDaXbig9B6DdA= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250217140215-4856397458c9/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c h1:bEe8VP2aHLR2NHk1BsBQFtP0XE3cxquvr0tW0CkKcDk= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024 h1:mrvWpiwfRklt9AyiQjKgDGJjf4YL6FZ3yC+ydbkuF2o= github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024/go.mod h1:+P3hs+Cvl10/Aj3SNE96TuBvKAXCe+XD1pKphTZyiwk= github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4= diff --git a/plugin/bilibili/bilibili_parse.go b/plugin/bilibili/bilibili_parse.go index 09607630c5..7296b68b8c 100644 --- a/plugin/bilibili/bilibili_parse.go +++ b/plugin/bilibili/bilibili_parse.go @@ -2,25 +2,31 @@ package bilibili 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 ( - enableHex = 0x10 - unableHex = 0x7fffffff_fffffffd + enableHex = 0x10 + unableHex = 0x7fffffff_fffffffd + bilibiliparseReferer = "https://www.bilibili.com" ) var ( @@ -33,6 +39,7 @@ var ( searchDynamicRe = regexp.MustCompile(searchDynamic) searchArticleRe = regexp.MustCompile(searchArticle) searchLiveRoomRe = regexp.MustCompile(searchLiveRoom) + cachePath string ) // 插件主体 @@ -42,6 +49,9 @@ func init() { 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] @@ -126,6 +136,12 @@ func handleVideo(ctx *zero.Ctx) { } } ctx.SendChain(msg...) + downLoadMsg, err := getVideoDownload(cfg, card, cachePath) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + ctx.SendChain(downLoadMsg...) } func handleDynamic(ctx *zero.Ctx) { @@ -189,3 +205,48 @@ func getVideoSummary(cookiecfg *bz.CookieConfig, card bz.Card) (msg []message.Se } 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 + +} diff --git a/plugin/bilibili/card2msg.go b/plugin/bilibili/card2msg.go index 3e6c59918e..d2dcc5fe30 100644 --- a/plugin/bilibili/card2msg.go +++ b/plugin/bilibili/card2msg.go @@ -2,10 +2,12 @@ package bilibili import ( "encoding/json" + "fmt" "time" bz "github.com/FloatTech/AnimeAPI/bilibili" "github.com/FloatTech/floatbox/binary" + "github.com/FloatTech/floatbox/web" "github.com/wdvxdr1123/ZeroBot/message" ) @@ -303,7 +305,10 @@ func liveCard2msg(card bz.RoomCard) (msg []message.Segment) { // videoCard2msg 视频卡片转消息 func videoCard2msg(card bz.Card) (msg []message.Segment, err error) { - var mCard bz.MemberCard + var ( + mCard bz.MemberCard + onlineTotal bz.OnlineTotal + ) msg = make([]message.Segment, 0, 16) mCard, err = bz.GetMemberCard(card.Owner.Mid) msg = append(msg, message.Text("标题: ", card.Title, "\n")) @@ -313,16 +318,25 @@ func videoCard2msg(card bz.Card) (msg []message.Segment, err error) { } } else { if err != nil { - err = nil msg = append(msg, message.Text("UP主: ", card.Owner.Name, "\n")) } else { msg = append(msg, message.Text("UP主: ", card.Owner.Name, " 粉丝: ", bz.HumanNum(mCard.Fans), "\n")) } } - msg = append(msg, message.Text("播放: ", bz.HumanNum(card.Stat.View), " 弹幕: ", bz.HumanNum(card.Stat.Danmaku))) msg = append(msg, message.Image(card.Pic)) - msg = append(msg, message.Text("\n点赞: ", bz.HumanNum(card.Stat.Like), " 投币: ", bz.HumanNum(card.Stat.Coin), "\n", - "收藏: ", bz.HumanNum(card.Stat.Favorite), " 分享: ", bz.HumanNum(card.Stat.Share), "\n", + data, err := web.GetData(fmt.Sprintf(bz.OnlineTotalURL, card.BvID, card.CID)) + if err != nil { + return + } + err = json.Unmarshal(data, &onlineTotal) + if err != nil { + return + } + msg = append(msg, message.Text("👀播放: ", bz.HumanNum(card.Stat.View), " 💬弹幕: ", bz.HumanNum(card.Stat.Danmaku), + "\n👍点赞: ", bz.HumanNum(card.Stat.Like), " 💰投币: ", bz.HumanNum(card.Stat.Coin), + "\n📁收藏: ", bz.HumanNum(card.Stat.Favorite), " 🔗分享: ", bz.HumanNum(card.Stat.Share), + "\n📝简介: ", card.Desc, + "\n🏄‍♂️ 总共 ", onlineTotal.Data.Total, " 人在观看,", onlineTotal.Data.Count, " 人在网页端观看\n", bz.VURL, card.BvID, "\n\n")) return } From 30e9d04f7481afe6d35463f184301e27e080f1f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=B9=E6=9F=B3=E7=85=9C?= <101934327+fangliuyu@users.noreply.github.com> Date: Wed, 23 Apr 2025 17:35:34 +0800 Subject: [PATCH 02/51] =?UTF-8?q?feat:=20=E7=94=B5=E5=BD=B1=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=20(#1155)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 + main.go | 1 + plugin/movies/main.go | 435 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 plugin/movies/main.go diff --git a/README.md b/README.md index 7056ca964b..54f59afd6b 100644 --- a/README.md +++ b/README.md @@ -976,6 +976,14 @@ print("run[CQ:image,file="+j["img"]+"]") - (机器人回答:您的下一条指令将被记录,在@@every 1m时触发) - mc服务器订阅拉取 +
+ Movies猫眼电影查询 + +`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/movies"` + +- [x] 今日电影 +- [x] 预售电影 +
摸鱼 diff --git a/main.go b/main.go index 34e7130c8a..a260c25857 100644 --- a/main.go +++ b/main.go @@ -110,6 +110,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/mcfish" // 钓鱼模拟器 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/midicreate" // 简易midi音乐制作 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/minecraftobserver" // Minecraft服务器监控&订阅 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/movies" // 电影插件 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyu" // 摸鱼 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/moyucalendar" // 摸鱼人日历 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/music" // 点歌 diff --git a/plugin/movies/main.go b/plugin/movies/main.go new file mode 100644 index 0000000000..b17cbb4631 --- /dev/null +++ b/plugin/movies/main.go @@ -0,0 +1,435 @@ +// Package movies 电影查询 +package movies + +import ( + "encoding/json" + "image" + "net/http" + "os" + "path/filepath" + "sort" + "strconv" + "sync" + "time" + + "github.com/FloatTech/floatbox/file" + "github.com/FloatTech/floatbox/web" + "github.com/FloatTech/gg" + "github.com/FloatTech/imgfactory" + "github.com/FloatTech/rendercard" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/img/text" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +const ( + apiURL = "https://m.maoyan.com/ajax/" + ua = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36" +) + +var ( + mu sync.RWMutex + todayPic = make([][]byte, 2) + lasttime time.Time + en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Brief: "电影查询", + Help: "- 今日电影\n" + + "- 预售电影", + PrivateDataFolder: "movies", + }) +) + +func init() { + en.OnFullMatch("今日电影").SetBlock(true).Handle(func(ctx *zero.Ctx) { + if todayPic != nil && time.Since(lasttime) < 12*time.Hour { + ctx.SendChain(message.ImageBytes(todayPic[0])) + return + } + lasttime = time.Now() + movieComingList, err := getMovieList("今日电影") + if err != nil { + ctx.SendChain(message.Text("[ERROR]:", err)) + return + } + if len(movieComingList) == 0 { + ctx.SendChain(message.Text("没有今日电影")) + return + } + pic, err := drawOnListPic(movieComingList) + if err != nil { + ctx.SendChain(message.Text("[ERROR]:", err)) + return + } + todayPic[0] = pic + ctx.SendChain(message.ImageBytes(pic)) + }) + en.OnFullMatch("预售电影").SetBlock(true).Handle(func(ctx *zero.Ctx) { + if todayPic[1] != nil && time.Since(lasttime) < 12*time.Hour { + ctx.SendChain(message.ImageBytes(todayPic[1])) + return + } + lasttime = time.Now() + movieComingList, err := getMovieList("预售电影") + if err != nil { + ctx.SendChain(message.Text("[ERROR]:", err)) + return + } + if len(movieComingList) == 0 { + ctx.SendChain(message.Text("没有预售电影")) + return + } + pic, err := drawComListPic(movieComingList) + if err != nil { + ctx.SendChain(message.Text("[ERROR]:", err)) + return + } + todayPic[1] = pic + ctx.SendChain(message.ImageBytes(pic)) + }) +} + +type movieInfo struct { + ID int64 `json:"id"` // 电影ID + Img string `json:"img"` // 海报 + + Nm string `json:"nm"` // 名称 + + Dir string `json:"dir"` // 导演 + Star string `json:"star"` // 演员 + + OriLang string `json:"oriLang"` // 原语言 + Cat string `json:"cat"` // 类型 + + Version string `json:"version"` // 电影格式 + Rt string `json:"rt"` // 上映时间 + + ShowInfo string `json:"showInfo"` // 今日上映信息 + ComingTitle string `json:"comingTitle"` // 预售信息 + + Sc float64 `json:"sc"` // 评分 + Wish int64 `json:"wish"` // 观看人数 + Watched int64 `json:"watched"` // 观看数 +} +type movieOnList struct { + MovieList []movieInfo `json:"movieList"` +} +type comingList struct { + MovieList []movieInfo `json:"coming"` +} +type movieShow struct { + MovieInfo movieInfo `json:"detailMovie"` +} + +type cardInfo struct { + Avatar image.Image + TopLeftText string + BottomLeftText []string + RightText string + Rank string +} + +func getMovieList(mode string) (movieList []movieInfo, err error) { + var data []byte + if mode == "今日电影" { + data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"movieOnInfoList", "", "GET", ua, nil) + if err != nil { + return + } + var parsed movieOnList + err = json.Unmarshal(data, &parsed) + if err != nil { + return + } + movieList = parsed.MovieList + } else { + data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"comingList?token=", "", "GET", ua, nil) + if err != nil { + return + } + var parsed comingList + err = json.Unmarshal(data, &parsed) + if err != nil { + return + } + movieList = parsed.MovieList + } + if len(movieList) == 0 { + return + } + for i, info := range movieList { + movieID := strconv.FormatInt(info.ID, 10) + data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"detailmovie?movieId="+movieID, "", "GET", ua, nil) + if err != nil { + return + } + var movieInfo movieShow + err = json.Unmarshal(data, &movieInfo) + if err != nil { + return + } + if mode != "今日电影" { + movieInfo.MovieInfo.ComingTitle = movieList[i].ComingTitle + } + movieList[i] = movieInfo.MovieInfo + } + // 整理数据,进行排序 + sort.Slice(movieList, func(i, j int) bool { + if movieList[i].Sc != movieList[j].Sc { + return movieList[i].Sc > movieList[j].Sc + } + if mode == "今日电影" { + return movieList[i].Watched > movieList[j].Watched + } + return movieList[i].Wish > movieList[j].Wish + }) + return movieList, nil +} +func drawOnListPic(lits []movieInfo) (data []byte, err error) { + rankinfo := make([]*cardInfo, len(lits)) + + wg := &sync.WaitGroup{} + wg.Add(len(lits)) + for i := 0; i < len(lits); i++ { + go func(i int) { + info := lits[i] + defer wg.Done() + img, err := avatar(&info) + if err != nil { + return + } + movieType := "2D" + if info.Version != "" { + movieType = info.Version + } + watched := "" + switch { + case info.Watched > 100000000: + watched = strconv.FormatFloat(float64(info.Watched)/100000000, 'f', 2, 64) + "亿" + case info.Watched > 10000: + watched = strconv.FormatFloat(float64(info.Watched)/10000, 'f', 2, 64) + "万" + default: + watched = strconv.FormatInt(info.Watched, 10) + } + rankinfo[i] = &cardInfo{ + TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")", + BottomLeftText: []string{ + "导演:" + info.Dir, + "演员:" + info.Star, + "标签:" + info.Cat, + "语言: " + info.OriLang + " 类型: " + movieType, + "上映时间: " + info.Rt, + }, + RightText: watched + "人已看", + Avatar: img, + Rank: strconv.FormatFloat(info.Sc, 'f', 1, 64), + } + }(i) + } + wg.Wait() + fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true) + if err != nil { + return + } + img, err := drawRankingCard(fontbyte, "今日电影", rankinfo) + if err != nil { + return + } + data, err = imgfactory.ToBytes(img) + return +} + +func drawComListPic(lits []movieInfo) (data []byte, err error) { + rankinfo := make([]*cardInfo, len(lits)) + + wg := &sync.WaitGroup{} + wg.Add(len(lits)) + for i := 0; i < len(lits); i++ { + go func(i int) { + info := lits[i] + defer wg.Done() + img, err := avatar(&info) + if err != nil { + return + } + movieType := "2D" + if info.Version != "" { + movieType = info.Version + } + wish := "" + switch { + case info.Wish > 100000000: + wish = strconv.FormatFloat(float64(info.Wish)/100000000, 'f', 2, 64) + "亿" + case info.Wish > 10000: + wish = strconv.FormatFloat(float64(info.Wish)/10000, 'f', 2, 64) + "万" + default: + wish = strconv.FormatInt(info.Wish, 10) + } + rankinfo[i] = &cardInfo{ + TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")", + BottomLeftText: []string{ + "导演:" + info.Dir, + "演员:" + info.Star, + "标签:" + info.Cat, + "语言: " + info.OriLang + " 类型: " + movieType, + "上映时间: " + info.Rt + " 播放时间: " + info.ComingTitle, + }, + RightText: wish + "人期待", + Avatar: img, + Rank: strconv.Itoa(i + 1), + } + }(i) + } + wg.Wait() + fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true) + if err != nil { + return + } + img, err := drawRankingCard(fontbyte, "预售电影", rankinfo) + if err != nil { + return + } + data, err = imgfactory.ToBytes(img) + return +} + +func drawRankingCard(fontdata []byte, title string, rankinfo []*cardInfo) (img image.Image, err error) { + line := len(rankinfo) + const lineh = 130 + const w = 800 + h := 64 + (lineh+14)*line + 20 - 14 + canvas := gg.NewContext(w, h) + canvas.SetRGBA255(255, 255, 255, 255) + canvas.Clear() + + cardh, cardw := lineh, 770 + cardspac := 14 + hspac, wspac := 64.0, 16.0 + r := 16.0 + + wg := &sync.WaitGroup{} + wg.Add(line) + cardimgs := make([]image.Image, line) + for i := 0; i < line; i++ { + go func(i int) { + defer wg.Done() + card := gg.NewContext(w, cardh) + + card.NewSubPath() + + card.MoveTo(wspac+float64(cardh)/2, 0) + + card.LineTo(wspac+float64(cardw)-r, 0) + card.DrawArc(wspac+float64(cardw)-r, r, r, gg.Radians(-90), gg.Radians(0)) + card.LineTo(wspac+float64(cardw), float64(cardh)-r) + card.DrawArc(wspac+float64(cardw)-r, float64(cardh)-r, r, gg.Radians(0), gg.Radians(90)) + card.LineTo(wspac+float64(cardh)/2, float64(cardh)) + card.DrawArc(wspac+r, float64(cardh)-r, r, gg.Radians(90), gg.Radians(180)) + card.LineTo(wspac, r) + card.DrawArc(wspac+r, r, r, gg.Radians(180), gg.Radians(270)) + + card.ClosePath() + + card.ClipPreserve() + + avatar := rankinfo[i].Avatar + + PicH := cardh - 20 + picW := int(float64(avatar.Bounds().Dx()) * float64(PicH) / float64(avatar.Bounds().Dy())) + card.DrawImageAnchored(imgfactory.Size(avatar, picW, PicH).Image(), int(wspac)+10+picW/2, cardh/2, 0.5, 0.5) + + card.ResetClip() + card.SetRGBA255(0, 0, 0, 127) + card.Stroke() + + card.SetRGBA255(240, 210, 140, 200) + card.DrawRoundedRectangle(wspac+float64(cardw-8-250), (float64(cardh)-50)/2, 250, 50, 25) + card.Fill() + card.SetRGB255(rendercard.RandJPColor()) + card.DrawRoundedRectangle(wspac+float64(cardw-8-60), (float64(cardh)-50)/2, 60, 50, 25) + card.Fill() + cardimgs[i] = card.Image() + }(i) + } + + canvas.SetRGBA255(0, 0, 0, 255) + err = canvas.ParseFontFace(fontdata, 32) + if err != nil { + return + } + canvas.DrawStringAnchored(title, w/2, 64/2, 0.5, 0.5) + + err = canvas.ParseFontFace(fontdata, 22) + if err != nil { + return + } + wg.Wait() + for i := 0; i < line; i++ { + canvas.DrawImageAnchored(cardimgs[i], w/2, int(hspac)+((cardh+cardspac)*i), 0.5, 0) + canvas.DrawStringAnchored(rankinfo[i].TopLeftText, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*3/16), 0, 0.5) + } + + // canvas.SetRGBA255(63, 63, 63, 255) + err = canvas.ParseFontFace(fontdata, 14) + if err != nil { + return + } + for i := 0; i < line; i++ { + for j, text := range rankinfo[i].BottomLeftText { + canvas.DrawStringAnchored(text, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*6/16)+float64(j*16), 0, 0.5) + } + } + canvas.SetRGBA255(0, 0, 0, 255) + err = canvas.ParseFontFace(fontdata, 20) + if err != nil { + return + } + for i := 0; i < line; i++ { + canvas.DrawStringAnchored(rankinfo[i].RightText, w-wspac-8-60-8, hspac+float64((cardspac+cardh)*i+cardh/2), 1, 0.5) + } + + canvas.SetRGBA255(255, 255, 255, 255) + err = canvas.ParseFontFace(fontdata, 28) + if err != nil { + return + } + for i := 0; i < line; i++ { + canvas.DrawStringAnchored(rankinfo[i].Rank, w-wspac-8-30, hspac+float64((cardspac+cardh)*i+cardh/2), 0.5, 0.5) + } + + img = canvas.Image() + return +} + +// avatar 获取电影海报,图片大且多,存本地增加响应速度 +func avatar(movieInfo *movieInfo) (pic image.Image, err error) { + mu.Lock() + defer mu.Unlock() + + aimgfile := filepath.Join(en.DataFolder(), movieInfo.Nm+"("+strconv.FormatInt(movieInfo.ID, 10)+").jpg") + if file.IsNotExist(aimgfile) { + err = file.DownloadTo(movieInfo.Img, aimgfile) + if err != nil { + return urlToImg(movieInfo.Img) + } + } + f, err := os.Open(filepath.Join(file.BOTPATH, aimgfile)) + if err != nil { + return urlToImg(movieInfo.Img) + } + defer f.Close() + pic, _, err = image.Decode(f) + return +} + +func urlToImg(url string) (img image.Image, err error) { + resp, err := http.Get(url) + if err != nil { + return + } + defer resp.Body.Close() + img, _, err = image.Decode(resp.Body) + return +} From 2c607dedee2303403fe909513733dc08b7ed8ee9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 18:36:20 +0900 Subject: [PATCH 03/51] chore: bump deps (#1158) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 1d8db9d68d..0b05be7f2e 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -5,8 +5,8 @@ schema = 3 version = "v1.1.1" hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo=" [mod."github.com/FloatTech/AnimeAPI"] - version = "v1.7.1-0.20250217140215-4856397458c9" - hash = "sha256-7TkWoVslfzO/aTx+F7UwttrtBGGMMqe4GHN0aF4JUd0=" + version = "v1.7.1-0.20250423082452-e16339a3962c" + hash = "sha256-pyv242GLfslHEgQN5TgYsdpJKWzWR8qc4mJ8NXF0I8s=" [mod."github.com/FloatTech/floatbox"] version = "v0.0.0-20241106130736-5aea0a935024" hash = "sha256-hSKmkzpNZwXRo0qm4G+1lXkNzWMwV9leYlYLQuzWx3M=" From 0b89312d9dd3694e202620a84292aed62bb411de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Apr 2025 18:36:41 +0900 Subject: [PATCH 04/51] =?UTF-8?q?chore(lint):=20=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=B7=E5=BC=8F=20(#1159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- plugin/bilibili/bilibili_parse.go | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/bilibili/bilibili_parse.go b/plugin/bilibili/bilibili_parse.go index 7296b68b8c..b094e0e88e 100644 --- a/plugin/bilibili/bilibili_parse.go +++ b/plugin/bilibili/bilibili_parse.go @@ -248,5 +248,4 @@ func getVideoDownload(cookiecfg *bz.CookieConfig, card bz.Card, cachePath string } msg = append(msg, message.Video("file:///"+file.BOTPATH+"/"+videoFile)) return - } From 4151464bdc684c3e1e6b1117e0fa6cc0dd837db7 Mon Sep 17 00:00:00 2001 From: Nobody6825 <117302867+Nobooooody@users.noreply.github.com> Date: Tue, 6 May 2025 17:11:46 +0800 Subject: [PATCH 05/51] chore: use new nixpkgs with overlay which bring back go_1_20 instead of using old nixpkgs (#1162) --- default.nix | 1 + flake.lock | 25 +++++++++++++++++++++---- flake.nix | 27 ++++++++++++++++++--------- shell.nix | 2 +- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/default.nix b/default.nix index ecde3a4a81..5d8ebfad95 100644 --- a/default.nix +++ b/default.nix @@ -11,6 +11,7 @@ } ), buildGoApplication ? pkgs.buildGoApplication, + ... }: buildGoApplication { pname = "ZeroBot-Plugin"; diff --git a/flake.lock b/flake.lock index ed9ab6dccd..36b302f6df 100644 --- a/flake.lock +++ b/flake.lock @@ -28,11 +28,11 @@ ] }, "locked": { - "lastModified": 1741396135, - "narHash": "sha256-wqmdLr7h4Bk8gyKutgaApJKOM8JVvywI5P48NuqJ9Jg=", + "lastModified": 1742209644, + "narHash": "sha256-jMy1XqXqD0/tJprEbUmKilTkvbDY/C0ZGSsJJH4TNCE=", "owner": "nix-community", "repo": "gomod2nix", - "rev": "0983848bf2a7ccbfe24d874065adb8fd0f23729b", + "rev": "8f3534eb8f6c5c3fce799376dc3b91bae6b11884", "type": "github" }, "original": { @@ -42,6 +42,22 @@ } }, "nixpkgs": { + "locked": { + "lastModified": 1745391562, + "narHash": "sha256-sPwcCYuiEopaafePqlG826tBhctuJsLx/mhKKM5Fmjo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "8a2f738d9d1f1d986b5a4cd2fd2061a7127237d7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-with-go_1_20": { "locked": { "lastModified": 1710843028, "narHash": "sha256-CMbK45c4nSkGvayiEHFkGFH+doGPbgo3AWfecd2t1Fk=", @@ -61,7 +77,8 @@ "inputs": { "flake-utils": "flake-utils", "gomod2nix": "gomod2nix", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "nixpkgs-with-go_1_20": "nixpkgs-with-go_1_20" } }, "systems": { diff --git a/flake.nix b/flake.nix index d069177aae..21b6eec41c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,8 +1,8 @@ { description = "基于 ZeroBot 的 OneBot 插件"; - # pin nixpkgs to preserve dropped go_1_20 - inputs.nixpkgs.url = "github:NixOS/nixpkgs/33c51330782cb486764eb598d5907b43dc87b4c2"; + inputs.nixpkgs-with-go_1_20.url = "github:NixOS/nixpkgs/33c51330782cb486764eb598d5907b43dc87b4c2"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; inputs.flake-utils.url = "github:numtide/flake-utils"; inputs.gomod2nix.url = "github:nix-community/gomod2nix"; inputs.gomod2nix.inputs.nixpkgs.follows = "nixpkgs"; @@ -11,14 +11,25 @@ outputs = { self, nixpkgs, + nixpkgs-with-go_1_20, flake-utils, gomod2nix, - }: let + ... + } @ inputs: let allSystems = flake-utils.lib.allSystems; in ( flake-utils.lib.eachSystem allSystems (system: let - pkgs = nixpkgs.legacyPackages.${system}; + old-nixpkgs = nixpkgs-with-go_1_20.legacyPackages.${system}; + pkgs = import nixpkgs { + inherit system; + + overlays = [ + (_: _: { + go_1_20 = old-nixpkgs.go_1_20; + }) + ]; + }; # The current default sdk for macOS fails to compile go projects, so we use a newer one for now. # This has no effect on other platforms. @@ -26,11 +37,10 @@ in { # doCheck will fail at write files packages = rec { - - ZeroBot-Plugin = - (callPackage ./. { + ZeroBot-Plugin = (callPackage ./. (inputs + // { inherit (gomod2nix.legacyPackages.${system}) buildGoApplication; - }) + })) .overrideAttrs (_: {doCheck = false;}); default = ZeroBot-Plugin; @@ -43,7 +53,6 @@ pkgs.cacert ]; }; - }; devShells.default = callPackage ./shell.nix { inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix; diff --git a/shell.nix b/shell.nix index b7b1aabc2e..303d0b78d3 100644 --- a/shell.nix +++ b/shell.nix @@ -13,7 +13,7 @@ mkGoEnv ? pkgs.mkGoEnv, gomod2nix ? pkgs.gomod2nix, }: let - goEnv = mkGoEnv {pwd = ./.;}; + goEnv = mkGoEnv { pwd = ./.; go = pkgs.go_1_20; }; in pkgs.mkShell { packages = [ From 39e1f56955f7c99fc6941f27202eb5160f2d227f Mon Sep 17 00:00:00 2001 From: Doordoorjay <31199261+Doordoorjay@users.noreply.github.com> Date: Tue, 6 May 2025 19:12:41 +1000 Subject: [PATCH 06/51] =?UTF-8?q?fix:=20=E7=96=AF=E7=8B=82=E6=98=9F?= =?UTF-8?q?=E6=9C=9F=E5=9B=9B=20API=20(#1161)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/kfccrazythursday/kfccrazythursday.go | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/plugin/kfccrazythursday/kfccrazythursday.go b/plugin/kfccrazythursday/kfccrazythursday.go index a7c2cd265c..373340399b 100644 --- a/plugin/kfccrazythursday/kfccrazythursday.go +++ b/plugin/kfccrazythursday/kfccrazythursday.go @@ -2,7 +2,8 @@ package kfccrazythursday import ( - "github.com/FloatTech/floatbox/binary" + "encoding/json" + "github.com/FloatTech/floatbox/web" ctrl "github.com/FloatTech/zbpctrl" "github.com/FloatTech/zbputils/control" @@ -11,9 +12,15 @@ import ( ) const ( - crazyURL = "http://api.jixs.cc/api/wenan-fkxqs/index.php" + crazyURL = "https://api.pearktrue.cn/api/kfc/" ) +type crazyResponse struct { + Code int `json:"code"` + Msg string `json:"msg"` + Text string `json:"text"` +} + func init() { engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, @@ -26,6 +33,18 @@ func init() { ctx.SendChain(message.Text("ERROR: ", err)) return } - ctx.SendChain(message.Text(binary.BytesToString(data))) + + var resp crazyResponse + if err := json.Unmarshal(data, &resp); err != nil { + ctx.SendChain(message.Text("JSON解析失败: ", err)) + return + } + + if resp.Code != 200 { + ctx.SendChain(message.Text("API返回错误: ", resp.Msg)) + return + } + + ctx.SendChain(message.Text(resp.Text)) }) } From e1d2dee8817e900c061baf7118239753d9ff9688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Tue, 13 May 2025 20:19:34 +0900 Subject: [PATCH 07/51] feat: replace jieba with gse --- go.mod | 4 +++- go.sum | 9 +++++++-- plugin/thesaurus/chat.go | 16 ++++++---------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 9031dd0e4e..22f92eb0e1 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.20 require ( github.com/Baidu-AIP/golang-sdk v1.1.1 github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c - github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024 + github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 github.com/FloatTech/gg v1.1.3 github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef github.com/FloatTech/rendercard v0.2.0 @@ -30,6 +30,7 @@ require ( github.com/fumiama/slowdo v0.0.0-20241001074058-27c4fe5259a4 github.com/fumiama/terasu v0.0.0-20241027183601-987ab91031ce github.com/fumiama/unibase2n v0.0.0-20240530074540-ec743fd5a6d6 + github.com/go-ego/gse v0.80.3 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/google/uuid v1.6.0 github.com/jinzhu/gorm v1.9.16 @@ -85,6 +86,7 @@ require ( github.com/tidwall/pretty v1.2.0 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect + github.com/vcaesar/cedar v0.20.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/exp/shiny v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/mobile v0.0.0-20231127183840-76ac6878050a // indirect diff --git a/go.sum b/go.sum index 318781510c..6b04ccf9d2 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spI github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c h1:bEe8VP2aHLR2NHk1BsBQFtP0XE3cxquvr0tW0CkKcDk= github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= -github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024 h1:mrvWpiwfRklt9AyiQjKgDGJjf4YL6FZ3yC+ydbkuF2o= -github.com/FloatTech/floatbox v0.0.0-20241106130736-5aea0a935024/go.mod h1:+P3hs+Cvl10/Aj3SNE96TuBvKAXCe+XD1pKphTZyiwk= +github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 h1:lFD1pd8NkYCrw0QpTX/T5pJ67I7AL5eGxQ4v0r9f81Q= +github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80/go.mod h1:IWoFFqu+0FeaHHQdddyiTRL5z7gJME6qHC96qh0R2sc= github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4= github.com/FloatTech/gg v1.1.3/go.mod h1:/9oLP54CMfq4r+71XL26uaFTJ1uL1boAyX67680/1HE= github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef h1:CJbK/2FRwPuZpeb6M4sWK2d7oXDnBEGhpkQuQrgc91A= @@ -92,6 +92,8 @@ github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebK github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498= github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE= +github.com/go-ego/gse v0.80.3 h1:YNFkjMhlhQnUeuoFcUEd1ivh6SOB764rT8GDsEbDiEg= +github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg0gU= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -192,6 +194,9 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4= +github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik= +github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ= github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5 h1:HsMcBsVpYuQv+W8pjX5WdwYROrFQP9c5Pbf4x4adDus= diff --git a/plugin/thesaurus/chat.go b/plugin/thesaurus/chat.go index 47fc4d0c08..268e3e8400 100644 --- a/plugin/thesaurus/chat.go +++ b/plugin/thesaurus/chat.go @@ -2,11 +2,10 @@ package thesaurus import ( - "bytes" "math/rand" "strings" - "github.com/fumiama/jieba" + "github.com/go-ego/gse" "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -55,11 +54,8 @@ func init() { ctx.SendChain(message.Text("成功!")) }) go func() { - data, err := engine.GetLazyData("dict.txt", false) - if err != nil { - panic(err) - } - seg, err := jieba.LoadDictionary(bytes.NewReader(data)) + var seg gse.Segmenter + err := seg.LoadDictEmbed() if err != nil { panic(err) } @@ -102,10 +98,10 @@ func init() { ctx.SendChain(message.Text(r.Reply)) } }) - engine.OnMessage(zero.OnlyToMe, canmatch(tDERE), match(chatListD, seg)). + engine.OnMessage(zero.OnlyToMe, canmatch(tDERE), match(chatListD, &seg)). SetBlock(false). Handle(randreply(sm.D)) - engine.OnMessage(zero.OnlyToMe, canmatch(tKAWA), match(chatListK, seg)). + engine.OnMessage(zero.OnlyToMe, canmatch(tKAWA), match(chatListK, &seg)). SetBlock(false). Handle(randreply(sm.K)) }() @@ -122,7 +118,7 @@ const ( tKAWA ) -func match(l []string, seg *jieba.Segmenter) zero.Rule { +func match(l []string, seg *gse.Segmenter) zero.Rule { return func(ctx *zero.Ctx) bool { return ctxext.JiebaSimilarity(0.66, seg, func(ctx *zero.Ctx) string { return ctx.ExtractPlainText() From c8889364894134abf34f9f398c209efadf017231 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 20:58:41 +0900 Subject: [PATCH 08/51] chore: bump deps (#1166) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 0b05be7f2e..09e32fb913 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -8,8 +8,8 @@ schema = 3 version = "v1.7.1-0.20250423082452-e16339a3962c" hash = "sha256-pyv242GLfslHEgQN5TgYsdpJKWzWR8qc4mJ8NXF0I8s=" [mod."github.com/FloatTech/floatbox"] - version = "v0.0.0-20241106130736-5aea0a935024" - hash = "sha256-hSKmkzpNZwXRo0qm4G+1lXkNzWMwV9leYlYLQuzWx3M=" + version = "v0.0.0-20250513111443-adba80e84e80" + hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE=" [mod."github.com/FloatTech/gg"] version = "v1.1.3" hash = "sha256-7K/R2mKjUHVnoJ3b1wDObJ5Un2Htj59Y97G1Ja1tuPo=" @@ -112,6 +112,9 @@ schema = 3 [mod."github.com/gabriel-vasile/mimetype"] version = "v1.0.4" hash = "sha256-5hl9zBo3nkPt8dZfcLoOix8lAKLm3qIkWhopoS4V34E=" + [mod."github.com/go-ego/gse"] + version = "v0.80.3" + hash = "sha256-uxTQN4cxE/ZReZqjlIEQ3WYD9w2Ec37LRHQftJXsSZQ=" [mod."github.com/go-ole/go-ole"] version = "v1.2.6" hash = "sha256-+oxitLeJxYF19Z6g+6CgmCHJ1Y5D8raMi2Cb3M6nXCs=" @@ -217,6 +220,9 @@ schema = 3 [mod."github.com/tklauser/numcpus"] version = "v0.6.1" hash = "sha256-8eFcw4YI0w6+GPhU5xMMQjiio94q/O5PpNO3QsvXve0=" + [mod."github.com/vcaesar/cedar"] + version = "v0.20.2" + hash = "sha256-3WblBdkR9AZcvZCKSteBV5kdhahiFHG2dbLWfwrVkwM=" [mod."github.com/wcharczuk/go-chart/v2"] version = "v2.1.2" hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM=" From 076b1134554fa78479ba316a03834eb5732cca8b Mon Sep 17 00:00:00 2001 From: Dodoj <31199261+Doordoorjay@users.noreply.github.com> Date: Tue, 13 May 2025 22:05:24 +1000 Subject: [PATCH 09/51] =?UTF-8?q?fix(wordcount):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=88=86=E8=AF=8D=E6=A8=A1=E5=9D=97=E8=87=B3=E5=A4=96=E9=83=A8?= =?UTF-8?q?gse=E4=BB=93=E5=BA=93=20(#1165)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com> --- plugin/wordcount/main.go | 416 ++++++++++++++++++++------------------- 1 file changed, 214 insertions(+), 202 deletions(-) diff --git a/plugin/wordcount/main.go b/plugin/wordcount/main.go index bb91222d71..1549388b80 100644 --- a/plugin/wordcount/main.go +++ b/plugin/wordcount/main.go @@ -1,202 +1,214 @@ -// Package wordcount 聊天热词 -package wordcount - -import ( - "fmt" - "os" - "regexp" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/FloatTech/floatbox/binary" - fcext "github.com/FloatTech/floatbox/ctxext" - "github.com/FloatTech/floatbox/file" - ctrl "github.com/FloatTech/zbpctrl" - "github.com/FloatTech/zbputils/control" - "github.com/FloatTech/zbputils/ctxext" - "github.com/FloatTech/zbputils/img/text" - "github.com/golang/freetype" - "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" - "github.com/wcharczuk/go-chart/v2" - zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/message" -) - -var ( - re = regexp.MustCompile(`^[一-龥]+$`) - stopwords []string -) - -func init() { - engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ - DisableOnDefault: false, - Brief: "聊天热词", - Help: "- 热词 [群号] [消息数目]|热词 123456 1000", - PublicDataFolder: "WordCount", - }) - cachePath := engine.DataFolder() + "cache/" - _ = os.RemoveAll(cachePath) - _ = os.MkdirAll(cachePath, 0755) - engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { - _, err := engine.GetLazyData("stopwords.txt", false) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return false - } - data, err := os.ReadFile(engine.DataFolder() + "stopwords.txt") - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return false - } - stopwords = strings.Split(strings.ReplaceAll(binary.BytesToString(data), "\r", ""), "\n") - sort.Strings(stopwords) - logrus.Infoln("[wordcount]加载", len(stopwords), "条停用词") - return true - })).Limit(ctxext.LimitByUser).SetBlock(true). - Handle(func(ctx *zero.Ctx) { - _, err := file.GetLazyData(text.FontFile, control.Md5File, true) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - b, err := os.ReadFile(text.FontFile) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - font, err := freetype.ParseFont(b) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - - ctx.SendChain(message.Text("少女祈祷中...")) - gid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) - p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64) - if p > 10000 { - p = 10000 - } - if p == 0 { - p = 1000 - } - if gid == 0 { - gid = ctx.Event.GroupID - } - group := ctx.GetGroupInfo(gid, false) - if group.MemberCount == 0 { - ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获得热词呢")) - return - } - today := time.Now().Format("20060102") - drawedFile := fmt.Sprintf("%s%d%s%dwordCount.png", cachePath, gid, today, p) - if file.IsExist(drawedFile) { - ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) - return - } - messageMap := make(map[string]int, 256) - msghists := make(chan *gjson.Result, 256) - go func() { - h := ctx.GetLatestGroupMessageHistory(gid) - messageSeq := h.Get("messages.0.message_seq").Int() - msghists <- &h - for i := 1; i < int(p/20) && messageSeq != 0; i++ { - h := ctx.GetGroupMessageHistory(gid, messageSeq) - msghists <- &h - messageSeq = h.Get("messages.0.message_seq").Int() - } - close(msghists) - }() - var wg sync.WaitGroup - var mapmu sync.Mutex - for h := range msghists { - wg.Add(1) - go func(h *gjson.Result) { - for _, v := range h.Get("messages.#.message").Array() { - tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText()) - if tex == "" { - continue - } - for _, t := range ctx.GetWordSlices(tex).Get("slices").Array() { - tex := strings.TrimSpace(t.Str) - i := sort.SearchStrings(stopwords, tex) - if re.MatchString(tex) && (i >= len(stopwords) || stopwords[i] != tex) { - mapmu.Lock() - messageMap[tex]++ - mapmu.Unlock() - } - } - } - wg.Done() - }(h) - } - wg.Wait() - - wc := rankByWordCount(messageMap) - if len(wc) > 20 { - wc = wc[:20] - } - // 绘图 - if len(wc) == 0 { - ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息")) - return - } - bars := make([]chart.Value, len(wc)) - for i, v := range wc { - bars[i] = chart.Value{ - Value: float64(v.Value), - Label: v.Key, - } - } - graph := chart.BarChart{ - Font: font, - Title: fmt.Sprintf("%s(%d)在%s号的%d条消息的热词top20", group.Name, gid, time.Now().Format("2006-01-02"), p), - Background: chart.Style{ - Padding: chart.Box{ - Top: 40, - }, - }, - Height: 500, - BarWidth: 25, - Bars: bars, - } - f, err := os.Create(drawedFile) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - err = graph.Render(chart.PNG, f) - _ = f.Close() - if err != nil { - _ = os.Remove(drawedFile) - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) - }) -} - -func rankByWordCount(wordFrequencies map[string]int) pairlist { - pl := make(pairlist, len(wordFrequencies)) - i := 0 - for k, v := range wordFrequencies { - pl[i] = pair{k, v} - i++ - } - sort.Sort(sort.Reverse(pl)) - return pl -} - -type pair struct { - Key string - Value int -} - -type pairlist []pair - -func (p pairlist) Len() int { return len(p) } -func (p pairlist) Less(i, j int) bool { return p[i].Value < p[j].Value } -func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +// Package wordcount 聊天热词 +package wordcount + +import ( + "fmt" + "os" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/go-ego/gse" + "github.com/golang/freetype" + "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + "github.com/wcharczuk/go-chart/v2" + + "github.com/FloatTech/floatbox/binary" + fcext "github.com/FloatTech/floatbox/ctxext" + "github.com/FloatTech/floatbox/file" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/ctxext" + "github.com/FloatTech/zbputils/img/text" + + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + "github.com/wdvxdr1123/ZeroBot/utils/helper" +) + +var ( + re = regexp.MustCompile(`^[一-龥]+$`) + stopwords []string + seg gse.Segmenter +) + +func init() { + engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Brief: "聊天热词", + Help: "- 热词 [群号] [消息数目]|热词 123456 1000", + PublicDataFolder: "WordCount", + }) + cachePath := engine.DataFolder() + "cache/" + // 读取gse内置中文词典 + err := seg.LoadDictEmbed() + if err != nil { + panic(err) + } + _ = os.RemoveAll(cachePath) + _ = os.MkdirAll(cachePath, 0755) + engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { + _, err := engine.GetLazyData("stopwords.txt", false) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return false + } + data, err := os.ReadFile(engine.DataFolder() + "stopwords.txt") + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return false + } + stopwords = strings.Split(strings.ReplaceAll(binary.BytesToString(data), "\r", ""), "\n") + sort.Strings(stopwords) + logrus.Infoln("[wordcount]加载", len(stopwords), "条停用词") + return true + })).Limit(ctxext.LimitByUser).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + _, err := file.GetLazyData(text.FontFile, control.Md5File, true) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + b, err := os.ReadFile(text.FontFile) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + font, err := freetype.ParseFont(b) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + ctx.SendChain(message.Text("少女祈祷中...")) + gid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) + p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64) + if p > 10000 { + p = 10000 + } + if p == 0 { + p = 1000 + } + if gid == 0 { + gid = ctx.Event.GroupID + } + group := ctx.GetGroupInfo(gid, false) + if group.MemberCount == 0 { + ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获得热词呢")) + return + } + today := time.Now().Format("20060102") + drawedFile := fmt.Sprintf("%s%d%s%dwordCount.png", cachePath, gid, today, p) + if file.IsExist(drawedFile) { + ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) + return + } + messageMap := make(map[string]int, 256) + msghists := make(chan *gjson.Result, 256) + go func() { + h := ctx.GetLatestGroupMessageHistory(gid) + messageSeq := h.Get("messages.0.message_seq").Int() + msghists <- &h + for i := 1; i < int(p/20) && messageSeq != 0; i++ { + h := ctx.GetGroupMessageHistory(gid, messageSeq) + msghists <- &h + messageSeq = h.Get("messages.0.message_seq").Int() + } + close(msghists) + }() + var wg sync.WaitGroup + var mapmu sync.Mutex + for h := range msghists { + wg.Add(1) + go func(h *gjson.Result) { + for _, v := range h.Get("messages.#.message").Array() { + tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText()) + if tex == "" { + continue + } + segments := seg.Segment(helper.StringToBytes(tex)) + words := gse.ToSlice(segments, true) + for _, word := range words { + word = strings.TrimSpace(word) + i := sort.SearchStrings(stopwords, word) + if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) { + mapmu.Lock() + messageMap[word]++ + mapmu.Unlock() + } + } + } + wg.Done() + }(h) + } + wg.Wait() + + wc := rankByWordCount(messageMap) + if len(wc) > 20 { + wc = wc[:20] + } + // 绘图 + if len(wc) == 0 { + ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息")) + return + } + bars := make([]chart.Value, len(wc)) + for i, v := range wc { + bars[i] = chart.Value{ + Value: float64(v.Value), + Label: v.Key, + } + } + graph := chart.BarChart{ + Font: font, + Title: fmt.Sprintf("%s(%d)在%s号的%d条消息的热词top20", group.Name, gid, time.Now().Format("2006-01-02"), p), + Background: chart.Style{ + Padding: chart.Box{ + Top: 40, + }, + }, + Height: 500, + BarWidth: 25, + Bars: bars, + } + f, err := os.Create(drawedFile) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + err = graph.Render(chart.PNG, f) + _ = f.Close() + if err != nil { + _ = os.Remove(drawedFile) + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) + }) +} + +func rankByWordCount(wordFrequencies map[string]int) pairlist { + pl := make(pairlist, len(wordFrequencies)) + i := 0 + for k, v := range wordFrequencies { + pl[i] = pair{k, v} + i++ + } + sort.Sort(sort.Reverse(pl)) + return pl +} + +type pair struct { + Key string + Value int +} + +type pairlist []pair + +func (p pairlist) Len() int { return len(p) } +func (p pairlist) Less(i, j int) bool { return p[i].Value < p[j].Value } +func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] } From 42fe124b098c5ab2108e16314293e95e04e957aa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 15:42:18 +0900 Subject: [PATCH 10/51] =?UTF-8?q?chore(lint):=20=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=B7=E5=BC=8F=20(#1167)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- plugin/wordcount/main.go | 428 +++++++++++++++++++-------------------- 1 file changed, 214 insertions(+), 214 deletions(-) diff --git a/plugin/wordcount/main.go b/plugin/wordcount/main.go index 1549388b80..5e58069fa3 100644 --- a/plugin/wordcount/main.go +++ b/plugin/wordcount/main.go @@ -1,214 +1,214 @@ -// Package wordcount 聊天热词 -package wordcount - -import ( - "fmt" - "os" - "regexp" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/go-ego/gse" - "github.com/golang/freetype" - "github.com/sirupsen/logrus" - "github.com/tidwall/gjson" - "github.com/wcharczuk/go-chart/v2" - - "github.com/FloatTech/floatbox/binary" - fcext "github.com/FloatTech/floatbox/ctxext" - "github.com/FloatTech/floatbox/file" - ctrl "github.com/FloatTech/zbpctrl" - "github.com/FloatTech/zbputils/control" - "github.com/FloatTech/zbputils/ctxext" - "github.com/FloatTech/zbputils/img/text" - - zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/message" - "github.com/wdvxdr1123/ZeroBot/utils/helper" -) - -var ( - re = regexp.MustCompile(`^[一-龥]+$`) - stopwords []string - seg gse.Segmenter -) - -func init() { - engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ - DisableOnDefault: false, - Brief: "聊天热词", - Help: "- 热词 [群号] [消息数目]|热词 123456 1000", - PublicDataFolder: "WordCount", - }) - cachePath := engine.DataFolder() + "cache/" - // 读取gse内置中文词典 - err := seg.LoadDictEmbed() - if err != nil { - panic(err) - } - _ = os.RemoveAll(cachePath) - _ = os.MkdirAll(cachePath, 0755) - engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { - _, err := engine.GetLazyData("stopwords.txt", false) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return false - } - data, err := os.ReadFile(engine.DataFolder() + "stopwords.txt") - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return false - } - stopwords = strings.Split(strings.ReplaceAll(binary.BytesToString(data), "\r", ""), "\n") - sort.Strings(stopwords) - logrus.Infoln("[wordcount]加载", len(stopwords), "条停用词") - return true - })).Limit(ctxext.LimitByUser).SetBlock(true). - Handle(func(ctx *zero.Ctx) { - _, err := file.GetLazyData(text.FontFile, control.Md5File, true) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - b, err := os.ReadFile(text.FontFile) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - font, err := freetype.ParseFont(b) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - - ctx.SendChain(message.Text("少女祈祷中...")) - gid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) - p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64) - if p > 10000 { - p = 10000 - } - if p == 0 { - p = 1000 - } - if gid == 0 { - gid = ctx.Event.GroupID - } - group := ctx.GetGroupInfo(gid, false) - if group.MemberCount == 0 { - ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获得热词呢")) - return - } - today := time.Now().Format("20060102") - drawedFile := fmt.Sprintf("%s%d%s%dwordCount.png", cachePath, gid, today, p) - if file.IsExist(drawedFile) { - ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) - return - } - messageMap := make(map[string]int, 256) - msghists := make(chan *gjson.Result, 256) - go func() { - h := ctx.GetLatestGroupMessageHistory(gid) - messageSeq := h.Get("messages.0.message_seq").Int() - msghists <- &h - for i := 1; i < int(p/20) && messageSeq != 0; i++ { - h := ctx.GetGroupMessageHistory(gid, messageSeq) - msghists <- &h - messageSeq = h.Get("messages.0.message_seq").Int() - } - close(msghists) - }() - var wg sync.WaitGroup - var mapmu sync.Mutex - for h := range msghists { - wg.Add(1) - go func(h *gjson.Result) { - for _, v := range h.Get("messages.#.message").Array() { - tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText()) - if tex == "" { - continue - } - segments := seg.Segment(helper.StringToBytes(tex)) - words := gse.ToSlice(segments, true) - for _, word := range words { - word = strings.TrimSpace(word) - i := sort.SearchStrings(stopwords, word) - if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) { - mapmu.Lock() - messageMap[word]++ - mapmu.Unlock() - } - } - } - wg.Done() - }(h) - } - wg.Wait() - - wc := rankByWordCount(messageMap) - if len(wc) > 20 { - wc = wc[:20] - } - // 绘图 - if len(wc) == 0 { - ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息")) - return - } - bars := make([]chart.Value, len(wc)) - for i, v := range wc { - bars[i] = chart.Value{ - Value: float64(v.Value), - Label: v.Key, - } - } - graph := chart.BarChart{ - Font: font, - Title: fmt.Sprintf("%s(%d)在%s号的%d条消息的热词top20", group.Name, gid, time.Now().Format("2006-01-02"), p), - Background: chart.Style{ - Padding: chart.Box{ - Top: 40, - }, - }, - Height: 500, - BarWidth: 25, - Bars: bars, - } - f, err := os.Create(drawedFile) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - err = graph.Render(chart.PNG, f) - _ = f.Close() - if err != nil { - _ = os.Remove(drawedFile) - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) - }) -} - -func rankByWordCount(wordFrequencies map[string]int) pairlist { - pl := make(pairlist, len(wordFrequencies)) - i := 0 - for k, v := range wordFrequencies { - pl[i] = pair{k, v} - i++ - } - sort.Sort(sort.Reverse(pl)) - return pl -} - -type pair struct { - Key string - Value int -} - -type pairlist []pair - -func (p pairlist) Len() int { return len(p) } -func (p pairlist) Less(i, j int) bool { return p[i].Value < p[j].Value } -func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] } +// Package wordcount 聊天热词 +package wordcount + +import ( + "fmt" + "os" + "regexp" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/go-ego/gse" + "github.com/golang/freetype" + "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + "github.com/wcharczuk/go-chart/v2" + + "github.com/FloatTech/floatbox/binary" + fcext "github.com/FloatTech/floatbox/ctxext" + "github.com/FloatTech/floatbox/file" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/ctxext" + "github.com/FloatTech/zbputils/img/text" + + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + "github.com/wdvxdr1123/ZeroBot/utils/helper" +) + +var ( + re = regexp.MustCompile(`^[一-龥]+$`) + stopwords []string + seg gse.Segmenter +) + +func init() { + engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Brief: "聊天热词", + Help: "- 热词 [群号] [消息数目]|热词 123456 1000", + PublicDataFolder: "WordCount", + }) + cachePath := engine.DataFolder() + "cache/" + // 读取gse内置中文词典 + err := seg.LoadDictEmbed() + if err != nil { + panic(err) + } + _ = os.RemoveAll(cachePath) + _ = os.MkdirAll(cachePath, 0755) + engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { + _, err := engine.GetLazyData("stopwords.txt", false) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return false + } + data, err := os.ReadFile(engine.DataFolder() + "stopwords.txt") + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return false + } + stopwords = strings.Split(strings.ReplaceAll(binary.BytesToString(data), "\r", ""), "\n") + sort.Strings(stopwords) + logrus.Infoln("[wordcount]加载", len(stopwords), "条停用词") + return true + })).Limit(ctxext.LimitByUser).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + _, err := file.GetLazyData(text.FontFile, control.Md5File, true) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + b, err := os.ReadFile(text.FontFile) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + font, err := freetype.ParseFont(b) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + ctx.SendChain(message.Text("少女祈祷中...")) + gid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) + p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64) + if p > 10000 { + p = 10000 + } + if p == 0 { + p = 1000 + } + if gid == 0 { + gid = ctx.Event.GroupID + } + group := ctx.GetGroupInfo(gid, false) + if group.MemberCount == 0 { + ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获得热词呢")) + return + } + today := time.Now().Format("20060102") + drawedFile := fmt.Sprintf("%s%d%s%dwordCount.png", cachePath, gid, today, p) + if file.IsExist(drawedFile) { + ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) + return + } + messageMap := make(map[string]int, 256) + msghists := make(chan *gjson.Result, 256) + go func() { + h := ctx.GetLatestGroupMessageHistory(gid) + messageSeq := h.Get("messages.0.message_seq").Int() + msghists <- &h + for i := 1; i < int(p/20) && messageSeq != 0; i++ { + h := ctx.GetGroupMessageHistory(gid, messageSeq) + msghists <- &h + messageSeq = h.Get("messages.0.message_seq").Int() + } + close(msghists) + }() + var wg sync.WaitGroup + var mapmu sync.Mutex + for h := range msghists { + wg.Add(1) + go func(h *gjson.Result) { + for _, v := range h.Get("messages.#.message").Array() { + tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText()) + if tex == "" { + continue + } + segments := seg.Segment(helper.StringToBytes(tex)) + words := gse.ToSlice(segments, true) + for _, word := range words { + word = strings.TrimSpace(word) + i := sort.SearchStrings(stopwords, word) + if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) { + mapmu.Lock() + messageMap[word]++ + mapmu.Unlock() + } + } + } + wg.Done() + }(h) + } + wg.Wait() + + wc := rankByWordCount(messageMap) + if len(wc) > 20 { + wc = wc[:20] + } + // 绘图 + if len(wc) == 0 { + ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息")) + return + } + bars := make([]chart.Value, len(wc)) + for i, v := range wc { + bars[i] = chart.Value{ + Value: float64(v.Value), + Label: v.Key, + } + } + graph := chart.BarChart{ + Font: font, + Title: fmt.Sprintf("%s(%d)在%s号的%d条消息的热词top20", group.Name, gid, time.Now().Format("2006-01-02"), p), + Background: chart.Style{ + Padding: chart.Box{ + Top: 40, + }, + }, + Height: 500, + BarWidth: 25, + Bars: bars, + } + f, err := os.Create(drawedFile) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + err = graph.Render(chart.PNG, f) + _ = f.Close() + if err != nil { + _ = os.Remove(drawedFile) + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) + }) +} + +func rankByWordCount(wordFrequencies map[string]int) pairlist { + pl := make(pairlist, len(wordFrequencies)) + i := 0 + for k, v := range wordFrequencies { + pl[i] = pair{k, v} + i++ + } + sort.Sort(sort.Reverse(pl)) + return pl +} + +type pair struct { + Key string + Value int +} + +type pairlist []pair + +func (p pairlist) Len() int { return len(p) } +func (p pairlist) Less(i, j int) bool { return p[i].Value < p[j].Value } +func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] } From 961fbb098e18fcb8016c5a828c847cb78bcfcc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Wed, 14 May 2025 21:54:07 +0900 Subject: [PATCH 11/51] =?UTF-8?q?=F0=9F=94=96=20v1.9.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kanban/banner/banner.go | 4 ++-- winres/winres.json | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/kanban/banner/banner.go b/kanban/banner/banner.go index 576141cd31..6784a3aa3a 100644 --- a/kanban/banner/banner.go +++ b/kanban/banner/banner.go @@ -3,13 +3,13 @@ package banner // Version ... -var Version = "v1.9.6" +var Version = "v1.9.7" // Copyright ... var Copyright = "© 2020 - 2025 FloatTech" // Banner ... var Banner = "* OneBot + ZeroBot + Golang\n" + - "* Version " + Version + " - 2025-03-30 23:46:38 +0900 JST\n" + + "* Version " + Version + " - 2025-05-14 21:52:40 +0900 JST\n" + "* Copyright " + Copyright + ". All Rights Reserved.\n" + "* Project: https://github.com/FloatTech/ZeroBot-Plugin" diff --git a/winres/winres.json b/winres/winres.json index 07387d7c77..5dc2799785 100644 --- a/winres/winres.json +++ b/winres/winres.json @@ -12,7 +12,7 @@ "0409": { "identity": { "name": "ZeroBot-Plugin", - "version": "1.9.6.2202" + "version": "1.9.7.2217" }, "description": "", "minimum-os": "vista", @@ -36,23 +36,23 @@ "#1": { "0000": { "fixed": { - "file_version": "1.9.6.2202", - "product_version": "v1.9.6", - "timestamp": "2025-03-30T23:46:55+08:00" + "file_version": "1.9.7.2217", + "product_version": "v1.9.7", + "timestamp": "2025-05-14T21:53:06+08:00" }, "info": { "0409": { "Comments": "OneBot plugins based on ZeroBot", "CompanyName": "FloatTech", "FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin", - "FileVersion": "1.9.6.2202", + "FileVersion": "1.9.7.2217", "InternalName": "", "LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.", "LegalTrademarks": "", "OriginalFilename": "ZBP.EXE", "PrivateBuild": "", "ProductName": "ZeroBot-Plugin", - "ProductVersion": "v1.9.6", + "ProductVersion": "v1.9.7", "SpecialBuild": "" } } From 609d819610403963ff6d2618c9468b8129bed971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 1 Jun 2025 15:06:20 +0900 Subject: [PATCH 12/51] chore: sync data --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index ca3652920a..4f751a1cda 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit ca3652920a2be63314adc32dc4b78ff7f2be4aff +Subproject commit 4f751a1cda692b5b8cb909ce1376a3a0ea0d57cc From f70cab80c2106542036a664f85f765f2ab818d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 1 Jun 2025 15:49:03 +0900 Subject: [PATCH 13/51] feat(aichat): add more configs --- README.md | 2 ++ go.mod | 4 +-- go.sum | 8 +++--- plugin/aichat/cfg.go | 57 +++++++++++++++++++++++++++++++++++++++++++ plugin/aichat/main.go | 22 ++++++++++++++--- 5 files changed, 83 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 54f59afd6b..f48d83effb 100644 --- a/README.md +++ b/README.md @@ -1588,6 +1588,8 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置AI聊天系统提示词xxx - [x] 设置AI聊天分隔符``(留空则清除) - [x] 设置AI聊天(不)响应AT + - [x] 设置AI聊天最大长度4096 + - [x] 设置AI聊天TopP 0.9
diff --git a/go.mod b/go.mod index 22f92eb0e1..f9cde00b41 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/Baidu-AIP/golang-sdk v1.1.1 - github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c + github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 github.com/FloatTech/gg v1.1.3 github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef @@ -12,7 +12,7 @@ require ( github.com/FloatTech/sqlite v1.7.1 github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 github.com/FloatTech/zbpctrl v1.7.0 - github.com/FloatTech/zbputils v1.7.2-0.20250330125231-d8be1c9d3b9c + github.com/FloatTech/zbputils v1.7.2-0.20250601064658-421e71926b83 github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 github.com/Tnze/go-mc v1.20.2 diff --git a/go.sum b/go.sum index 6b04ccf9d2..34666119f9 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw= github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c h1:bEe8VP2aHLR2NHk1BsBQFtP0XE3cxquvr0tW0CkKcDk= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250423082452-e16339a3962c/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b h1:H/1xpchTGmdoHqrszH4gjafCyHIhsGSFryAkBNsu8OI= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 h1:lFD1pd8NkYCrw0QpTX/T5pJ67I7AL5eGxQ4v0r9f81Q= github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80/go.mod h1:IWoFFqu+0FeaHHQdddyiTRL5z7gJME6qHC96qh0R2sc= github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4= @@ -17,8 +17,8 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo= github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE= -github.com/FloatTech/zbputils v1.7.2-0.20250330125231-d8be1c9d3b9c h1:nIybmanPvQknseVOJ+s4/m3q7EZxtqMoTy3wiiZts6E= -github.com/FloatTech/zbputils v1.7.2-0.20250330125231-d8be1c9d3b9c/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= +github.com/FloatTech/zbputils v1.7.2-0.20250601064658-421e71926b83 h1:WfHzSblV7TZ/32cm1HUsfxTXUDKauzUYq5dbI1L+w3s= +github.com/FloatTech/zbputils v1.7.2-0.20250601064658-421e71926b83/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= diff --git a/plugin/aichat/cfg.go b/plugin/aichat/cfg.go index 545e3e2c7e..2d40bf04e3 100644 --- a/plugin/aichat/cfg.go +++ b/plugin/aichat/cfg.go @@ -1,6 +1,7 @@ package aichat import ( + "strconv" "strings" ctrl "github.com/FloatTech/zbpctrl" @@ -17,6 +18,8 @@ var cfg = newconfig() type config struct { ModelName string Type int + MaxN uint + TopP float32 SystemP string API string Key string @@ -94,3 +97,57 @@ func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) { ctx.SendChain(message.Text("成功")) } } + +func newextrasetuint(ptr *uint) func(ctx *zero.Ctx) { + return func(ctx *zero.Ctx) { + args := strings.TrimSpace(ctx.State["args"].(string)) + if args == "" { + ctx.SendChain(message.Text("ERROR: empty args")) + return + } + c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) + if !ok { + ctx.SendChain(message.Text("ERROR: no such plugin")) + return + } + n, err := strconv.ParseUint(args, 10, 64) + if err != nil { + ctx.SendChain(message.Text("ERROR: parse args err: ", err)) + return + } + *ptr = uint(n) + err = c.SetExtra(&cfg) + if err != nil { + ctx.SendChain(message.Text("ERROR: set extra err: ", err)) + return + } + ctx.SendChain(message.Text("成功")) + } +} + +func newextrasetfloat32(ptr *float32) func(ctx *zero.Ctx) { + return func(ctx *zero.Ctx) { + args := strings.TrimSpace(ctx.State["args"].(string)) + if args == "" { + ctx.SendChain(message.Text("ERROR: empty args")) + return + } + c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) + if !ok { + ctx.SendChain(message.Text("ERROR: no such plugin")) + return + } + n, err := strconv.ParseFloat(args, 32) + if err != nil { + ctx.SendChain(message.Text("ERROR: parse args err: ", err)) + return + } + *ptr = float32(n) + err = c.SetExtra(&cfg) + if err != nil { + ctx.SendChain(message.Text("ERROR: set extra err: ", err)) + return + } + ctx.SendChain(message.Text("成功")) + } +} diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index 25bd4dce9f..3cfa224060 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -36,7 +36,9 @@ var ( "- 重置AI聊天系统提示词\n" + "- 设置AI聊天系统提示词xxx\n" + "- 设置AI聊天分隔符(留空则清除)\n" + - "- 设置AI聊天(不)响应AT", + "- 设置AI聊天(不)响应AT\n" + + "- 设置AI聊天最大长度4096\n" + + "- 设置AI聊天TopP 0.9", PrivateDataFolder: "aichat", }) ) @@ -83,22 +85,30 @@ func init() { x := deepinfra.NewAPI(cfg.API, cfg.Key) var mod model.Protocol + maxn := cfg.MaxN + if maxn == 0 { + maxn = 4096 + } + topp := cfg.TopP + if topp == 0 { + topp = 0.9 + } switch cfg.Type { case 0: mod = model.NewOpenAI( cfg.ModelName, cfg.Separator, - float32(temp)/100, 0.9, 4096, + float32(temp)/100, topp, maxn, ) case 1: mod = model.NewOLLaMA( cfg.ModelName, cfg.Separator, - float32(temp)/100, 0.9, 4096, + float32(temp)/100, topp, maxn, ) case 2: mod = model.NewGenAI( cfg.ModelName, - float32(temp)/100, 0.9, 4096, + float32(temp)/100, topp, maxn, ) default: logrus.Warnln("[aichat] unsupported AI type", cfg.Type) @@ -255,4 +265,8 @@ func init() { Handle(newextrasetbool(&cfg.NoReplyAT)) en.OnRegex("^设置AI聊天(不)?支持系统提示词$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetbool(&cfg.NoSystemP)) + en.OnPrefix("设置AI聊天最大长度", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(newextrasetuint(&cfg.MaxN)) + en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(newextrasetfloat32(&cfg.TopP)) } From 426905728342e9e7a549978f0c70edaec4ed4acb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 16:27:17 +0900 Subject: [PATCH 14/51] chore: bump deps (#1170) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 09e32fb913..df712041ed 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -5,8 +5,8 @@ schema = 3 version = "v1.1.1" hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo=" [mod."github.com/FloatTech/AnimeAPI"] - version = "v1.7.1-0.20250423082452-e16339a3962c" - hash = "sha256-pyv242GLfslHEgQN5TgYsdpJKWzWR8qc4mJ8NXF0I8s=" + version = "v1.7.1-0.20250530055006-50f5c7587c5b" + hash = "sha256-xDXPwphMS26J02q/ysQy3CJPK5B9c7uoqwFt1xFIF5Q=" [mod."github.com/FloatTech/floatbox"] version = "v0.0.0-20250513111443-adba80e84e80" hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE=" @@ -29,8 +29,8 @@ schema = 3 version = "v1.7.0" hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU=" [mod."github.com/FloatTech/zbputils"] - version = "v1.7.2-0.20250330125231-d8be1c9d3b9c" - hash = "sha256-v2ueCLEwy6oqF8HoxodDHcNOeDPzKn+7fiYSC2L2/e4=" + version = "v1.7.2-0.20250601064658-421e71926b83" + hash = "sha256-6yA8sN2+PIFhdUvbtI91s3ZFL2nPzw9UgdKchtztgD0=" [mod."github.com/RomiChan/syncx"] version = "v0.0.0-20240418144900-b7402ffdebc7" hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4=" From 997857a55838cdceeeb377def8a6802bdbecf406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:56:37 +0900 Subject: [PATCH 15/51] chore: update deps --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f9cde00b41..8bc63fc31f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/FloatTech/sqlite v1.7.1 github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 github.com/FloatTech/zbpctrl v1.7.0 - github.com/FloatTech/zbputils v1.7.2-0.20250601064658-421e71926b83 + github.com/FloatTech/zbputils v1.7.2-0.20250601085303-b2a05f133d5b github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 github.com/Tnze/go-mc v1.20.2 diff --git a/go.sum b/go.sum index 34666119f9..3fbc893032 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo= github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE= -github.com/FloatTech/zbputils v1.7.2-0.20250601064658-421e71926b83 h1:WfHzSblV7TZ/32cm1HUsfxTXUDKauzUYq5dbI1L+w3s= -github.com/FloatTech/zbputils v1.7.2-0.20250601064658-421e71926b83/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= +github.com/FloatTech/zbputils v1.7.2-0.20250601085303-b2a05f133d5b h1:oaC5dvVuyCL/uJUskD4OIEGva6BsqNkR4TRCnWS1jE4= +github.com/FloatTech/zbputils v1.7.2-0.20250601085303-b2a05f133d5b/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= From 5b28ad75b7728614f6067f6533167d7efc42c9b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 17:58:46 +0900 Subject: [PATCH 16/51] chore: bump deps (#1171) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index df712041ed..9e69920623 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -29,8 +29,8 @@ schema = 3 version = "v1.7.0" hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU=" [mod."github.com/FloatTech/zbputils"] - version = "v1.7.2-0.20250601064658-421e71926b83" - hash = "sha256-6yA8sN2+PIFhdUvbtI91s3ZFL2nPzw9UgdKchtztgD0=" + version = "v1.7.2-0.20250601085303-b2a05f133d5b" + hash = "sha256-Z3hHIGpJOXYiX56JaM+XMzq/cCQQ/PxKEmpNWOIv52Q=" [mod."github.com/RomiChan/syncx"] version = "v0.0.0-20240418144900-b7402ffdebc7" hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4=" From 566f6ecfd561bde8f87d5c240e79953f1cf3b726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 1 Jun 2025 18:53:06 +0900 Subject: [PATCH 17/51] =?UTF-8?q?=F0=9F=94=96=20v1.9.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kanban/banner/banner.go | 4 ++-- winres/winres.json | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/kanban/banner/banner.go b/kanban/banner/banner.go index 6784a3aa3a..d224514b53 100644 --- a/kanban/banner/banner.go +++ b/kanban/banner/banner.go @@ -3,13 +3,13 @@ package banner // Version ... -var Version = "v1.9.7" +var Version = "v1.9.8" // Copyright ... var Copyright = "© 2020 - 2025 FloatTech" // Banner ... var Banner = "* OneBot + ZeroBot + Golang\n" + - "* Version " + Version + " - 2025-05-14 21:52:40 +0900 JST\n" + + "* Version " + Version + " - 2025-06-01 18:52:48 +0900 JST\n" + "* Copyright " + Copyright + ". All Rights Reserved.\n" + "* Project: https://github.com/FloatTech/ZeroBot-Plugin" diff --git a/winres/winres.json b/winres/winres.json index 5dc2799785..a29dc16b44 100644 --- a/winres/winres.json +++ b/winres/winres.json @@ -12,7 +12,7 @@ "0409": { "identity": { "name": "ZeroBot-Plugin", - "version": "1.9.7.2217" + "version": "1.9.8.2223" }, "description": "", "minimum-os": "vista", @@ -36,23 +36,23 @@ "#1": { "0000": { "fixed": { - "file_version": "1.9.7.2217", - "product_version": "v1.9.7", - "timestamp": "2025-05-14T21:53:06+08:00" + "file_version": "1.9.8.2223", + "product_version": "v1.9.8", + "timestamp": "2025-06-01T18:52:57+08:00" }, "info": { "0409": { "Comments": "OneBot plugins based on ZeroBot", "CompanyName": "FloatTech", "FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin", - "FileVersion": "1.9.7.2217", + "FileVersion": "1.9.8.2223", "InternalName": "", "LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.", "LegalTrademarks": "", "OriginalFilename": "ZBP.EXE", "PrivateBuild": "", "ProductName": "ZeroBot-Plugin", - "ProductVersion": "v1.9.7", + "ProductVersion": "v1.9.8", "SpecialBuild": "" } } From 95dd5e6b94ea3af0dec8730205e5d5abae6fa0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:30:46 +0900 Subject: [PATCH 18/51] chore: update deps --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8bc63fc31f..3ba42d4d45 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/FloatTech/sqlite v1.7.1 github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 github.com/FloatTech/zbpctrl v1.7.0 - github.com/FloatTech/zbputils v1.7.2-0.20250601085303-b2a05f133d5b + github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6 github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 github.com/Tnze/go-mc v1.20.2 @@ -22,7 +22,7 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/fumiama/ahsai v0.1.0 github.com/fumiama/cron v1.3.0 - github.com/fumiama/deepinfra v0.0.0-20250330125128-71ec2f7c085e + github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1 github.com/fumiama/go-base16384 v1.7.0 github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 diff --git a/go.sum b/go.sum index 3fbc893032..48ffe9bed8 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo= github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE= -github.com/FloatTech/zbputils v1.7.2-0.20250601085303-b2a05f133d5b h1:oaC5dvVuyCL/uJUskD4OIEGva6BsqNkR4TRCnWS1jE4= -github.com/FloatTech/zbputils v1.7.2-0.20250601085303-b2a05f133d5b/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= +github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6 h1:85LGKvgkWMZU065WOIEVaaeHDNYK01CwdNr/m+jzTKw= +github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= @@ -59,8 +59,8 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4= github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk= github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo= github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY= -github.com/fumiama/deepinfra v0.0.0-20250330125128-71ec2f7c085e h1:L/Z5N6UfpuqNIiPUrjSzmrnWj3mjd3auwl/2ctpGXNY= -github.com/fumiama/deepinfra v0.0.0-20250330125128-71ec2f7c085e/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= +github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1 h1:qE3l/y4Y1gMD2NokQ5Nw4NIUjL8ZwYLPIHOExQNu4hM= +github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA= github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs= From 43b45ce6c5f9bccf8489b122de8c8453550733f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Jun 2025 20:32:24 +0900 Subject: [PATCH 19/51] chore: bump deps (#1172) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 9e69920623..4f05f81d8f 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -29,8 +29,8 @@ schema = 3 version = "v1.7.0" hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU=" [mod."github.com/FloatTech/zbputils"] - version = "v1.7.2-0.20250601085303-b2a05f133d5b" - hash = "sha256-Z3hHIGpJOXYiX56JaM+XMzq/cCQQ/PxKEmpNWOIv52Q=" + version = "v1.7.2-0.20250601113004-1bee2a7cd4b6" + hash = "sha256-pNL591h1gGP60wKEuvvF3DoshbaphoLze7Pa7gDA9bQ=" [mod."github.com/RomiChan/syncx"] version = "v0.0.0-20240418144900-b7402ffdebc7" hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4=" @@ -77,8 +77,8 @@ schema = 3 version = "v1.3.0" hash = "sha256-/sN7X8dKXQgv8J+EDzVUB+o+AY9gBC8e1C6sYhaTy1k=" [mod."github.com/fumiama/deepinfra"] - version = "v0.0.0-20250330125128-71ec2f7c085e" - hash = "sha256-O7Om4mIcBB2zdVxaeKh7qHbiG83gVLP+f8vxSF17kCw=" + version = "v0.0.0-20250601112706-0175c95164c1" + hash = "sha256-/8Hufq5n84QHOgS0igYQWo1zxjOBBbrqad2wQfKHBhY=" [mod."github.com/fumiama/go-base16384"] version = "v1.7.0" hash = "sha256-vTAsBBYe2ISzb2Nba5E96unodZSkhMcqo6hbwR01nz8=" From beada7f4da42ef588060ec2b5559d3b5e0de2267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Sun, 15 Jun 2025 02:01:23 +0900 Subject: [PATCH 20/51] chore: update deps --- go.mod | 2 +- go.sum | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3ba42d4d45..6e5daf610a 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/FloatTech/sqlite v1.7.1 github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 github.com/FloatTech/zbpctrl v1.7.0 - github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6 + github.com/FloatTech/zbputils v1.7.2-0.20250614165821-95cf57cf2434 github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 github.com/Tnze/go-mc v1.20.2 diff --git a/go.sum b/go.sum index 48ffe9bed8..56d4f9662d 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo= github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE= -github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6 h1:85LGKvgkWMZU065WOIEVaaeHDNYK01CwdNr/m+jzTKw= -github.com/FloatTech/zbputils v1.7.2-0.20250601113004-1bee2a7cd4b6/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= +github.com/FloatTech/zbputils v1.7.2-0.20250614165821-95cf57cf2434 h1:oEYQFQ2/qx10FtZKCNbW3Ohj/Iw71aM4RWpIu+LMmf8= +github.com/FloatTech/zbputils v1.7.2-0.20250614165821-95cf57cf2434/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= @@ -97,6 +97,7 @@ github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -176,12 +177,14 @@ github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U3 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0= github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -197,6 +200,7 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4= github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik= github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= +github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU= github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ= github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5 h1:HsMcBsVpYuQv+W8pjX5WdwYROrFQP9c5Pbf4x4adDus= @@ -256,6 +260,7 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -307,6 +312,7 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -315,14 +321,22 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= +modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= +modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From cac3a4be8195c4a439da98b3629dfb45c64d0e81 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 02:03:23 +0900 Subject: [PATCH 21/51] =?UTF-8?q?chore(lint):=20=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=B7=E5=BC=8F=20(#1174)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- go.sum | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/go.sum b/go.sum index 56d4f9662d..ee423745dc 100644 --- a/go.sum +++ b/go.sum @@ -97,7 +97,6 @@ github.com/go-ego/gse v0.80.3/go.mod h1:Gt3A9Ry1Eso2Kza4MRaiZ7f2DTAvActmETY46Lxg github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -177,14 +176,12 @@ github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U3 github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.5.0 h1:Yz3fZHivfDiZFUXnWMPUoiW7s8tC1sjdBtlJn08qYa0= github.com/tetratelabs/wazero v1.5.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= @@ -200,7 +197,6 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f github.com/vcaesar/cedar v0.20.2 h1:TDx7AdZhilKcfE1WvdToTJf5VrC/FXcUOW+KY1upLZ4= github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik= github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= -github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU= github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ= github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5 h1:HsMcBsVpYuQv+W8pjX5WdwYROrFQP9c5Pbf4x4adDus= @@ -260,7 +256,6 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -312,7 +307,6 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -321,22 +315,14 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk= -modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs= -modernc.org/ccgo/v4 v4.17.8/go.mod h1:buJnJ6Fn0tyAdP/dqePbrrvLyr6qslFfTbFrCuaYvtA= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 19e5e6636fe306c97bc0112cadf9ba421237431f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 02:03:33 +0900 Subject: [PATCH 22/51] chore: bump deps (#1173) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 4f05f81d8f..92d90597cd 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -29,8 +29,8 @@ schema = 3 version = "v1.7.0" hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU=" [mod."github.com/FloatTech/zbputils"] - version = "v1.7.2-0.20250601113004-1bee2a7cd4b6" - hash = "sha256-pNL591h1gGP60wKEuvvF3DoshbaphoLze7Pa7gDA9bQ=" + version = "v1.7.2-0.20250614165821-95cf57cf2434" + hash = "sha256-9r2nbDAo/ZCDSAXuOHUKaQM5bEoSOAFb0FJTDxne/gQ=" [mod."github.com/RomiChan/syncx"] version = "v0.0.0-20240418144900-b7402ffdebc7" hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4=" From c94ee365ce3dc6b3324929ef4a57e8d6e571b0cf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 15 Jun 2025 17:04:45 +0900 Subject: [PATCH 23/51] chore: bump deps (#1175) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> From 1c0d91424adebd0a46a0491e5f82eb34c32eaf9d Mon Sep 17 00:00:00 2001 From: Rinai Date: Fri, 27 Jun 2025 16:49:50 +0800 Subject: [PATCH 24/51] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20niuniu=20?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=20bug=EF=BC=8C=E4=BF=AE=E6=94=B9=E6=A0=87?= =?UTF-8?q?=E7=82=B9=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=83=A8=E5=88=86=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=20(#1177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/niuniu/main.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/plugin/niuniu/main.go b/plugin/niuniu/main.go index 40028efb91..ed618c4460 100644 --- a/plugin/niuniu/main.go +++ b/plugin/niuniu/main.go @@ -81,7 +81,7 @@ func init() { for { select { case <-timer.C: - ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消")) + ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消")) return case r := <-recv: answer = r.Event.Message.String() @@ -109,6 +109,12 @@ func init() { ctx.SendChain(message.Text("ERROR:", err)) return } + // 数据库操作成功之后,及时删除残留的缓存 + key := fmt.Sprintf("%d_%d", gid, uid) + _, ok := jjCount.Load(key) + if ok { + jjCount.Delete(key) + } ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(sell)) }) en.OnFullMatch("牛牛背包", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { @@ -165,7 +171,7 @@ func init() { for { select { case <-timer.C: - ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消")) + ctx.SendChain(message.At(uid), message.Text(" 超时,已自动取消")) return case r := <-recv: answer = r.Event.Message.String() @@ -196,16 +202,16 @@ func init() { } if time.Since(last.TimeLimit) > time.Hour { - ctx.SendChain(message.Text("时间已经过期了,牛牛已被收回!")) + ctx.SendChain(message.Text("时间已经过期了,牛牛已被收回!")) jjCount.Delete(fmt.Sprintf("%d_%d", gid, uid)) return } if last.Count < 4 { - ctx.SendChain(message.Text("你还没有被厥够4次呢,不能赎牛牛")) + ctx.SendChain(message.Text("你还没有被厥够4次呢,不能赎牛牛")) return } - ctx.SendChain(message.Text("再次确认一下哦,这次赎牛牛,牛牛长度将会变成", last.Length, "cm\n还需要嘛【是|否】")) + ctx.SendChain(message.Text("再次确认一下哦,这次赎牛牛,牛牛长度将会变成", last.Length, "cm\n还需要嘛【是|否】")) recv, cancel := zero.NewFutureEvent("message", 999, false, zero.CheckUser(uid), zero.CheckGroup(gid), zero.RegexRule(`^(是|否)$`)).Repeat() defer cancel() timer := time.NewTimer(2 * time.Minute) @@ -222,11 +228,11 @@ func init() { return } - if err := niu.Redeem(gid, uid, last.Length); err == nil { + if err := niu.Redeem(gid, uid, last.Length); err != nil { ctx.SendChain(message.Text("ERROR:", err)) return } - + // 成功赎回,删除残留的缓存。 jjCount.Delete(fmt.Sprintf("%d_%d", gid, uid)) ctx.SendChain(message.At(uid), message.Text(fmt.Sprintf("恭喜你!成功赎回牛牛,当前长度为:%.2fcm", last.Length))) @@ -342,8 +348,9 @@ func init() { j := fmt.Sprintf("%d_%d", gid, adduser) count, ok := jjCount.Load(j) var c lastLength - // 按照最后一次被jj时的时间计算,超过60分钟则重置 + // 按照最后一次被 jj 时的时间计算,超过60分钟则重置 if !ok { + // 第一次被 jj c = lastLength{ TimeLimit: time.Now(), Count: 1, @@ -355,6 +362,7 @@ func init() { Count: count.Count + 1, Length: count.Length, } + // 超时了,重置 if time.Since(c.TimeLimit) > time.Hour { c = lastLength{ TimeLimit: time.Now(), From 06159932972bc576036885f1922015b26aa6e0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=87=7E?= <158024940+xyy0411@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:49:59 +0800 Subject: [PATCH 25/51] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B3=A8?= =?UTF-8?q?=E9=94=80=E7=89=9B=E7=89=9B=E6=97=A0=E6=B3=95=E8=BF=9B=E8=A1=8C?= =?UTF-8?q?=E7=B4=AF=E5=8A=A0=E6=94=B6=E8=B4=B9=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?&&=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=20(#1178)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix:修复注销牛牛无法进行累加收费的问题 * 修改牛牛商店的循环条件为商品变量的长度 --- plugin/niuniu/main.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugin/niuniu/main.go b/plugin/niuniu/main.go index ed618c4460..013903ddfc 100644 --- a/plugin/niuniu/main.go +++ b/plugin/niuniu/main.go @@ -65,7 +65,7 @@ func init() { messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text("牛牛拍卖行有以下牛牛"))) for _, info := range auction { msg := fmt.Sprintf("商品序号: %d\n牛牛原所属: %d\n牛牛价格: %d%s\n牛牛大小: %.2fcm", - info.ID+1, info.UserID, info.Money, wallet.GetWalletName(), info.Length) + info.ID, info.UserID, info.Money, wallet.GetWalletName(), info.Length) messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text(msg))) } if id := ctx.Send(messages).ID(); id == 0 { @@ -90,7 +90,6 @@ func init() { ctx.SendChain(message.Text("ERROR: ", err)) return } - n-- msg, err := niu.Auction(gid, uid, n) if err != nil { ctx.SendChain(message.Text("ERROR:", err)) @@ -151,7 +150,7 @@ func init() { var messages message.Message messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text("牛牛商店当前售卖的物品如下"))) - for id := range propMap { + for id := 1; id <= len(propMap); id++ { product := propMap[id] productInfo := fmt.Sprintf("商品%d\n商品名: %s\n商品价格: %dATRI币\n商品作用域: %s\n商品描述: %s\n使用次数:%d", id, product.name, product.cost, product.scope, product.description, product.count) @@ -380,6 +379,9 @@ func init() { ))) if c.Count >= 4 { + if c.Count == 6 { + return + } id := ctx.SendPrivateMessage(adduser, message.Text(fmt.Sprintf("你在%d群里已经被厥冒烟了,快去群里赎回你原本的牛牛!\n发送:`赎牛牛`即可!", gid))) if id == 0 { @@ -394,7 +396,7 @@ func init() { key := fmt.Sprintf("%d_%d", gid, uid) data, ok := register.Load(key) switch { - case !ok || time.Since(data.TimeLimit) > time.Hour*12: + case !ok || time.Since(data.TimeLimit) > time.Hour*24: data = &lastLength{ TimeLimit: time.Now(), Count: 1, @@ -404,6 +406,7 @@ func init() { ctx.SendChain(message.Text("你的钱不够你注销牛牛了,这次注销需要", data.Count*50, wallet.GetWalletName())) return } + data.Count++ } register.Store(key, data) msg, err := niu.Cancel(gid, uid) From cb0ffa0c17b59a72a618370196b804b6417e6228 Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:05:28 +0800 Subject: [PATCH 26/51] =?UTF-8?q?feat(music):=20=E9=BE=99=E7=8F=A0?= =?UTF-8?q?=E8=81=9A=E5=90=88=E6=90=9C=E7=B4=A2=20(#1179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 修改听歌问题 * ✨ 添加龙珠聚合搜索 * 🎨 优化聚合搜索 * 🐛 一定能点出歌 * 🎨 删除调试 --- README.md | 4 ++++ console/console_windows.go | 8 +++++++- plugin/music/selecter.go | 34 ++++++++++++++++++++++++++++++++-- plugin/score/model.go | 32 ++++++++++++++++++++++++-------- 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f48d83effb..5279927cd6 100644 --- a/README.md +++ b/README.md @@ -1026,6 +1026,10 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 酷我点歌[xxx] - [x] 酷狗点歌[xxx] + + - [x] qq点歌[xxx] + + - [x] 咪咕点歌[xxx]
diff --git a/console/console_windows.go b/console/console_windows.go index e4b5ed1b34..4b93161ec1 100644 --- a/console/console_windows.go +++ b/console/console_windows.go @@ -38,12 +38,18 @@ func setConsoleTitle(title string) (err error) { } func init() { + debugMode := os.Getenv("DEBUG_MODE") == "1" stdin := windows.Handle(os.Stdin.Fd()) var mode uint32 err := windows.GetConsoleMode(stdin, &mode) if err != nil { - panic(err) + if debugMode { + logrus.Warnf("调试模式下忽略控制台模式获取失败: %v", err) + return // 调试模式下直接返回,跳过后续配置 + } else { + panic(err) // 非调试模式下 panic + } } mode &^= windows.ENABLE_QUICK_EDIT_MODE // 禁用快速编辑模式 diff --git a/plugin/music/selecter.go b/plugin/music/selecter.go index 592438359e..5f43844404 100644 --- a/plugin/music/selecter.go +++ b/plugin/music/selecter.go @@ -21,6 +21,10 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" ) +var ( + longZhuURL = "https://www.hhlqilongzhu.cn/api/joox/juhe_music.php?msg=%v" +) + func init() { control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, @@ -29,7 +33,8 @@ func init() { "- 网易点歌[xxx]\n" + "- 酷我点歌[xxx]\n" + "- 酷狗点歌[xxx]\n" + - "- 咪咕点歌[xxx]", + "- 咪咕点歌[xxx]\n" + + "- qq点歌[xxx]\n", }).OnRegex(`^(.{0,2})点歌\s?(.{1,25})$`).SetBlock(true).Limit(ctxext.LimitByUser). Handle(func(ctx *zero.Ctx) { // switch 平台 @@ -42,12 +47,37 @@ func init() { ctx.SendChain(kugou(ctx.State["regex_matched"].([]string)[2])) case "网易": ctx.SendChain(cloud163(ctx.State["regex_matched"].([]string)[2])) - default: // 默认 QQ音乐 + case "qq": ctx.SendChain(qqmusic(ctx.State["regex_matched"].([]string)[2])) + default: // 默认聚合点歌 + ctx.SendChain(longzhu(ctx.State["regex_matched"].([]string)[2])) } }) } +// longzhu 聚合平台 +func longzhu(keyword string) message.Segment { + data, _ := web.GetData(fmt.Sprintf(longZhuURL, url.QueryEscape(keyword))) + // 假设 data 是包含整个 JSON 数组的字节切片 + results := gjson.ParseBytes(data).Array() + for _, result := range results { + if strings.Contains(strings.ToLower(result.Get("title").String()), strings.ToLower(keyword)) { + if musicURL := result.Get("full_track").String(); musicURL != "" { + return message.Record(musicURL) + } + } + } + + results = gjson.GetBytes(data, "#.full_track").Array() + if len(results) > 0 { + if musicURL := results[0].String(); musicURL != "" { + return message.Record(musicURL) + } + } + + return message.Text("点歌失败, 找不到 ", keyword, " 的相关结果") +} + // migu 返回咪咕音乐卡片 func migu(keyword string) message.Segment { headers := http.Header{ diff --git a/plugin/score/model.go b/plugin/score/model.go index e0e21d8a4f..95eaadb57d 100644 --- a/plugin/score/model.go +++ b/plugin/score/model.go @@ -2,6 +2,7 @@ package score import ( "os" + "sync" "time" "github.com/jinzhu/gorm" @@ -11,7 +12,10 @@ import ( var sdb *scoredb // scoredb 分数数据库 -type scoredb gorm.DB +type scoredb struct { + db *gorm.DB + scoremu sync.Mutex +} // scoretable 分数结构体 type scoretable struct { @@ -52,25 +56,31 @@ func initialize(dbpath string) *scoredb { panic(err) } gdb.AutoMigrate(&scoretable{}).AutoMigrate(&signintable{}) - return (*scoredb)(gdb) + return &scoredb{ + db: gdb, + } } // Close ... func (sdb *scoredb) Close() error { - db := (*gorm.DB)(sdb) + db := sdb.db return db.Close() } // GetScoreByUID 取得分数 func (sdb *scoredb) GetScoreByUID(uid int64) (s scoretable) { - db := (*gorm.DB)(sdb) + sdb.scoremu.Lock() + defer sdb.scoremu.Unlock() + db := sdb.db db.Model(&scoretable{}).FirstOrCreate(&s, "uid = ? ", uid) return s } // InsertOrUpdateScoreByUID 插入或更新分数 func (sdb *scoredb) InsertOrUpdateScoreByUID(uid int64, score int) (err error) { - db := (*gorm.DB)(sdb) + sdb.scoremu.Lock() + defer sdb.scoremu.Unlock() + db := sdb.db s := scoretable{ UID: uid, Score: score, @@ -91,14 +101,18 @@ func (sdb *scoredb) InsertOrUpdateScoreByUID(uid int64, score int) (err error) { // GetSignInByUID 取得签到次数 func (sdb *scoredb) GetSignInByUID(uid int64) (si signintable) { - db := (*gorm.DB)(sdb) + sdb.scoremu.Lock() + defer sdb.scoremu.Unlock() + db := sdb.db db.Model(&signintable{}).FirstOrCreate(&si, "uid = ? ", uid) return si } // InsertOrUpdateSignInCountByUID 插入或更新签到次数 func (sdb *scoredb) InsertOrUpdateSignInCountByUID(uid int64, count int) (err error) { - db := (*gorm.DB)(sdb) + sdb.scoremu.Lock() + defer sdb.scoremu.Unlock() + db := sdb.db si := signintable{ UID: uid, Count: count, @@ -118,7 +132,9 @@ func (sdb *scoredb) InsertOrUpdateSignInCountByUID(uid int64, count int) (err er } func (sdb *scoredb) GetScoreRankByTopN(n int) (st []scoretable, err error) { - db := (*gorm.DB)(sdb) + sdb.scoremu.Lock() + defer sdb.scoremu.Unlock() + db := sdb.db err = db.Model(&scoretable{}).Order("score desc").Limit(n).Find(&st).Error return } From 617d4f50a4c51c657d289113754dd40ca13a45dd Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Sun, 20 Jul 2025 13:31:06 +0800 Subject: [PATCH 27/51] feat: airecord (#1180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com> --- README.md | 20 ++++- go.mod | 4 +- go.sum | 8 +- main.go | 2 + plugin/aichat/cfg.go | 47 ++++++++++- plugin/aichat/main.go | 56 ++++++++++--- plugin/airecord/record.go | 134 ++++++++++++++++++++++++++++++ plugin/bilibili/bilibili_parse.go | 7 +- plugin/bilibili/card2msg_test.go | 2 +- plugin/manager/manager.go | 7 ++ 10 files changed, 267 insertions(+), 20 deletions(-) create mode 100644 plugin/airecord/record.go diff --git a/README.md b/README.md index 5279927cd6..988beba65b 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,8 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w] - [x] 翻牌 - [x] 赞我 + + - [x] 群签到 - [x] [开启 | 关闭]入群验证 @@ -276,6 +278,20 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w] - 设置欢迎语可选添加参数说明:{at}可在发送时艾特被欢迎者 {nickname}是被欢迎者名字 {avatar}是被欢迎者头像 {uid}是被欢迎者QQ号 {gid}是当前群群号 {groupname} 是当前群群名 +
+
+ 群应用:AI声聊 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/airecord"` + + - [x] 设置AI语音群号1048452984(tips:机器人任意所在群聊即可) + + - [x] 设置AI语音模型 + + - [x] 查看AI语音配置 + + - [x] 发送AI语音xxx +
定时指令触发器 @@ -1584,7 +1600,7 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置AI聊天温度80 - [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI] - [x] 设置AI聊天(不)支持系统提示词 - - [x] 设置AI聊天接口地址https://xxx + - [x] 设置AI聊天接口地址https://api.deepseek.com/chat/completions - [x] 设置AI聊天密钥xxx - [x] 设置AI聊天模型名xxx - [x] 查看AI聊天系统提示词 @@ -1594,6 +1610,8 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置AI聊天(不)响应AT - [x] 设置AI聊天最大长度4096 - [x] 设置AI聊天TopP 0.9 + - [x] 设置AI聊天(不)以AI语音输出 + - [x] 查看AI聊天配置
diff --git a/go.mod b/go.mod index 6e5daf610a..c319db1c9e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/Baidu-AIP/golang-sdk v1.1.1 - github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b + github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46 github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 github.com/FloatTech/gg v1.1.3 github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef @@ -45,7 +45,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/tidwall/gjson v1.18.0 github.com/wcharczuk/go-chart/v2 v2.1.2 - github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5 + github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7 gitlab.com/gomidi/midi/v2 v2.1.7 golang.org/x/image v0.24.0 golang.org/x/sys v0.30.0 diff --git a/go.sum b/go.sum index ee423745dc..d4b9a61146 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw= github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b h1:H/1xpchTGmdoHqrszH4gjafCyHIhsGSFryAkBNsu8OI= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46 h1:X6ZbOWoZJIoHCin+CeU92Q3EwpvglyQ4gc5BZhOtAwo= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 h1:lFD1pd8NkYCrw0QpTX/T5pJ67I7AL5eGxQ4v0r9f81Q= github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80/go.mod h1:IWoFFqu+0FeaHHQdddyiTRL5z7gJME6qHC96qh0R2sc= github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4= @@ -199,8 +199,8 @@ github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFe github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ= -github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5 h1:HsMcBsVpYuQv+W8pjX5WdwYROrFQP9c5Pbf4x4adDus= -github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M= +github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7 h1:ya+lVbCC/EN5JumpQDDlVCSrWzLwHl4CHzlTANKDvrU= +github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= diff --git a/main.go b/main.go index a260c25857..5e9b13e577 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,8 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/sleepmanage" // 统计睡眠时间 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/airecord" // 群应用:AI声聊 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/atri" // ATRI词库 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/manager" // 群管 diff --git a/plugin/aichat/cfg.go b/plugin/aichat/cfg.go index 2d40bf04e3..f92875320e 100644 --- a/plugin/aichat/cfg.go +++ b/plugin/aichat/cfg.go @@ -1,6 +1,7 @@ package aichat import ( + "fmt" "strconv" "strings" @@ -13,7 +14,9 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" ) -var cfg = newconfig() +var ( + cfg = newconfig() +) type config struct { ModelName string @@ -26,6 +29,7 @@ type config struct { Separator string NoReplyAT bool NoSystemP bool + NoRecord bool } func newconfig() config { @@ -151,3 +155,44 @@ func newextrasetfloat32(ptr *float32) func(ctx *zero.Ctx) { ctx.SendChain(message.Text("成功")) } } + +func printConfig(rate int64, temperature int64, cfg config) string { + maxn := cfg.MaxN + if maxn == 0 { + maxn = 4096 + } + topp := cfg.TopP + if topp == 0 { + topp = 0.9 + } + var builder strings.Builder + builder.WriteString("当前AI聊天配置:\n") + builder.WriteString(fmt.Sprintf("• 模型名:%s\n", cfg.ModelName)) + builder.WriteString(fmt.Sprintf("• 接口类型:%d(%s)\n", cfg.Type, apilist[cfg.Type])) + builder.WriteString(fmt.Sprintf("• 触发概率:%d%%\n", rate)) + builder.WriteString(fmt.Sprintf("• 温度:%.2f\n", float32(temperature)/100)) + builder.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn)) + builder.WriteString(fmt.Sprintf("• TopP:%.1f\n", topp)) + builder.WriteString(fmt.Sprintf("• 系统提示词:%s\n", cfg.SystemP)) + builder.WriteString(fmt.Sprintf("• 接口地址:%s\n", cfg.API)) + builder.WriteString(fmt.Sprintf("• 密钥:%s\n", maskKey(cfg.Key))) + builder.WriteString(fmt.Sprintf("• 分隔符:%s\n", cfg.Separator)) + builder.WriteString(fmt.Sprintf("• 响应@:%s\n", yesNo(!cfg.NoReplyAT))) + builder.WriteString(fmt.Sprintf("• 支持系统提示词:%s\n", yesNo(!cfg.NoSystemP))) + builder.WriteString(fmt.Sprintf("• 以AI语音输出:%s\n", yesNo(!cfg.NoRecord))) + return builder.String() +} + +func maskKey(key string) string { + if len(key) <= 4 { + return "****" + } + return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:] +} + +func yesNo(b bool) string { + if b { + return "是" + } + return "否" +} diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index 3cfa224060..c8ac1df646 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -13,6 +13,7 @@ import ( zero "github.com/wdvxdr1123/ZeroBot" "github.com/wdvxdr1123/ZeroBot/message" + "github.com/FloatTech/AnimeAPI/airecord" "github.com/FloatTech/floatbox/process" ctrl "github.com/FloatTech/zbpctrl" "github.com/FloatTech/zbputils/chat" @@ -29,7 +30,7 @@ var ( "- 设置AI聊天温度80\n" + "- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" + "- 设置AI聊天(不)支持系统提示词\n" + - "- 设置AI聊天接口地址https://xxx\n" + + "- 设置AI聊天接口地址https://api.deepseek.com/chat/completions\n" + "- 设置AI聊天密钥xxx\n" + "- 设置AI聊天模型名xxx\n" + "- 查看AI聊天系统提示词\n" + @@ -38,16 +39,21 @@ var ( "- 设置AI聊天分隔符(留空则清除)\n" + "- 设置AI聊天(不)响应AT\n" + "- 设置AI聊天最大长度4096\n" + - "- 设置AI聊天TopP 0.9", + "- 设置AI聊天TopP 0.9\n" + + "- 设置AI聊天(不)以AI语音输出\n" + + "- 查看AI聊天配置\n", PrivateDataFolder: "aichat", }) ) -var apitypes = map[string]uint8{ - "OpenAI": 0, - "OLLaMA": 1, - "GenAI": 2, -} +var ( + apitypes = map[string]uint8{ + "OpenAI": 0, + "OLLaMA": 1, + "GenAI": 2, + } + apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"} +) func init() { en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool { @@ -135,10 +141,20 @@ func init() { if t == "" { continue } - if id != nil { - id = ctx.SendChain(message.Reply(id), message.Text(t)) + logrus.Infoln("[aichat] 回复内容:", t) + recCfg := airecord.GetConfig() + record := "" + if !cfg.NoRecord { + record = ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, t) + } + if record != "" { + ctx.SendChain(message.Record(record)) } else { - id = ctx.SendChain(message.Text(t)) + if id != nil { + id = ctx.SendChain(message.Reply(id), message.Text(t)) + } else { + id = ctx.SendChain(message.Text(t)) + } } process.SleepAbout1sTo2s() } @@ -269,4 +285,24 @@ func init() { Handle(newextrasetuint(&cfg.MaxN)) en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetfloat32(&cfg.TopP)) + en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(newextrasetbool(&cfg.NoRecord)) + en.OnFullMatch("查看AI聊天配置", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) + if !ok { + ctx.SendChain(message.Text("ERROR: no such plugin")) + return + } + gid := ctx.Event.GroupID + rate := c.GetData(gid) & 0xff + temp := (c.GetData(gid) >> 8) & 0xff + if temp <= 0 { + temp = 70 // default setting + } + if temp > 100 { + temp = 100 + } + ctx.SendChain(message.Text(printConfig(rate, temp, cfg))) + }) } diff --git a/plugin/airecord/record.go b/plugin/airecord/record.go new file mode 100644 index 0000000000..908784d483 --- /dev/null +++ b/plugin/airecord/record.go @@ -0,0 +1,134 @@ +// Package airecord 群应用:AI声聊 +package airecord + +import ( + "strconv" + "strings" + "time" + + "github.com/tidwall/gjson" + + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + + "github.com/FloatTech/AnimeAPI/airecord" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" +) + +func init() { + en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Extra: control.ExtraFromString("airecord"), + Brief: "群应用:AI声聊", + Help: "- 设置AI语音群号1048452984(tips:机器人任意所在群聊即可)\n" + + "- 设置AI语音模型\n" + + "- 查看AI语音配置\n" + + "- 发送AI语音xxx", + PrivateDataFolder: "airecord", + }) + + en.OnPrefix("设置AI语音群号", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + u := strings.TrimSpace(ctx.State["args"].(string)) + num, err := strconv.ParseInt(u, 10, 64) + if err != nil { + ctx.SendChain(message.Text("ERROR: parse gid err: ", err)) + return + } + err = airecord.SetCustomGID(num) + if err != nil { + ctx.SendChain(message.Text("ERROR: set gid err: ", err)) + return + } + ctx.SendChain(message.Text("设置AI语音群号为", num)) + }) + en.OnFullMatch("设置AI语音模型", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession()) + recv, cancel := next.Repeat() + defer cancel() + jsonData := ctx.GetAICharacters(0, 1) + + // 转换为字符串数组 + var names []string + // 初始化两个映射表 + nameToID := make(map[string]string) + nameToURL := make(map[string]string) + characters := jsonData.Get("#.characters") + + // 遍历每个角色对象 + characters.ForEach(func(_, group gjson.Result) bool { + group.ForEach(func(_, character gjson.Result) bool { + // 提取当前角色的三个字段 + name := character.Get("character_name").String() + names = append(names, name) + // 存入映射表(重复名称会覆盖,保留最后出现的条目) + nameToID[name] = character.Get("character_id").String() + nameToURL[name] = character.Get("preview_url").String() + return true // 继续遍历 + }) + return true // 继续遍历 + }) + var builder strings.Builder + // 写入开头文本 + builder.WriteString("请选择语音模型序号:\n") + + // 遍历names数组,拼接序号和名称 + for i, v := range names { + // 将数字转换为字符串(不依赖fmt) + numStr := strconv.Itoa(i) + // 拼接格式:"序号. 名称\n" + builder.WriteString(numStr) + builder.WriteString(". ") + builder.WriteString(v) + builder.WriteString("\n") + } + // 获取最终字符串 + ctx.SendChain(message.Text(builder.String())) + for { + select { + case <-time.After(time.Second * 120): + ctx.SendChain(message.Text("设置AI语音模型指令过期")) + return + case ct := <-recv: + msg := ct.Event.Message.ExtractPlainText() + num, err := strconv.Atoi(msg) + if err != nil { + ctx.SendChain(message.Text("请输入数字!")) + continue + } + if num < 0 || num >= len(names) { + ctx.SendChain(message.Text("序号非法!")) + continue + } + err = airecord.SetRecordModel(names[num], nameToID[names[num]]) + if err != nil { + ctx.SendChain(message.Text("ERROR: set model err: ", err)) + continue + } + ctx.SendChain(message.Text("已选择语音模型: ", names[num])) + ctx.SendChain(message.Record(nameToURL[names[num]])) + return + } + } + }) + en.OnFullMatch("查看AI语音配置", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + ctx.SendChain(message.Text(airecord.PrintRecordConfig())) + }) + en.OnPrefix("发送AI语音", zero.UserOrGrpAdmin).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + u := strings.TrimSpace(ctx.State["args"].(string)) + recCfg := airecord.GetConfig() + record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, u) + if record == "" { + id := ctx.SendGroupAIRecord(recCfg.ModelID, ctx.Event.GroupID, u) + if id == "" { + ctx.SendChain(message.Text("ERROR: get record err: empty record")) + return + } + } + ctx.SendChain(message.Record(record)) + }) +} diff --git a/plugin/bilibili/bilibili_parse.go b/plugin/bilibili/bilibili_parse.go index b094e0e88e..189c6586f2 100644 --- a/plugin/bilibili/bilibili_parse.go +++ b/plugin/bilibili/bilibili_parse.go @@ -163,7 +163,12 @@ func handleArticle(ctx *zero.Ctx) { } func handleLive(ctx *zero.Ctx) { - card, err := bz.GetLiveRoomInfo(ctx.State["regex_matched"].([]string)[1]) + 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 diff --git a/plugin/bilibili/card2msg_test.go b/plugin/bilibili/card2msg_test.go index 5c43c8491c..5d85d77507 100644 --- a/plugin/bilibili/card2msg_test.go +++ b/plugin/bilibili/card2msg_test.go @@ -47,7 +47,7 @@ func TestVideoInfo(t *testing.T) { } func TestLiveRoomInfo(t *testing.T) { - card, err := bz.GetLiveRoomInfo("83171") + card, err := bz.GetLiveRoomInfo("83171", "b_ut=7;buvid3=0;i-wanna-go-back=-1;innersign=0;") if err != nil { t.Fatal(err) } diff --git a/plugin/manager/manager.go b/plugin/manager/manager.go index f1a6f788e8..2521412bdb 100644 --- a/plugin/manager/manager.go +++ b/plugin/manager/manager.go @@ -50,6 +50,7 @@ const ( "- 列出所有提醒\n" + "- 翻牌\n" + "- 赞我\n" + + "- 群签到\n" + "- 对信息回复: 回应表情 [表情]\n" + "- 设置欢迎语XXX 可选添加 [{at}] [{nickname}] [{avatar}] [{uid}] [{gid}] [{groupname}]\n" + "- 测试欢迎语\n" + @@ -405,6 +406,12 @@ func init() { // 插件主体 ctx.SendLike(ctx.Event.UserID, 10) ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("给你赞了10下哦,记得回我~")) }) + // 群签到 + engine.OnFullMatch("群签到", zero.OnlyGroup).SetBlock(true).Limit(ctxext.LimitByUser). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupSign(ctx.Event.GroupID) + ctx.SendChain(message.Text("群签到成功,可在手机端输入框中的打卡查看")) + }) facere := regexp.MustCompile(`\[CQ:face,id=(\d+)\]`) // 给消息回应表情 engine.OnRegex(`^\[CQ:reply,id=(-?\d+)\].*回应表情\s*(.+)\s*$`, zero.AdminPermission, zero.OnlyGroup).SetBlock(true). From 21aa3bc49f8f2250e56ca81f1009341c675c2971 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 20 Jul 2025 14:33:29 +0900 Subject: [PATCH 28/51] chore: bump deps (#1182) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 92d90597cd..4ea7195ba2 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -5,8 +5,8 @@ schema = 3 version = "v1.1.1" hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo=" [mod."github.com/FloatTech/AnimeAPI"] - version = "v1.7.1-0.20250530055006-50f5c7587c5b" - hash = "sha256-xDXPwphMS26J02q/ysQy3CJPK5B9c7uoqwFt1xFIF5Q=" + version = "v1.7.1-0.20250717123723-d300df538b46" + hash = "sha256-ZauOGdJb6XcAVsXvZ8B76+1daRm1s84vXoKwmvS0rn4=" [mod."github.com/FloatTech/floatbox"] version = "v0.0.0-20250513111443-adba80e84e80" hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE=" @@ -227,8 +227,8 @@ schema = 3 version = "v2.1.2" hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM=" [mod."github.com/wdvxdr1123/ZeroBot"] - version = "v1.8.2-0.20250330133859-27c25d9412b5" - hash = "sha256-gT3uFTg5E0Th3r1M1vLzr0QtOjbMusqEjD/ckoBdDFc=" + version = "v1.8.2-0.20250707133321-6197b8ee5df7" + hash = "sha256-iVAgUtXm/SO3BcdW4XDRFnbZBUk9b1GbIILajibcFo8=" [mod."github.com/yusufpapurcu/wmi"] version = "v1.2.4" hash = "sha256-N+YDBjOW59YOsZ2lRBVtFsEEi48KhNQRb63/0ZSU3bA=" From a1621f34a08236bb928ab84fe1b550c795827a15 Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Wed, 23 Jul 2025 20:24:00 +0800 Subject: [PATCH 29/51] =?UTF-8?q?optimize(antiabuse):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=BF=9D=E7=A6=81=E8=AF=8D=E8=A7=A3=E9=87=8A=20(#1183)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++++++++ plugin/antiabuse/anti.go | 32 +++++++++++++++++++++----------- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 988beba65b..1442aad6ff 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,18 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w] - [x] 早安 | 晚安 +
+
+ 违禁词检测 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/antiabuse" + ` + - [x] 添加违禁词 + + - [x] 删除违禁词 + + - [x] 查看违禁词 +
ATRI diff --git a/plugin/antiabuse/anti.go b/plugin/antiabuse/anti.go index b4ba459f68..dfe64fbdff 100644 --- a/plugin/antiabuse/anti.go +++ b/plugin/antiabuse/anti.go @@ -17,7 +17,12 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" ) -const bandur time.Duration = time.Minute * 10 +const ( + bandur time.Duration = time.Minute * 2 + add = "添加违禁词" + del = "删除违禁词" + list = "查看违禁词" +) var ( managers *ctrl.Manager[*zero.Ctx] // managers lazy load @@ -41,7 +46,7 @@ func init() { engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, Brief: "违禁词检测", - Help: "- /[添加|删除|查看]违禁词", + Help: "- [添加|删除|查看]违禁词", PrivateDataFolder: "anti_abuse", }) @@ -56,10 +61,14 @@ func init() { return true }) - engine.OnMessage(onceRule, zero.OnlyGroup, func(ctx *zero.Ctx) bool { - if !ctx.Event.IsToMe { - return true + notAntiabuse := func(ctx *zero.Ctx) bool { + if zero.PrefixRule(add)(ctx) || zero.PrefixRule(del)(ctx) || zero.PrefixRule(list)(ctx) { + return false } + return true + } + + engine.OnMessage(onceRule, notAntiabuse, zero.OnlyGroup, func(ctx *zero.Ctx) bool { uid := ctx.Event.UserID gid := ctx.Event.GroupID msg := strings.ReplaceAll(ctx.MessageString(), "\n", "") @@ -70,7 +79,8 @@ func init() { if err := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]).Manager.DoBlock(uid); err == nil { t := time.Now().Unix() cache.Set(uid, struct{}{}) - ctx.SetThisGroupBan(uid, int64(bandur.Minutes())) + ctx.SetThisGroupBan(uid, int64(bandur.Seconds())) + ctx.DeleteMessage(ctx.Event.MessageID) ctx.SendChain(message.Text("检测到违禁词, 已封禁/屏蔽", bandur)) db.Lock() defer db.Unlock() @@ -92,9 +102,9 @@ func init() { return true }) - engine.OnCommand("添加违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle( + engine.OnPrefix(add, zero.OnlyGroup, zero.AdminPermission, onceRule).SetBlock(true).Handle( func(ctx *zero.Ctx) { - args := ctx.State["args"].(string) + args := strings.TrimSpace(ctx.State["args"].(string)) if err := db.insertWord(ctx.Event.GroupID, args); err != nil { ctx.SendChain(message.Text("ERROR: ", err)) } else { @@ -102,9 +112,9 @@ func init() { } }) - engine.OnCommand("删除违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle( + engine.OnPrefix(del, zero.OnlyGroup, zero.AdminPermission, onceRule).SetBlock(true).Handle( func(ctx *zero.Ctx) { - args := ctx.State["args"].(string) + args := strings.TrimSpace(ctx.State["args"].(string)) if err := db.deleteWord(ctx.Event.GroupID, args); err != nil { ctx.SendChain(message.Text("ERROR: ", err)) } else { @@ -112,7 +122,7 @@ func init() { } }) - engine.OnCommand("查看违禁词", zero.OnlyGroup, onceRule).Handle( + engine.OnPrefix(list, zero.OnlyGroup, onceRule).SetBlock(true).Handle( func(ctx *zero.Ctx) { b, err := text.RenderToBase64(db.listWords(ctx.Event.GroupID), text.FontFile, 400, 20) if err != nil { From b6ddda1d51e85e8a115937fbc5282ff72c700efd Mon Sep 17 00:00:00 2001 From: Dodoj <31199261+Doordoorjay@users.noreply.github.com> Date: Fri, 25 Jul 2025 09:31:33 +0800 Subject: [PATCH 30/51] =?UTF-8?q?fix(kfccrazythursday):=20API=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=20(#1184)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/kfccrazythursday/kfccrazythursday.go | 22 ++------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/plugin/kfccrazythursday/kfccrazythursday.go b/plugin/kfccrazythursday/kfccrazythursday.go index 373340399b..d144cdaebb 100644 --- a/plugin/kfccrazythursday/kfccrazythursday.go +++ b/plugin/kfccrazythursday/kfccrazythursday.go @@ -2,8 +2,6 @@ package kfccrazythursday import ( - "encoding/json" - "github.com/FloatTech/floatbox/web" ctrl "github.com/FloatTech/zbpctrl" "github.com/FloatTech/zbputils/control" @@ -15,12 +13,6 @@ const ( crazyURL = "https://api.pearktrue.cn/api/kfc/" ) -type crazyResponse struct { - Code int `json:"code"` - Msg string `json:"msg"` - Text string `json:"text"` -} - func init() { engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, @@ -34,17 +26,7 @@ func init() { return } - var resp crazyResponse - if err := json.Unmarshal(data, &resp); err != nil { - ctx.SendChain(message.Text("JSON解析失败: ", err)) - return - } - - if resp.Code != 200 { - ctx.SendChain(message.Text("API返回错误: ", resp.Msg)) - return - } - - ctx.SendChain(message.Text(resp.Text)) + // 根据来源API修改返回方式到直接输出文本 + ctx.SendChain(message.Text(string(data))) }) } From 2fa78688385bdd8c79a68cb95f0e533ff4637962 Mon Sep 17 00:00:00 2001 From: Dodoj <31199261+Doordoorjay@users.noreply.github.com> Date: Tue, 12 Aug 2025 02:18:32 +1000 Subject: [PATCH 31/51] =?UTF-8?q?fix(gif):=20branch=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84404=E9=97=AE=E9=A2=98=20(#1186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/gif/context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/gif/context.go b/plugin/gif/context.go index 1b5f541738..6db373bf2c 100644 --- a/plugin/gif/context.go +++ b/plugin/gif/context.go @@ -20,7 +20,7 @@ func dlchan(name string, s *string, wg *sync.WaitGroup, exit func(error)) { defer wg.Done() target := datapath + `materials/` + name if file.IsNotExist(target) { - data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/main/` + name) + data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/master/` + name) if err != nil { _ = os.Remove(target) exit(err) @@ -48,7 +48,7 @@ func dlchan(name string, s *string, wg *sync.WaitGroup, exit func(error)) { func dlblock(name string) (string, error) { target := datapath + `materials/` + name if file.IsNotExist(target) { - data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/main/` + name) + data, err := web.GetData(`https://gitea.seku.su/fumiama/ImageMaterials/raw/branch/master/` + name) if err != nil { _ = os.Remove(target) return "", err From 34f3b9ba2a582afa091040fd782ef60994e628e5 Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Fri, 22 Aug 2025 22:37:35 +0800 Subject: [PATCH 32/51] feat(aiimage&aichat): add new plugin & summary of group chat (#1187) --- README.md | 20 +++- go.mod | 6 +- go.sum | 12 +-- main.go | 1 + plugin/aichat/main.go | 103 +++++++++++++++++++- plugin/aiimage/config.go | 56 +++++++++++ plugin/aiimage/main.go | 171 ++++++++++++++++++++++++++++++++++ plugin/chatcount/chatcount.go | 9 +- plugin/wordcount/main.go | 62 ++++-------- 9 files changed, 379 insertions(+), 61 deletions(-) create mode 100644 plugin/aiimage/config.go create mode 100644 plugin/aiimage/main.go diff --git a/README.md b/README.md index 1442aad6ff..f7535e4ae7 100644 --- a/README.md +++ b/README.md @@ -412,6 +412,18 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置默认限速为每 m [分钟 | 秒] n 次触发 +
+
+ aiimage + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiimage"` + + - [x] 设置AI画图密钥xxx + - [x] 设置AI画图接口地址https://api.siliconflow.cn/v1/images/generations + - [x] 设置AI画图模型名Kwai-Kolors/Kolors + - [x] 查看AI画图配置 + - [x] AI画图 [描述] +
AIWife @@ -1496,7 +1508,7 @@ print("run[CQ:image,file="+j["img"]+"]") `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/word_count"` - - [x] 热词 [群号] [消息数目]|热词 123456 1000 + - [x] 热词 [消息数目]|热词 1000
@@ -1612,9 +1624,9 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置AI聊天温度80 - [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI] - [x] 设置AI聊天(不)支持系统提示词 - - [x] 设置AI聊天接口地址https://api.deepseek.com/chat/completions + - [x] 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions - [x] 设置AI聊天密钥xxx - - [x] 设置AI聊天模型名xxx + - [x] 设置AI聊天模型名Qwen/Qwen3-8B - [x] 查看AI聊天系统提示词 - [x] 重置AI聊天系统提示词 - [x] 设置AI聊天系统提示词xxx @@ -1624,6 +1636,8 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置AI聊天TopP 0.9 - [x] 设置AI聊天(不)以AI语音输出 - [x] 查看AI聊天配置 + - [x] 重置AI聊天 + - [x] 群聊总结 [消息数目]|群聊总结 1000
diff --git a/go.mod b/go.mod index c319db1c9e..3e7cd6dc5d 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/FloatTech/sqlite v1.7.1 github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 github.com/FloatTech/zbpctrl v1.7.0 - github.com/FloatTech/zbputils v1.7.2-0.20250614165821-95cf57cf2434 + github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 github.com/Tnze/go-mc v1.20.2 @@ -22,7 +22,7 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/fumiama/ahsai v0.1.0 github.com/fumiama/cron v1.3.0 - github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1 + github.com/fumiama/deepinfra v0.0.0-20250812083039-f1b27f21d8c9 github.com/fumiama/go-base16384 v1.7.0 github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 @@ -45,7 +45,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/tidwall/gjson v1.18.0 github.com/wcharczuk/go-chart/v2 v2.1.2 - github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7 + github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250804063440-ccc03e33ac20 gitlab.com/gomidi/midi/v2 v2.1.7 golang.org/x/image v0.24.0 golang.org/x/sys v0.30.0 diff --git a/go.sum b/go.sum index d4b9a61146..31ada71b36 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562 h1:snfw7FNFym1eNnLrQ github.com/FloatTech/ttl v0.0.0-20240716161252-965925764562/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs= github.com/FloatTech/zbpctrl v1.7.0 h1:Hxo6EIhJo+pHjcQP9QgIJgluaT1pHH99zkk3njqTNMo= github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69PfrN5NYf8BPE= -github.com/FloatTech/zbputils v1.7.2-0.20250614165821-95cf57cf2434 h1:oEYQFQ2/qx10FtZKCNbW3Ohj/Iw71aM4RWpIu+LMmf8= -github.com/FloatTech/zbputils v1.7.2-0.20250614165821-95cf57cf2434/go.mod h1:ArZ0fMAcmPEIXOqDmfzbSx+oYH8sssApQnbCu694iS8= +github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f h1:5jnrFe9FTydb/pcUhxkWHuQVCwmYIZmneOkvmgHOwGI= +github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f/go.mod h1:HG/yZwExV3b1Vqu4chbqwhfX4hx7gDS07QO436JkwIg= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= @@ -59,8 +59,8 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4= github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk= github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo= github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY= -github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1 h1:qE3l/y4Y1gMD2NokQ5Nw4NIUjL8ZwYLPIHOExQNu4hM= -github.com/fumiama/deepinfra v0.0.0-20250601112706-0175c95164c1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= +github.com/fumiama/deepinfra v0.0.0-20250812083039-f1b27f21d8c9 h1:X2h8RnCgC04LmwBoizYbFawXh/h6CouXmhYtaVuUn7k= +github.com/fumiama/deepinfra v0.0.0-20250812083039-f1b27f21d8c9/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA= github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs= @@ -199,8 +199,8 @@ github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFe github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4= github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E= github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ= -github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7 h1:ya+lVbCC/EN5JumpQDDlVCSrWzLwHl4CHzlTANKDvrU= -github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M= +github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250804063440-ccc03e33ac20 h1:Yzd+cbiJQYtf6cZDP5ZB/LqjNWiV752+5P6Eua+wnic= +github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250804063440-ccc03e33ac20/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= diff --git a/main.go b/main.go index 5e9b13e577..117b111aa1 100644 --- a/main.go +++ b/main.go @@ -67,6 +67,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/custom" // 自定义插件合集 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai" // ahsai tts _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aifalse" // 服务器监控 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiimage" // AI画图 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/alipayvoice" // 支付宝到账语音 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace" // AnimeTrace 动画/Galgame识别 diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index c8ac1df646..dbb9e9aa80 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -1,14 +1,16 @@ -// Package aichat OpenAI聊天 +// Package aichat OpenAI聊天和群聊总结 package aichat import ( "math/rand" "strconv" "strings" + "time" "github.com/fumiama/deepinfra" "github.com/fumiama/deepinfra/model" "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" zero "github.com/wdvxdr1123/ZeroBot" "github.com/wdvxdr1123/ZeroBot/message" @@ -18,6 +20,7 @@ import ( ctrl "github.com/FloatTech/zbpctrl" "github.com/FloatTech/zbputils/chat" "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/ctxext" ) var ( @@ -30,9 +33,9 @@ var ( "- 设置AI聊天温度80\n" + "- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" + "- 设置AI聊天(不)支持系统提示词\n" + - "- 设置AI聊天接口地址https://api.deepseek.com/chat/completions\n" + + "- 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions\n" + "- 设置AI聊天密钥xxx\n" + - "- 设置AI聊天模型名xxx\n" + + "- 设置AI聊天模型名Qwen/Qwen3-8B\n" + "- 查看AI聊天系统提示词\n" + "- 重置AI聊天系统提示词\n" + "- 设置AI聊天系统提示词xxx\n" + @@ -41,7 +44,9 @@ var ( "- 设置AI聊天最大长度4096\n" + "- 设置AI聊天TopP 0.9\n" + "- 设置AI聊天(不)以AI语音输出\n" + - "- 查看AI聊天配置\n", + "- 查看AI聊天配置\n" + + "- 重置AI聊天\n" + + "- 群聊总结 [消息数目]|群聊总结 1000\n", PrivateDataFolder: "aichat", }) ) @@ -53,6 +58,7 @@ var ( "GenAI": 2, } apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"} + limit = ctxext.NewLimiterManager(time.Second*30, 1) ) func init() { @@ -305,4 +311,93 @@ func init() { } ctx.SendChain(message.Text(printConfig(rate, temp, cfg))) }) + en.OnFullMatch("重置AI聊天", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { + chat.Reset() + ctx.SendChain(message.Text("成功")) + }) + + // 添加群聊总结功能 + en.OnRegex(`^群聊总结\s?(\d*)$`, ensureconfig, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(limit.LimitByGroup).Handle(func(ctx *zero.Ctx) { + ctx.SendChain(message.Text("少女思考中...")) + p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) + if p > 1000 { + p = 1000 + } + if p == 0 { + p = 200 + } + gid := ctx.Event.GroupID + group := ctx.GetGroupInfo(gid, false) + if group.MemberCount == 0 { + ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取摘要")) + return + } + + var messages []string + + h := ctx.GetGroupMessageHistory(gid, 0, p, false) + h.Get("messages").ForEach(func(_, msgObj gjson.Result) bool { + nickname := msgObj.Get("sender.nickname").Str + text := strings.TrimSpace(message.ParseMessageFromString(msgObj.Get("raw_message").Str).ExtractPlainText()) + if text != "" { + messages = append(messages, nickname+": "+text) + } + return true + }) + + if len(messages) == 0 { + ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息")) + return + } + + // 调用大模型API进行摘要 + summary, err := summarizeMessages(messages) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + var b strings.Builder + b.WriteString("群 ") + b.WriteString(group.Name) + b.WriteByte('(') + b.WriteString(strconv.FormatInt(gid, 10)) + b.WriteString(") 的 ") + b.WriteString(strconv.FormatInt(p, 10)) + b.WriteString(" 条消息总结:\n\n") + b.WriteString(summary) + + // 分割总结内容为多段 + parts := strings.Split(b.String(), "\n\n") + msg := make(message.Message, 0, len(parts)) + for _, part := range parts { + if part != "" { + msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(part))) + } + } + if len(msg) > 0 { + ctx.Send(msg) + } + }) +} + +// summarizeMessages 调用大模型API进行消息摘要 +func summarizeMessages(messages []string) (string, error) { + // 使用现有的AI配置进行摘要 + x := deepinfra.NewAPI(cfg.API, cfg.Key) + mod := model.NewOpenAI( + cfg.ModelName, cfg.Separator, + float32(70)/100, 0.9, 4096, + ) + + // 构造摘要请求提示 + summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n\n" + + strings.Join(messages, "\n---\n") + + data, err := x.Request(mod.User(summaryPrompt)) + if err != nil { + return "", err + } + + return strings.TrimSpace(data), nil } diff --git a/plugin/aiimage/config.go b/plugin/aiimage/config.go new file mode 100644 index 0000000000..e48ec5c926 --- /dev/null +++ b/plugin/aiimage/config.go @@ -0,0 +1,56 @@ +// Package aiimage 提供AI画图功能配置 +package aiimage + +import ( + "fmt" + "strings" + "sync" + + sql "github.com/FloatTech/sqlite" +) + +// storage 管理画图配置存储 +type storage struct { + sync.RWMutex + db sql.Sqlite +} + +// imageConfig 存储AI画图配置信息 +type imageConfig struct { + ID int64 `db:"id"` // 主键ID + APIKey string `db:"apiKey"` // API密钥 + APIURL string `db:"apiUrl"` // API地址 + ModelName string `db:"modelName"` // 画图模型名称 +} + +// getConfig 获取当前配置 +func (sdb *storage) getConfig() imageConfig { + sdb.RLock() + defer sdb.RUnlock() + cfg := imageConfig{} + _ = sdb.db.Find("config", &cfg, "WHERE id = 1") + return cfg +} + +// setConfig 设置AI画图配置 +func (sdb *storage) setConfig(apiKey, apiURL, modelName string) error { + sdb.Lock() + defer sdb.Unlock() + return sdb.db.Insert("config", &imageConfig{ + ID: 1, + APIKey: apiKey, + APIURL: apiURL, + ModelName: modelName, + }) +} + +// PrintConfig 返回格式化后的配置信息 +func (sdb *storage) PrintConfig() string { + cfg := sdb.getConfig() + var builder strings.Builder + builder.WriteString("当前AI画图配置:\n") + builder.WriteString(fmt.Sprintf("• 密钥: %s\n", cfg.APIKey)) + builder.WriteString(fmt.Sprintf("• 接口地址: %s\n", cfg.APIURL)) + builder.WriteString(fmt.Sprintf("• 模型名: %s\n", cfg.ModelName)) + return builder.String() +} diff --git a/plugin/aiimage/main.go b/plugin/aiimage/main.go new file mode 100644 index 0000000000..519065e92a --- /dev/null +++ b/plugin/aiimage/main.go @@ -0,0 +1,171 @@ +// Package aiimage AI画图 +package aiimage + +import ( + "bytes" + "encoding/json" + "net/http" + "strings" + "time" + + fcext "github.com/FloatTech/floatbox/ctxext" + "github.com/FloatTech/floatbox/web" + sql "github.com/FloatTech/sqlite" + "github.com/tidwall/gjson" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/ctxext" +) + +func init() { + var sdb = &storage{} + + en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Extra: control.ExtraFromString("aiimage"), + Brief: "AI画图", + Help: "- 设置AI画图密钥xxx\n" + + "- 设置AI画图接口地址https://api.siliconflow.cn/v1/images/generations\n" + + "- 设置AI画图模型名Kwai-Kolors/Kolors\n" + + "- 查看AI画图配置\n" + + "- AI画图 [描述]", + PrivateDataFolder: "aiimage", + }) + + getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { + sdb.db = sql.New(en.DataFolder() + "aiimage.db") + err := sdb.db.Open(time.Hour) + if err == nil { + // 创建配置表 + err = sdb.db.Create("config", &imageConfig{}) + if err != nil { + ctx.SendChain(message.Text("[ERROR]:", err)) + return false + } + return true + } + ctx.SendChain(message.Text("[ERROR]:", err)) + return false + }) + + en.OnPrefix("设置AI画图密钥", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + apiKey := strings.TrimSpace(ctx.State["args"].(string)) + cfg := sdb.getConfig() + err := sdb.setConfig(apiKey, cfg.APIURL, cfg.ModelName) + if err != nil { + ctx.SendChain(message.Text("ERROR: 设置API密钥失败: ", err)) + return + } + ctx.SendChain(message.Text("成功设置API密钥")) + }) + + en.OnPrefix("设置AI画图接口地址", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + apiURL := strings.TrimSpace(ctx.State["args"].(string)) + cfg := sdb.getConfig() + err := sdb.setConfig(cfg.APIKey, apiURL, cfg.ModelName) + if err != nil { + ctx.SendChain(message.Text("ERROR: 设置API地址失败: ", err)) + return + } + ctx.SendChain(message.Text("成功设置API地址")) + }) + + en.OnPrefix("设置AI画图模型名", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + modelName := strings.TrimSpace(ctx.State["args"].(string)) + cfg := sdb.getConfig() + err := sdb.setConfig(cfg.APIKey, cfg.APIURL, modelName) + if err != nil { + ctx.SendChain(message.Text("ERROR: 设置模型失败: ", err)) + return + } + ctx.SendChain(message.Text("成功设置模型: ", modelName)) + }) + + en.OnFullMatch("查看AI画图配置", getdb, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + ctx.SendChain(message.Text(sdb.PrintConfig())) + }) + + en.OnPrefix("AI画图", getdb).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + ctx.SendChain(message.Text("少女思考中...")) + prompt := strings.TrimSpace(ctx.State["args"].(string)) + if prompt == "" { + ctx.SendChain(message.Text("请输入图片描述")) + return + } + + cfg := sdb.getConfig() + if cfg.APIKey == "" || cfg.APIURL == "" || cfg.ModelName == "" { + ctx.SendChain(message.Text("请先配置API密钥、地址和模型")) + return + } + + // 准备请求数据 + reqBytes, _ := json.Marshal(map[string]interface{}{ + "model": cfg.ModelName, + "prompt": prompt, + "image_size": "1024x1024", + "batch_size": 4, + "num_inference_steps": 20, + "guidance_scale": 7.5, + }) + + // 发送API请求 + data, err := web.RequestDataWithHeaders( + web.NewDefaultClient(), + cfg.APIURL, + "POST", + func(req *http.Request) error { + req.Header.Set("Authorization", "Bearer "+cfg.APIKey) + req.Header.Set("Content-Type", "application/json") + return nil + }, + bytes.NewReader(reqBytes), + ) + if err != nil { + ctx.SendChain(message.Text("API请求失败: ", err)) + return + } + + // 解析API响应 + jsonData := gjson.ParseBytes(data) + images := jsonData.Get("images") + if !images.Exists() { + images = jsonData.Get("data") + if !images.Exists() { + ctx.SendChain(message.Text("未获取到图片URL")) + return + } + } + + // 发送生成的图片和相关信息 + inferenceTime := jsonData.Get("timings.inference").Float() + seed := jsonData.Get("seed").Int() + msg := make(message.Message, 0, 1) + msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text("图片生成成功!\n", + "提示词: ", prompt, "\n", + "模型: ", cfg.ModelName, "\n", + "推理时间: ", inferenceTime, "秒\n", + "种子: ", seed))) + + // 添加所有图片 + images.ForEach(func(_, value gjson.Result) bool { + url := value.Get("url").String() + if url != "" { + msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Image(url))) + } + return true + }) + + if len(msg) > 0 { + ctx.Send(msg) + } + }) +} diff --git a/plugin/chatcount/chatcount.go b/plugin/chatcount/chatcount.go index a4e1bb41a8..caf88ebeed 100644 --- a/plugin/chatcount/chatcount.go +++ b/plugin/chatcount/chatcount.go @@ -43,8 +43,15 @@ func init() { }) engine.OnPrefix(`查询水群`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { + param := ctx.State["args"].(string) + var uid int64 + if len(ctx.Event.Message) > 1 && ctx.Event.Message[1].Type == "at" { + uid, _ = strconv.ParseInt(ctx.Event.Message[1].Data["qq"], 10, 64) + } else if param == "" { + uid = ctx.Event.UserID + } name := ctx.NickName() - todayTime, todayMessage, totalTime, totalMessage := ctdb.getChatTime(ctx.Event.GroupID, ctx.Event.UserID) + todayTime, todayMessage, totalTime, totalMessage := ctdb.getChatTime(ctx.Event.GroupID, uid) ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(fmt.Sprintf("%s今天水了%d分%d秒,发了%d条消息;总计水了%d分%d秒,发了%d条消息。", name, todayTime/60, todayTime%60, todayMessage, totalTime/60, totalTime%60, totalMessage))) }) engine.OnFullMatch("查看水群排名", zero.OnlyGroup).Limit(ctxext.LimitByGroup).SetBlock(true). diff --git a/plugin/wordcount/main.go b/plugin/wordcount/main.go index 5e58069fa3..127c7da8db 100644 --- a/plugin/wordcount/main.go +++ b/plugin/wordcount/main.go @@ -8,7 +8,6 @@ import ( "sort" "strconv" "strings" - "sync" "time" "github.com/go-ego/gse" @@ -40,7 +39,7 @@ func init() { engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, Brief: "聊天热词", - Help: "- 热词 [群号] [消息数目]|热词 123456 1000", + Help: "- 热词 [消息数目]|热词 1000", PublicDataFolder: "WordCount", }) cachePath := engine.DataFolder() + "cache/" @@ -51,7 +50,7 @@ func init() { } _ = os.RemoveAll(cachePath) _ = os.MkdirAll(cachePath, 0755) - engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { + engine.OnRegex(`^热词\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { _, err := engine.GetLazyData("stopwords.txt", false) if err != nil { ctx.SendChain(message.Text("ERROR: ", err)) @@ -85,17 +84,14 @@ func init() { } ctx.SendChain(message.Text("少女祈祷中...")) - gid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) - p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[2], 10, 64) + p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) if p > 10000 { p = 10000 } if p == 0 { p = 1000 } - if gid == 0 { - gid = ctx.Event.GroupID - } + gid := ctx.Event.GroupID group := ctx.GetGroupInfo(gid, false) if group.MemberCount == 0 { ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获得热词呢")) @@ -108,44 +104,22 @@ func init() { return } messageMap := make(map[string]int, 256) - msghists := make(chan *gjson.Result, 256) - go func() { - h := ctx.GetLatestGroupMessageHistory(gid) - messageSeq := h.Get("messages.0.message_seq").Int() - msghists <- &h - for i := 1; i < int(p/20) && messageSeq != 0; i++ { - h := ctx.GetGroupMessageHistory(gid, messageSeq) - msghists <- &h - messageSeq = h.Get("messages.0.message_seq").Int() - } - close(msghists) - }() - var wg sync.WaitGroup - var mapmu sync.Mutex - for h := range msghists { - wg.Add(1) - go func(h *gjson.Result) { - for _, v := range h.Get("messages.#.message").Array() { - tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText()) - if tex == "" { - continue - } - segments := seg.Segment(helper.StringToBytes(tex)) - words := gse.ToSlice(segments, true) - for _, word := range words { - word = strings.TrimSpace(word) - i := sort.SearchStrings(stopwords, word) - if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) { - mapmu.Lock() - messageMap[word]++ - mapmu.Unlock() - } + h := ctx.GetGroupMessageHistory(gid, 0, p, false) + h.Get("messages").ForEach(func(_, msgObj gjson.Result) bool { + tex := strings.TrimSpace(message.ParseMessageFromString(msgObj.Get("raw_message").Str).ExtractPlainText()) + if tex != "" { + segments := seg.Segment(helper.StringToBytes(tex)) + words := gse.ToSlice(segments, true) + for _, word := range words { + word = strings.TrimSpace(word) + i := sort.SearchStrings(stopwords, word) + if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) { + messageMap[word]++ } } - wg.Done() - }(h) - } - wg.Wait() + } + return true + }) wc := rankByWordCount(messageMap) if len(wc) > 20 { From 1f66f47ce67ce29e6bd312e85cf04009131e279a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:08:05 +0800 Subject: [PATCH 33/51] chore: bump deps (#1188) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- gomod2nix.toml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 4ea7195ba2..fbccd6a82e 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -29,8 +29,8 @@ schema = 3 version = "v1.7.0" hash = "sha256-HDDnE0oktWJH1tkxuQwUUbeJhmVwY5fyc/vR72D2mkU=" [mod."github.com/FloatTech/zbputils"] - version = "v1.7.2-0.20250614165821-95cf57cf2434" - hash = "sha256-9r2nbDAo/ZCDSAXuOHUKaQM5bEoSOAFb0FJTDxne/gQ=" + version = "v1.7.2-0.20250812085410-2741050f465f" + hash = "sha256-NoCU7tqzihm2xEr1LelrfMzeg9RDQ9OsFBVXfNDcxvs=" [mod."github.com/RomiChan/syncx"] version = "v0.0.0-20240418144900-b7402ffdebc7" hash = "sha256-L1j1vgiwqXpF9pjMoRRlrQUHzoULisw/01plaEAwxs4=" @@ -77,8 +77,8 @@ schema = 3 version = "v1.3.0" hash = "sha256-/sN7X8dKXQgv8J+EDzVUB+o+AY9gBC8e1C6sYhaTy1k=" [mod."github.com/fumiama/deepinfra"] - version = "v0.0.0-20250601112706-0175c95164c1" - hash = "sha256-/8Hufq5n84QHOgS0igYQWo1zxjOBBbrqad2wQfKHBhY=" + version = "v0.0.0-20250812083039-f1b27f21d8c9" + hash = "sha256-P2WdChVhLEjZFV2B4O7j2lNyI6NpfuzVyjvwjbj5NPI=" [mod."github.com/fumiama/go-base16384"] version = "v1.7.0" hash = "sha256-vTAsBBYe2ISzb2Nba5E96unodZSkhMcqo6hbwR01nz8=" @@ -227,8 +227,8 @@ schema = 3 version = "v2.1.2" hash = "sha256-GXWWea/u6BezTsPPrWhTYiTetPP/YW6P+Sj4YdocPaM=" [mod."github.com/wdvxdr1123/ZeroBot"] - version = "v1.8.2-0.20250707133321-6197b8ee5df7" - hash = "sha256-iVAgUtXm/SO3BcdW4XDRFnbZBUk9b1GbIILajibcFo8=" + version = "v1.8.2-0.20250804063440-ccc03e33ac20" + hash = "sha256-2bFcPmcDsZxTD3sU3i2QD4M/ehSF43Ohf5ltuq1QtOQ=" [mod."github.com/yusufpapurcu/wmi"] version = "v1.2.4" hash = "sha256-N+YDBjOW59YOsZ2lRBVtFsEEi48KhNQRb63/0ZSU3bA=" From 20d49ccf15e09bf04be758f62ef3f8c89174b8b4 Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Mon, 1 Sep 2025 22:38:15 +0800 Subject: [PATCH 34/51] =?UTF-8?q?feat(aichat):=20=E6=B7=BB=E5=8A=A0/gpt?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=EF=BC=8C=E7=9B=B4=E6=8E=A5=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=20(#1190)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ 添加大模型聊天,便于使用版本的 * 🐛 减少重复关键词 * 🎨 优化换行符 * ✨ 添加转换函数 * ✨ 添加lint优化 * 🎨 按字符切片 * 🎨 修改lint --- plugin/aichat/main.go | 220 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 179 insertions(+), 41 deletions(-) diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index dbb9e9aa80..fc78af58c6 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -2,6 +2,7 @@ package aichat import ( + "errors" "math/rand" "strconv" "strings" @@ -46,7 +47,9 @@ var ( "- 设置AI聊天(不)以AI语音输出\n" + "- 查看AI聊天配置\n" + "- 重置AI聊天\n" + - "- 群聊总结 [消息数目]|群聊总结 1000\n", + "- 群聊总结 [消息数目]|群聊总结 1000\n" + + "- /gpt [内容] (使用大模型聊天)\n", + PrivateDataFolder: "aichat", }) ) @@ -61,6 +64,32 @@ var ( limit = ctxext.NewLimiterManager(time.Second*30, 1) ) +// getModelParams 获取模型参数:温度(float32(temp)/100)、TopP和最大长度 +func getModelParams(temp int64) (temperature float32, topp float32, maxn uint) { + // 处理温度参数 + if temp <= 0 { + temp = 70 // default setting + } + if temp > 100 { + temp = 100 + } + temperature = float32(temp) / 100 + + // 处理TopP参数 + topp = cfg.TopP + if topp == 0 { + topp = 0.9 + } + + // 处理最大长度参数 + maxn = cfg.MaxN + if maxn == 0 { + maxn = 4096 + } + + return temperature, topp, maxn +} + func init() { en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool { return ctx.ExtractPlainText() != "" && @@ -88,39 +117,25 @@ func init() { return } - if temp <= 0 { - temp = 70 // default setting - } - if temp > 100 { - temp = 100 - } + temperature, topp, maxn := getModelParams(temp) x := deepinfra.NewAPI(cfg.API, cfg.Key) var mod model.Protocol - maxn := cfg.MaxN - if maxn == 0 { - maxn = 4096 - } - topp := cfg.TopP - if topp == 0 { - topp = 0.9 - } - switch cfg.Type { case 0: mod = model.NewOpenAI( cfg.ModelName, cfg.Separator, - float32(temp)/100, topp, maxn, + temperature, topp, maxn, ) case 1: mod = model.NewOLLaMA( cfg.ModelName, cfg.Separator, - float32(temp)/100, topp, maxn, + temperature, topp, maxn, ) case 2: mod = model.NewGenAI( cfg.ModelName, - float32(temp)/100, topp, maxn, + temperature, topp, maxn, ) default: logrus.Warnln("[aichat] unsupported AI type", cfg.Type) @@ -319,6 +334,16 @@ func init() { // 添加群聊总结功能 en.OnRegex(`^群聊总结\s?(\d*)$`, ensureconfig, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).Limit(limit.LimitByGroup).Handle(func(ctx *zero.Ctx) { ctx.SendChain(message.Text("少女思考中...")) + gid := ctx.Event.GroupID + if gid == 0 { + gid = -ctx.Event.UserID + } + c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) + if !ok { + return + } + rate := c.GetData(gid) + temp := (rate >> 8) & 0xff p, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) if p > 1000 { p = 1000 @@ -326,10 +351,9 @@ func init() { if p == 0 { p = 200 } - gid := ctx.Event.GroupID group := ctx.GetGroupInfo(gid, false) if group.MemberCount == 0 { - ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取摘要")) + ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "未加入", group.Name, "(", gid, "),无法获取总结")) return } @@ -350,8 +374,13 @@ func init() { return } - // 调用大模型API进行摘要 - summary, err := summarizeMessages(messages) + // 构造总结请求提示 + summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n" + + strings.Join(messages, "\n") + + // 调用大模型API进行总结 + summary, err := llmchat(summaryPrompt, temp) + if err != nil { ctx.SendChain(message.Text("ERROR: ", err)) return @@ -367,13 +396,108 @@ func init() { b.WriteString(" 条消息总结:\n\n") b.WriteString(summary) - // 分割总结内容为多段 - parts := strings.Split(b.String(), "\n\n") - msg := make(message.Message, 0, len(parts)) - for _, part := range parts { - if part != "" { - msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(part))) + // 分割总结内容为多段(按1000字符长度切割) + summaryText := b.String() + msg := make(message.Message, 0) + for len(summaryText) > 0 { + if len(summaryText) <= 1000 { + msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(summaryText))) + break } + + // 查找1000字符内的最后一个换行符,尽量在换行处分割 + chunk := summaryText[:1000] + lastNewline := strings.LastIndex(chunk, "\n") + if lastNewline > 0 { + chunk = summaryText[:lastNewline+1] + } + + msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk))) + summaryText = summaryText[len(chunk):] + } + if len(msg) > 0 { + ctx.Send(msg) + } + }) + + // 添加 /gpt 命令处理(同时支持回复消息和直接使用) + en.OnKeyword("/gpt", ensureconfig).SetBlock(true).Handle(func(ctx *zero.Ctx) { + gid := ctx.Event.GroupID + if gid == 0 { + gid = -ctx.Event.UserID + } + c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) + if !ok { + return + } + rate := c.GetData(gid) + temp := (rate >> 8) & 0xff + text := ctx.MessageString() + + var query string + var replyContent string + + // 检查是否是回复消息 (使用MessageElement检查而不是CQ码) + for _, elem := range ctx.Event.Message { + if elem.Type == "reply" { + // 提取被回复的消息ID + replyIDStr := elem.Data["id"] + replyID, err := strconv.ParseInt(replyIDStr, 10, 64) + if err == nil { + // 获取被回复的消息内容 + replyMsg := ctx.GetMessage(replyID) + if replyMsg.Elements != nil { + replyContent = replyMsg.Elements.ExtractPlainText() + } + } + break // 找到回复元素后退出循环 + } + } + + // 提取 /gpt 后面的内容 + parts := strings.SplitN(text, "/gpt", 2) + + var gContent string + if len(parts) > 1 { + gContent = strings.TrimSpace(parts[1]) + } + + // 组合内容:优先使用回复内容,如果同时有/gpt内容则拼接 + switch { + case replyContent != "" && gContent != "": + query = replyContent + "\n" + gContent + case replyContent != "": + query = replyContent + case gContent != "": + query = gContent + default: + return + } + + // 调用大模型API进行聊天 + reply, err := llmchat(query, temp) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + + // 分割总结内容为多段(按1000字符长度切割) + msg := make(message.Message, 0) + for len(reply) > 0 { + if len(reply) <= 1000 { + msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(reply))) + break + } + + // 查找1000字符内的最后一个换行符,尽量在换行处分割 + chunk := reply[:1000] + lastNewline := strings.LastIndex(chunk, "\n") + if lastNewline > 0 { + chunk = reply[:lastNewline+1] + } + + msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Text(chunk))) + reply = reply[len(chunk):] } if len(msg) > 0 { ctx.Send(msg) @@ -381,20 +505,34 @@ func init() { }) } -// summarizeMessages 调用大模型API进行消息摘要 -func summarizeMessages(messages []string) (string, error) { - // 使用现有的AI配置进行摘要 - x := deepinfra.NewAPI(cfg.API, cfg.Key) - mod := model.NewOpenAI( - cfg.ModelName, cfg.Separator, - float32(70)/100, 0.9, 4096, - ) +// llmchat 调用大模型API包装 +func llmchat(prompt string, temp int64) (string, error) { + temperature, topp, maxn := getModelParams(temp) // 使用默认温度70 - // 构造摘要请求提示 - summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n\n" + - strings.Join(messages, "\n---\n") + x := deepinfra.NewAPI(cfg.API, cfg.Key) + var mod model.Protocol + switch cfg.Type { + case 0: + mod = model.NewOpenAI( + cfg.ModelName, cfg.Separator, + temperature, topp, maxn, + ) + case 1: + mod = model.NewOLLaMA( + cfg.ModelName, cfg.Separator, + temperature, topp, maxn, + ) + case 2: + mod = model.NewGenAI( + cfg.ModelName, + temperature, topp, maxn, + ) + default: + logrus.Warnln("[aichat] unsupported AI type", cfg.Type) + return "", errors.New("不支持的AI类型") + } - data, err := x.Request(mod.User(summaryPrompt)) + data, err := x.Request(mod.User(prompt)) if err != nil { return "", err } From 1e7b2d3335664338bda657f132a920d551d913df Mon Sep 17 00:00:00 2001 From: Kajiekazz <145256947+Kajiekazz@users.noreply.github.com> Date: Mon, 1 Sep 2025 22:42:42 +0800 Subject: [PATCH 35/51] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=20crypter=20(#1191)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.go | 1 + plugin/crypter/fumo.go | 94 ++++++++++++++++++++++++++++++++++++++ plugin/crypter/handlers.go | 33 +++++++++++++ plugin/crypter/hou.go | 87 +++++++++++++++++++++++++++++++++++ plugin/crypter/main.go | 31 +++++++++++++ 5 files changed, 246 insertions(+) create mode 100644 plugin/crypter/fumo.go create mode 100644 plugin/crypter/handlers.go create mode 100644 plugin/crypter/hou.go create mode 100644 plugin/crypter/main.go diff --git a/main.go b/main.go index 117b111aa1..4de143cfcc 100644 --- a/main.go +++ b/main.go @@ -84,6 +84,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chrev" // 英文字符翻转 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/coser" // 三次元小姐姐 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/cpstory" // cp短打 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/crypter" // 奇怪语言加解密 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/dailynews" // 今日早报 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/danbooru" // DeepDanbooru二次元图标签识别 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/diana" // 嘉心糖发病 diff --git a/plugin/crypter/fumo.go b/plugin/crypter/fumo.go new file mode 100644 index 0000000000..e488bcb36a --- /dev/null +++ b/plugin/crypter/fumo.go @@ -0,0 +1,94 @@ +// Package crypter Fumo语 +package crypter + +import ( + "encoding/base64" + "fmt" + "regexp" + "strings" +) + +// Base64字符表 +const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + +// Fumo语字符表 - 使用各种fumo变体来表示base64字符 +var fumoChars = []string{ + "fumo-", "Fumo-", "fUmo-", "fuMo-", "fumO-", "FUmo-", "FuMo-", "FumO-", + "fUMo-", "fUmO-", "fuMO-", "FUMo-", "FUmO-", "fUMO-", "FUMO-", "fumo.", + "Fumo.", "fUmo.", "fuMo.", "fumO.", "FUmo.", "FuMo.", "FumO.", "fUMo.", + "fUmO.", "fuMO.", "FUMo.", "FUmO.", "fUMO.", "FUMO.", "fumo,", "Fumo,", + "fUmo,", "fuMo,", "fumO,", "FUmo,", "FuMo,", "FumO,", "fUMo,", "fUmO,", + "fuMO,", "FUMo,", "FuMO,", "fUMO,", "FUMO,", "fumo+", "Fumo+", "fUmo+", + "fuMo+", "fumO+", "FUmo+", "FuMo+", "FumO+", "fUMo+", "fUmO+", "fuMO+", + "FUMo+", "FUmO+", "fUMO+", "FUMO+", "fumo|", "Fumo|", "fUmo|", "fuMo|", + "fumO|", "FUmo|", "FuMo|", "FumO|", "fUMo|", "fUmO|", "fuMO|", "fumo/", + "Fumo/", "fUmo/", +} + +//Base64 2 Fumo +// 创建编码映射表 +var encodeMap = make(map[byte]string) +// 创建解码映射表 +var decodeMap = make(map[string]byte) + +func init() { + for i := 0; i < 64 && i < len(fumoChars); i++ { + base64Char := base64Chars[i] + fumoChar := fumoChars[i] + + encodeMap[base64Char] = fumoChar + decodeMap[fumoChar] = base64Char + } +} + +//加密 +func encryptFumo(text string) string { + if text == "" { + return "请输入要加密的文本" + } + textBytes := []byte(text) + base64String := base64.StdEncoding.EncodeToString(textBytes) + base64Body := strings.TrimRight(base64String, "=") + paddingCount := len(base64String) - len(base64Body) + var fumoBody strings.Builder + for _, char := range base64Body { + if fumoChar, exists := encodeMap[byte(char)]; exists { + fumoBody.WriteString(fumoChar) + } else { + return fmt.Sprintf("Fumo加密失败: 未知字符 %c", char) + } + } + result := fumoBody.String() + strings.Repeat("=", paddingCount) + + return result +} + +//解密 +func decryptFumo(fumoText string) string { + if fumoText == "" { + return "请输入要解密的Fumo语密文" + } + fumoBody := strings.TrimRight(fumoText, "=") + paddingCount := len(fumoText) - len(fumoBody) + fumoPattern := regexp.MustCompile(`(\w+[-.,+|/])`) + fumoWords := fumoPattern.FindAllString(fumoBody, -1) + reconstructed := strings.Join(fumoWords, "") + if reconstructed != fumoBody { + return "Fumo解密失败: 包含无效的Fumo字符或格式错误" + } + var base64Body strings.Builder + for _, fumoWord := range fumoWords { + if base64Char, exists := decodeMap[fumoWord]; exists { + base64Body.WriteByte(base64Char) + } else { + return fmt.Sprintf("Fumo解密失败: 包含无效的Fumo字符 %s", fumoWord) + } + } + base64String := base64Body.String() + strings.Repeat("=", paddingCount) + decodedBytes, err := base64.StdEncoding.DecodeString(base64String) + if err != nil { + return fmt.Sprintf("Fumo解密失败: Base64解码错误 %v", err) + } + originalText := string(decodedBytes) + return originalText +} \ No newline at end of file diff --git a/plugin/crypter/handlers.go b/plugin/crypter/handlers.go new file mode 100644 index 0000000000..cc949378fa --- /dev/null +++ b/plugin/crypter/handlers.go @@ -0,0 +1,33 @@ +// Package crypter 处理函数 +package crypter + +import ( + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +//hou +func houEncryptHandler(ctx *zero.Ctx) { + text := ctx.State["regex_matched"].([]string)[1] + result := encodeHou(text) + ctx.SendChain(message.Text(result)) +} + +func houDecryptHandler(ctx *zero.Ctx) { + text := ctx.State["regex_matched"].([]string)[1] + result := decodeHou(text) + ctx.SendChain(message.Text(result)) +} + +//fumo +func fumoEncryptHandler(ctx *zero.Ctx) { + text := ctx.State["regex_matched"].([]string)[1] + result := encryptFumo(text) + ctx.SendChain(message.Text(result)) +} + +func fumoDecryptHandler(ctx *zero.Ctx) { + text := ctx.State["regex_matched"].([]string)[1] + result := decryptFumo(text) + ctx.SendChain(message.Text(result)) +} \ No newline at end of file diff --git a/plugin/crypter/hou.go b/plugin/crypter/hou.go new file mode 100644 index 0000000000..7bda968af7 --- /dev/null +++ b/plugin/crypter/hou.go @@ -0,0 +1,87 @@ +// Package crypter 齁语加解密 +package crypter + +import ( + "strings" +) + +// 齁语密码表 +var houCodebook = []string{ + "齁", "哦", "噢", "喔", "咕", "咿", "嗯", "啊", + "~", "哈", "!", "唔", "哼", "❤", "呃", "呼", +} +// 索引: 0 1 2 3 4 5 6 7 +// 8 9 10 11 12 13 14 15 + +// 创建映射表 +var houCodebookMap = make(map[string]int) + +// 初始化映射表 +func init() { + for idx, ch := range houCodebook { + houCodebookMap[ch] = idx + } +} + +func encodeHou(text string) string { + if text == "" { + return "请输入要加密的文本" + } + var encoded strings.Builder + textBytes := []byte(text) + for _, b := range textBytes { + high := (b >> 4) & 0x0F + low := b & 0x0F + encoded.WriteString(houCodebook[high]) + encoded.WriteString(houCodebook[low]) + } + + return encoded.String() +} + +func decodeHou(code string) string { + if code == "" { + return "请输入要解密的齁语密文" + } + + // 过滤出有效的齁语字符 + var validChars []string + for _, r := range code { + charStr := string(r) + if _, exists := houCodebookMap[charStr]; exists { + validChars = append(validChars, charStr) + } + } + + if len(validChars)%2 != 0 { + return "齁语密文长度错误,无法解密" + } + + // 解密过程 + var byteList []byte + for i := 0; i < len(validChars); i += 2 { + highIdx, highExists := houCodebookMap[validChars[i]] + lowIdx, lowExists := houCodebookMap[validChars[i+1]] + + if !highExists || !lowExists { + return "齁语密文包含无效字符" + } + + originalByte := byte((highIdx << 4) | lowIdx) + byteList = append(byteList, originalByte) + } + + result := string(byteList) + + if !isValidUTF8(result) { + return "齁语解密失败,结果不是有效的文本" + } + + return result +} + +// 检查字符串是否为有效的UTF-8编码 +func isValidUTF8(s string) bool { + // Go的string类型默认就是UTF-8,如果转换没有出错说明是有效的 + return len(s) > 0 || s == "" +} \ No newline at end of file diff --git a/plugin/crypter/main.go b/plugin/crypter/main.go new file mode 100644 index 0000000000..e850a9b5c5 --- /dev/null +++ b/plugin/crypter/main.go @@ -0,0 +1,31 @@ +// Package crypter 奇怪语言加解密 +package crypter + +import ( + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + zero "github.com/wdvxdr1123/ZeroBot" +) + +func init() { + engine := control.Register("crypter", &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Brief: "奇怪语言加解密", + Help: "多种语言加解密插件\n" + + "- 齁语加解密:\n" + + "- 齁语加密 [文本] 或 h加密 [文本]\n" + + "- 齁语解密 [密文] 或 h解密 [密文]\n\n" + + "- Fumo语加解密:\n" + + "- fumo加密 [文本]\n" + + "- fumo解密 [密文]\n\n", + PublicDataFolder: "Crypter", + }) + + //hou + engine.OnRegex(`^(?:齁语加密|h加密)\s*(.+)$`).SetBlock(true).Handle(houEncryptHandler) + engine.OnRegex(`^(?:齁语解密|h解密)\s*(.+)$`).SetBlock(true).Handle(houDecryptHandler) + + //Fumo + engine.OnRegex(`^fumo加密\s*(.+)$`).SetBlock(true).Handle(fumoEncryptHandler) + engine.OnRegex(`^fumo解密\s*(.+)$`).SetBlock(true).Handle(fumoDecryptHandler) +} \ No newline at end of file From cd16a755d7f6e74a55370fada535271d1f56ebc7 Mon Sep 17 00:00:00 2001 From: Kajiekazz <145256947+Kajiekazz@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:12:13 +0800 Subject: [PATCH 36/51] doc: update README.md (#1193) --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index f7535e4ae7..e923a77fdc 100644 --- a/README.md +++ b/README.md @@ -645,6 +645,17 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 磕cp大老师 雪乃 +
+
+ 奇怪语言加解密 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/crypter"` + + - [x] 齁语加密 [文本] 或 h加密 [文本] + - [x] 齁语解密 [密文] 或 h解密 [密文] + - [x] fumo加密 [文本] + - [x] fumo解密 [文本] +
今日早报 From 35292a69fc4f27335ba5cec38520b09a4c87cea2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:13:22 +0800 Subject: [PATCH 37/51] =?UTF-8?q?chore(lint):=20=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=B7=E5=BC=8F=20(#1192)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- plugin/crypter/fumo.go | 17 +++++++++-------- plugin/crypter/handlers.go | 6 +++--- plugin/crypter/hou.go | 21 +++++++++++---------- plugin/crypter/main.go | 8 ++++---- 4 files changed, 27 insertions(+), 25 deletions(-) diff --git a/plugin/crypter/fumo.go b/plugin/crypter/fumo.go index e488bcb36a..a96a1dfbf8 100644 --- a/plugin/crypter/fumo.go +++ b/plugin/crypter/fumo.go @@ -20,14 +20,15 @@ var fumoChars = []string{ "fUmo,", "fuMo,", "fumO,", "FUmo,", "FuMo,", "FumO,", "fUMo,", "fUmO,", "fuMO,", "FUMo,", "FuMO,", "fUMO,", "FUMO,", "fumo+", "Fumo+", "fUmo+", "fuMo+", "fumO+", "FUmo+", "FuMo+", "FumO+", "fUMo+", "fUmO+", "fuMO+", - "FUMo+", "FUmO+", "fUMO+", "FUMO+", "fumo|", "Fumo|", "fUmo|", "fuMo|", - "fumO|", "FUmo|", "FuMo|", "FumO|", "fUMo|", "fUmO|", "fuMO|", "fumo/", + "FUMo+", "FUmO+", "fUMO+", "FUMO+", "fumo|", "Fumo|", "fUmo|", "fuMo|", + "fumO|", "FUmo|", "FuMo|", "FumO|", "fUMo|", "fUmO|", "fuMO|", "fumo/", "Fumo/", "fUmo/", } -//Base64 2 Fumo +// Base64 2 Fumo // 创建编码映射表 var encodeMap = make(map[byte]string) + // 创建解码映射表 var decodeMap = make(map[string]byte) @@ -35,13 +36,13 @@ func init() { for i := 0; i < 64 && i < len(fumoChars); i++ { base64Char := base64Chars[i] fumoChar := fumoChars[i] - + encodeMap[base64Char] = fumoChar decodeMap[fumoChar] = base64Char } } -//加密 +// 加密 func encryptFumo(text string) string { if text == "" { return "请输入要加密的文本" @@ -59,11 +60,11 @@ func encryptFumo(text string) string { } } result := fumoBody.String() + strings.Repeat("=", paddingCount) - + return result } -//解密 +// 解密 func decryptFumo(fumoText string) string { if fumoText == "" { return "请输入要解密的Fumo语密文" @@ -91,4 +92,4 @@ func decryptFumo(fumoText string) string { } originalText := string(decodedBytes) return originalText -} \ No newline at end of file +} diff --git a/plugin/crypter/handlers.go b/plugin/crypter/handlers.go index cc949378fa..e194e4b5ab 100644 --- a/plugin/crypter/handlers.go +++ b/plugin/crypter/handlers.go @@ -6,7 +6,7 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" ) -//hou +// hou func houEncryptHandler(ctx *zero.Ctx) { text := ctx.State["regex_matched"].([]string)[1] result := encodeHou(text) @@ -19,7 +19,7 @@ func houDecryptHandler(ctx *zero.Ctx) { ctx.SendChain(message.Text(result)) } -//fumo +// fumo func fumoEncryptHandler(ctx *zero.Ctx) { text := ctx.State["regex_matched"].([]string)[1] result := encryptFumo(text) @@ -30,4 +30,4 @@ func fumoDecryptHandler(ctx *zero.Ctx) { text := ctx.State["regex_matched"].([]string)[1] result := decryptFumo(text) ctx.SendChain(message.Text(result)) -} \ No newline at end of file +} diff --git a/plugin/crypter/hou.go b/plugin/crypter/hou.go index 7bda968af7..6302375bcd 100644 --- a/plugin/crypter/hou.go +++ b/plugin/crypter/hou.go @@ -7,9 +7,10 @@ import ( // 齁语密码表 var houCodebook = []string{ - "齁", "哦", "噢", "喔", "咕", "咿", "嗯", "啊", + "齁", "哦", "噢", "喔", "咕", "咿", "嗯", "啊", "~", "哈", "!", "唔", "哼", "❤", "呃", "呼", } + // 索引: 0 1 2 3 4 5 6 7 // 8 9 10 11 12 13 14 15 @@ -35,7 +36,7 @@ func encodeHou(text string) string { encoded.WriteString(houCodebook[high]) encoded.WriteString(houCodebook[low]) } - + return encoded.String() } @@ -52,31 +53,31 @@ func decodeHou(code string) string { validChars = append(validChars, charStr) } } - + if len(validChars)%2 != 0 { return "齁语密文长度错误,无法解密" } - + // 解密过程 var byteList []byte for i := 0; i < len(validChars); i += 2 { highIdx, highExists := houCodebookMap[validChars[i]] lowIdx, lowExists := houCodebookMap[validChars[i+1]] - + if !highExists || !lowExists { return "齁语密文包含无效字符" } - + originalByte := byte((highIdx << 4) | lowIdx) byteList = append(byteList, originalByte) } - + result := string(byteList) - + if !isValidUTF8(result) { return "齁语解密失败,结果不是有效的文本" } - + return result } @@ -84,4 +85,4 @@ func decodeHou(code string) string { func isValidUTF8(s string) bool { // Go的string类型默认就是UTF-8,如果转换没有出错说明是有效的 return len(s) > 0 || s == "" -} \ No newline at end of file +} diff --git a/plugin/crypter/main.go b/plugin/crypter/main.go index e850a9b5c5..4a94ffcaaa 100644 --- a/plugin/crypter/main.go +++ b/plugin/crypter/main.go @@ -10,7 +10,7 @@ import ( func init() { engine := control.Register("crypter", &ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, - Brief: "奇怪语言加解密", + Brief: "奇怪语言加解密", Help: "多种语言加解密插件\n" + "- 齁语加解密:\n" + "- 齁语加密 [文本] 或 h加密 [文本]\n" + @@ -21,11 +21,11 @@ func init() { PublicDataFolder: "Crypter", }) - //hou + // hou engine.OnRegex(`^(?:齁语加密|h加密)\s*(.+)$`).SetBlock(true).Handle(houEncryptHandler) engine.OnRegex(`^(?:齁语解密|h解密)\s*(.+)$`).SetBlock(true).Handle(houDecryptHandler) - //Fumo + // Fumo engine.OnRegex(`^fumo加密\s*(.+)$`).SetBlock(true).Handle(fumoEncryptHandler) engine.OnRegex(`^fumo解密\s*(.+)$`).SetBlock(true).Handle(fumoDecryptHandler) -} \ No newline at end of file +} From ac2d53352c3f4bca6285777d4299ce9c180483f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=AB=E6=80=9D=E6=BD=8B?= <55676105+shudorcl@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:02:12 +0800 Subject: [PATCH 38/51] =?UTF-8?q?feat(bilibiliparse):=20B=E7=AB=99?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E8=A7=A3=E6=9E=90=20=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=BC=80=E5=85=B3=20(#1196)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add control for getVideoDownload * fix: typo --- plugin/bilibili/bilibili_parse.go | 53 +++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/plugin/bilibili/bilibili_parse.go b/plugin/bilibili/bilibili_parse.go index 189c6586f2..81dd03bdaa 100644 --- a/plugin/bilibili/bilibili_parse.go +++ b/plugin/bilibili/bilibili_parse.go @@ -24,8 +24,10 @@ import ( ) const ( - enableHex = 0x10 - unableHex = 0x7fffffff_fffffffd + enableVideoSummary = int64(0x10) + disableVideoSummary = ^enableVideoSummary + enableVideoDownload = int64(0x20) + disableVideoDownload = ^enableVideoDownload bilibiliparseReferer = "https://www.bilibili.com" ) @@ -92,9 +94,9 @@ func init() { data := c.GetData(ctx.Event.GroupID) switch option { case "开启", "打开", "启用": - data |= enableHex + data |= enableVideoSummary case "关闭", "关掉", "禁用": - data &= unableHex + data &= disableVideoSummary default: return } @@ -105,6 +107,35 @@ func init() { } 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) @@ -127,7 +158,7 @@ func handleVideo(ctx *zero.Ctx) { return } c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) - if ok && c.GetData(ctx.Event.GroupID)&enableHex == enableHex { + if ok && c.GetData(ctx.Event.GroupID)&enableVideoSummary == enableVideoSummary { summaryMsg, err := getVideoSummary(cfg, card) if err != nil { msg = append(msg, message.Text("ERROR: ", err)) @@ -136,12 +167,14 @@ func handleVideo(ctx *zero.Ctx) { } } ctx.SendChain(msg...) - downLoadMsg, err := getVideoDownload(cfg, card, cachePath) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return + 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...) } - ctx.SendChain(downLoadMsg...) } func handleDynamic(ctx *zero.Ctx) { From fb090839d6bcbb237a33e63b3d9f0b81b37f633c Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Wed, 3 Sep 2025 17:07:09 +0800 Subject: [PATCH 39/51] =?UTF-8?q?feat(crypter):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E8=AF=AD=E9=9F=B3=20(#1197)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/crypter/handlers.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugin/crypter/handlers.go b/plugin/crypter/handlers.go index e194e4b5ab..1cec8a47ee 100644 --- a/plugin/crypter/handlers.go +++ b/plugin/crypter/handlers.go @@ -2,6 +2,8 @@ package crypter import ( + "github.com/FloatTech/AnimeAPI/airecord" + "github.com/sirupsen/logrus" zero "github.com/wdvxdr1123/ZeroBot" "github.com/wdvxdr1123/ZeroBot/message" ) @@ -10,7 +12,14 @@ import ( func houEncryptHandler(ctx *zero.Ctx) { text := ctx.State["regex_matched"].([]string)[1] result := encodeHou(text) - ctx.SendChain(message.Text(result)) + logrus.Infoln("[crypter] 回复内容:", result) + recCfg := airecord.GetConfig() + record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, result) + if record != "" { + ctx.SendChain(message.Record(record)) + } else { + ctx.SendChain(message.Text(result)) + } } func houDecryptHandler(ctx *zero.Ctx) { From 08e02ab7306a76bccba2667b7304275de02f1b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:32:30 +0800 Subject: [PATCH 40/51] =?UTF-8?q?fix(aichat):=20adapt=20to=20=E7=99=BE?= =?UTF-8?q?=E7=82=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data | 2 +- go.mod | 2 +- go.sum | 4 ++-- plugin/aichat/main.go | 8 ++++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/data b/data index 4f751a1cda..6bcac0faab 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 4f751a1cda692b5b8cb909ce1376a3a0ea0d57cc +Subproject commit 6bcac0faaba9dc8664a84ae4de46154cfd9e740c diff --git a/go.mod b/go.mod index 3e7cd6dc5d..ee50c08ea4 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/fumiama/ahsai v0.1.0 github.com/fumiama/cron v1.3.0 - github.com/fumiama/deepinfra v0.0.0-20250812083039-f1b27f21d8c9 + github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4 github.com/fumiama/go-base16384 v1.7.0 github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 diff --git a/go.sum b/go.sum index 31ada71b36..f17e20ce8f 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4= github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk= github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo= github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY= -github.com/fumiama/deepinfra v0.0.0-20250812083039-f1b27f21d8c9 h1:X2h8RnCgC04LmwBoizYbFawXh/h6CouXmhYtaVuUn7k= -github.com/fumiama/deepinfra v0.0.0-20250812083039-f1b27f21d8c9/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= +github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4 h1:cV3HXXLNudIL9rIEYt1RCgl6H4703nE3+jL4pJNsRtc= +github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA= github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs= diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index fc78af58c6..50c875a9de 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -126,7 +126,9 @@ func init() { mod = model.NewOpenAI( cfg.ModelName, cfg.Separator, temperature, topp, maxn, - ) + ).SetExtra(&map[string]bool{ + "enable_thinking": false, + }) case 1: mod = model.NewOLLaMA( cfg.ModelName, cfg.Separator, @@ -516,7 +518,9 @@ func llmchat(prompt string, temp int64) (string, error) { mod = model.NewOpenAI( cfg.ModelName, cfg.Separator, temperature, topp, maxn, - ) + ).SetExtra(&map[string]bool{ + "enable_thinking": false, + }) case 1: mod = model.NewOLLaMA( cfg.ModelName, cfg.Separator, From b012df4c230e2636eec85736a37e0a2e6dd28b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=87=7E?= <158024940+xyy0411@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:33:21 +0800 Subject: [PATCH 41/51] =?UTF-8?q?feat(niuniu):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8F=AF=E8=87=AA=E5=AE=9A=E4=B9=89=E8=B4=AD=E4=B9=B0=E5=95=86?= =?UTF-8?q?=E5=93=81=E6=95=B0=E9=87=8F=EF=BC=8C=E8=B0=83=E6=95=B4=E5=95=86?= =?UTF-8?q?=E5=93=81=E5=8D=95=E4=BB=B7=20(#1189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 6 ++-- plugin/niuniu/main.go | 77 +++++++++++++++++++++++-------------------- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index ee50c08ea4..0a5fe897cc 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/Baidu-AIP/golang-sdk v1.1.1 - github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46 + github.com/FloatTech/AnimeAPI v1.7.1-0.20250901143505-180d33844860 github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 github.com/FloatTech/gg v1.1.3 github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef diff --git a/go.sum b/go.sum index f17e20ce8f..54224894f0 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw= github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46 h1:X6ZbOWoZJIoHCin+CeU92Q3EwpvglyQ4gc5BZhOtAwo= -github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250901143505-180d33844860 h1:ddthsMzYC2LZ517/71W//9VsXT82CSBALVt3sQY5vfA= +github.com/FloatTech/AnimeAPI v1.7.1-0.20250901143505-180d33844860/go.mod h1:CzpSeo5Pvslnq7Ho14E438Yn/flFMKzjGeX2nbC1mzk= github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 h1:lFD1pd8NkYCrw0QpTX/T5pJ67I7AL5eGxQ4v0r9f81Q= github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80/go.mod h1:IWoFFqu+0FeaHHQdddyiTRL5z7gJME6qHC96qh0R2sc= github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4= @@ -124,8 +124,8 @@ github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jozsefsallai/gophersauce v1.0.1 h1:BA3ovtQRrAb1qYU9JoRLbDHpxnDunlNcEkEfhCvDDCM= github.com/jozsefsallai/gophersauce v1.0.1/go.mod h1:YVEI7djliMTmZ1Vh01YPF8bUHi+oKhe3yXgKf1T49vg= github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY= diff --git a/plugin/niuniu/main.go b/plugin/niuniu/main.go index 013903ddfc..d49b5a062f 100644 --- a/plugin/niuniu/main.go +++ b/plugin/niuniu/main.go @@ -2,9 +2,11 @@ package niuniu import ( + "errors" "fmt" "math/rand" "strconv" + "strings" "time" "github.com/FloatTech/AnimeAPI/niu" @@ -18,12 +20,6 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" ) -type lastLength struct { - TimeLimit time.Time - Count int - Length float64 -} - var ( en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, @@ -47,8 +43,8 @@ var ( }) dajiaoLimiter = rate.NewManager[string](time.Second*90, 1) jjLimiter = rate.NewManager[string](time.Second*150, 1) - jjCount = syncx.Map[string, *lastLength]{} - register = syncx.Map[string, *lastLength]{} + jjCount = syncx.Map[string, *niu.PKRecord]{} + register = syncx.Map[string, *niu.PKRecord]{} ) func init() { @@ -103,15 +99,19 @@ func init() { en.OnFullMatch("出售牛牛", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { gid := ctx.Event.GroupID uid := ctx.Event.UserID + key := fmt.Sprintf("%d_%d", gid, uid) sell, err := niu.Sell(gid, uid) - if err != nil { + if errors.Is(err, niu.ErrCanceled) || errors.Is(err, niu.ErrNoNiuNiu) { + ctx.SendChain(message.Text(err)) + jjCount.Delete(key) + return + } else if err != nil { ctx.SendChain(message.Text("ERROR:", err)) return } + // 数据库操作成功之后,及时删除残留的缓存 - key := fmt.Sprintf("%d_%d", gid, uid) - _, ok := jjCount.Load(key) - if ok { + if _, ok := jjCount.Load(key); ok { jjCount.Delete(key) } ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(sell)) @@ -140,29 +140,33 @@ func init() { cost int scope string description string - count int }{ - 1: {"伟哥", 300, "打胶", "可以让你打胶每次都增长", 5}, - 2: {"媚药", 300, "打胶", "可以让你打胶每次都减少", 5}, - 3: {"击剑神器", 500, "jj", "可以让你每次击剑都立于不败之地", 2}, - 4: {"击剑神稽", 500, "jj", "可以让你每次击剑都失败", 2}, + 1: {"伟哥", 100, "打胶", "可以让你打胶每次都增长"}, + 2: {"媚药", 100, "打胶", "可以让你打胶每次都减少"}, + 3: {"击剑神器", 300, "jj", "可以让你每次击剑都立于不败之地"}, + 4: {"击剑神稽", 300, "jj", "可以让你每次击剑都失败"}, } var messages message.Message + messages = append(messages, ctxext.FakeSenderForwardNode(ctx, + message.Text("输入对应序号进行购买商品"), + message.Text( + "使用说明:\n"+ + "商品id-商品数量\n"+ + "如想购买10个伟哥\n"+ + "即:1-10"))) messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text("牛牛商店当前售卖的物品如下"))) for id := 1; id <= len(propMap); id++ { product := propMap[id] - productInfo := fmt.Sprintf("商品%d\n商品名: %s\n商品价格: %dATRI币\n商品作用域: %s\n商品描述: %s\n使用次数:%d", - id, product.name, product.cost, product.scope, product.description, product.count) + productInfo := fmt.Sprintf("商品%d\n商品名: %s\n商品价格: %dATRI币\n商品作用域: %s\n商品描述: %s", + id, product.name, product.cost, product.scope, product.description) messages = append(messages, ctxext.FakeSenderForwardNode(ctx, message.Text(productInfo))) } if id := ctx.Send(messages).ID(); id == 0 { ctx.Send(message.Text("发送商店失败")) return } - - ctx.SendChain(message.Text("输入对应序号进行购买商品")) - recv, cancel := zero.NewFutureEvent("message", 999, false, zero.CheckUser(uid), zero.CheckGroup(gid), zero.RegexRule(`^(\d+)$`)).Repeat() + recv, cancel := zero.NewFutureEvent("message", 999, false, zero.CheckUser(uid), zero.CheckGroup(gid), zero.RegexRule(`^(\d+)-(\d+)$`)).Repeat() defer cancel() timer := time.NewTimer(120 * time.Second) answer := "" @@ -174,13 +178,13 @@ func init() { return case r := <-recv: answer = r.Event.Message.String() - n, err := strconv.Atoi(answer) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - if err = niu.Store(gid, uid, n); err != nil { + // 解析输入的商品ID和数量 + parts := strings.Split(answer, "-") + productID, _ := strconv.Atoi(parts[0]) + quantity, _ := strconv.Atoi(parts[1]) + + if err := niu.Store(gid, uid, productID, quantity); err != nil { ctx.SendChain(message.Text("ERROR: ", err)) return } @@ -227,7 +231,7 @@ func init() { return } - if err := niu.Redeem(gid, uid, last.Length); err != nil { + if err := niu.Redeem(gid, uid, *last); err != nil { ctx.SendChain(message.Text("ERROR:", err)) return } @@ -337,7 +341,7 @@ func init() { } uid := ctx.Event.UserID gid := ctx.Event.GroupID - msg, length, err := niu.JJ(gid, uid, adduser, patternParsed[0].Text()[1]) + msg, length, niuID, err := niu.JJ(gid, uid, adduser, patternParsed[0].Text()[1]) if err != nil { ctx.SendChain(message.Text("ERROR: ", err)) jjLimiter.Delete(fmt.Sprintf("%d_%d", ctx.Event.GroupID, ctx.Event.UserID)) @@ -346,24 +350,27 @@ func init() { ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(msg)) j := fmt.Sprintf("%d_%d", gid, adduser) count, ok := jjCount.Load(j) - var c lastLength + var c niu.PKRecord // 按照最后一次被 jj 时的时间计算,超过60分钟则重置 if !ok { // 第一次被 jj - c = lastLength{ + c = niu.PKRecord{ + NiuID: niuID, TimeLimit: time.Now(), Count: 1, Length: length, } } else { - c = lastLength{ + c = niu.PKRecord{ + NiuID: niuID, TimeLimit: time.Now(), Count: count.Count + 1, Length: count.Length, } // 超时了,重置 if time.Since(c.TimeLimit) > time.Hour { - c = lastLength{ + c = niu.PKRecord{ + NiuID: niuID, TimeLimit: time.Now(), Count: 1, Length: length, @@ -397,7 +404,7 @@ func init() { data, ok := register.Load(key) switch { case !ok || time.Since(data.TimeLimit) > time.Hour*24: - data = &lastLength{ + data = &niu.PKRecord{ TimeLimit: time.Now(), Count: 1, } From e84e44476abbece654189e5474bef3df3b1d9526 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:39:15 +0800 Subject: [PATCH 42/51] Changes by create-pull-request action (#1198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: bump deps * Change Go version in go.mod --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com> --- gomod2nix.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index fbccd6a82e..5983ecacd7 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -77,8 +77,8 @@ schema = 3 version = "v1.3.0" hash = "sha256-/sN7X8dKXQgv8J+EDzVUB+o+AY9gBC8e1C6sYhaTy1k=" [mod."github.com/fumiama/deepinfra"] - version = "v0.0.0-20250812083039-f1b27f21d8c9" - hash = "sha256-P2WdChVhLEjZFV2B4O7j2lNyI6NpfuzVyjvwjbj5NPI=" + version = "v0.0.0-20250910022828-8cde75e137f4" + hash = "sha256-1CV8t3R91maqJztHg7whECqvS4+sxWcSvq+EyO4PyZ8=" [mod."github.com/fumiama/go-base16384"] version = "v1.7.0" hash = "sha256-vTAsBBYe2ISzb2Nba5E96unodZSkhMcqo6hbwR01nz8=" From fc9a21d2d1dff5f290ce5cf39e593ecedaaf346f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:39:51 +0800 Subject: [PATCH 43/51] Changes by create-pull-request action (#1199) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: bump deps * Change Go version in go.mod Updated Go version from 1.23.0 to 1.20 and removed toolchain specification. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: 源文雨 <41315874+fumiama@users.noreply.github.com> --- gomod2nix.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gomod2nix.toml b/gomod2nix.toml index 5983ecacd7..12c63f8e72 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -5,8 +5,8 @@ schema = 3 version = "v1.1.1" hash = "sha256-hKshA0K92bKuK92mmtM0osVmqLJcSbeobeWSDpQoRCo=" [mod."github.com/FloatTech/AnimeAPI"] - version = "v1.7.1-0.20250717123723-d300df538b46" - hash = "sha256-ZauOGdJb6XcAVsXvZ8B76+1daRm1s84vXoKwmvS0rn4=" + version = "v1.7.1-0.20250901143505-180d33844860" + hash = "sha256-k1MlgaBGwpaqoVk+8WYfXoVLfzqyEQW5LQaJgBKlhUA=" [mod."github.com/FloatTech/floatbox"] version = "v0.0.0-20250513111443-adba80e84e80" hash = "sha256-Zt9zkUa3qqldrSttAq66YLPZPxrnkOR2MaU7oapIWEE=" From 6b505d050af63d9610817343d2cc7e18c1d0a7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:41:07 +0800 Subject: [PATCH 44/51] =?UTF-8?q?=F0=9F=94=96=20v1.9.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kanban/banner/banner.go | 4 ++-- kanban/gen/banner.go | 2 +- winres/winres.json | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/kanban/banner/banner.go b/kanban/banner/banner.go index d224514b53..cf6bd49f1c 100644 --- a/kanban/banner/banner.go +++ b/kanban/banner/banner.go @@ -3,13 +3,13 @@ package banner // Version ... -var Version = "v1.9.8" +var Version = "v1.9.9" // Copyright ... var Copyright = "© 2020 - 2025 FloatTech" // Banner ... var Banner = "* OneBot + ZeroBot + Golang\n" + - "* Version " + Version + " - 2025-06-01 18:52:48 +0900 JST\n" + + "* Version " + Version + " - 2025-09-10 10:40:39 +0800 CST\n" + "* Copyright " + Copyright + ". All Rights Reserved.\n" + "* Project: https://github.com/FloatTech/ZeroBot-Plugin" diff --git a/kanban/gen/banner.go b/kanban/gen/banner.go index a39a1c343c..3af4a9984c 100644 --- a/kanban/gen/banner.go +++ b/kanban/gen/banner.go @@ -27,7 +27,7 @@ var Banner = "* OneBot + ZeroBot + Golang\n" + "* Project: https://github.com/FloatTech/ZeroBot-Plugin" ` -const timeformat = `2006-01-02 15:04:05 +0900 JST` +const timeformat = `2006-01-02 15:04:05 +0800 CST` func main() { f, err := os.Create("banner/banner.go") diff --git a/winres/winres.json b/winres/winres.json index a29dc16b44..329dbb5479 100644 --- a/winres/winres.json +++ b/winres/winres.json @@ -12,7 +12,7 @@ "0409": { "identity": { "name": "ZeroBot-Plugin", - "version": "1.9.8.2223" + "version": "1.9.9.2250" }, "description": "", "minimum-os": "vista", @@ -36,23 +36,23 @@ "#1": { "0000": { "fixed": { - "file_version": "1.9.8.2223", - "product_version": "v1.9.8", - "timestamp": "2025-06-01T18:52:57+08:00" + "file_version": "1.9.9.2250", + "product_version": "v1.9.9", + "timestamp": "2025-09-10T10:40:54+08:00" }, "info": { "0409": { "Comments": "OneBot plugins based on ZeroBot", "CompanyName": "FloatTech", "FileDescription": "https://github.com/FloatTech/ZeroBot-Plugin", - "FileVersion": "1.9.8.2223", + "FileVersion": "1.9.9.2250", "InternalName": "", "LegalCopyright": "© 2020 - 2025 FloatTech. All Rights Reserved.", "LegalTrademarks": "", "OriginalFilename": "ZBP.EXE", "PrivateBuild": "", "ProductName": "ZeroBot-Plugin", - "ProductVersion": "v1.9.8", + "ProductVersion": "v1.9.9", "SpecialBuild": "" } } From e6a4dfcdf26aa8a7741161b8bd1dca19c8584364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Wed, 10 Sep 2025 22:51:44 +0800 Subject: [PATCH 45/51] =?UTF-8?q?fix(aichat):=20adapt=20to=20=E7=99=BE?= =?UTF-8?q?=E7=82=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 4 ++-- plugin/aichat/main.go | 8 ++------ 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 0a5fe897cc..bb0498903e 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/fumiama/ahsai v0.1.0 github.com/fumiama/cron v1.3.0 - github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4 + github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d github.com/fumiama/go-base16384 v1.7.0 github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 diff --git a/go.sum b/go.sum index 54224894f0..9a54a9651a 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/fumiama/ahsai v0.1.0 h1:LXD61Kaj6kJHa3AEGsLIfKNzcgaVxg7JB72OR4yNNZ4= github.com/fumiama/ahsai v0.1.0/go.mod h1:fFeNnqgo44i8FIaguK659aQryuZeFy+4klYLQu/rfdk= github.com/fumiama/cron v1.3.0 h1:ZWlwuexF+HQHl3cYytEE5HNwD99q+3vNZF1GrEiXCFo= github.com/fumiama/cron v1.3.0/go.mod h1:bz5Izvgi/xEUI8tlBN8BI2jr9Moo8N4or0KV8xXuPDY= -github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4 h1:cV3HXXLNudIL9rIEYt1RCgl6H4703nE3+jL4pJNsRtc= -github.com/fumiama/deepinfra v0.0.0-20250910022828-8cde75e137f4/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= +github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d h1:iGxnST620IHrJ47DXkjzrZJ2rskBogWze+UyvnAxT6g= +github.com/fumiama/deepinfra v0.0.0-20250910144855-27a4e697106d/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= github.com/fumiama/go-base16384 v1.7.0 h1:6fep7XPQWxRlh4Hu+KsdH+6+YdUp+w6CwRXtMWSsXCA= github.com/fumiama/go-base16384 v1.7.0/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs= diff --git a/plugin/aichat/main.go b/plugin/aichat/main.go index 50c875a9de..fc78af58c6 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -126,9 +126,7 @@ func init() { mod = model.NewOpenAI( cfg.ModelName, cfg.Separator, temperature, topp, maxn, - ).SetExtra(&map[string]bool{ - "enable_thinking": false, - }) + ) case 1: mod = model.NewOLLaMA( cfg.ModelName, cfg.Separator, @@ -518,9 +516,7 @@ func llmchat(prompt string, temp int64) (string, error) { mod = model.NewOpenAI( cfg.ModelName, cfg.Separator, temperature, topp, maxn, - ).SetExtra(&map[string]bool{ - "enable_thinking": false, - }) + ) case 1: mod = model.NewOLLaMA( cfg.ModelName, cfg.Separator, From e020bf0d0f817e78345773b02b3c04299d66c3e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=94=85=E9=A5=AD?= <1156544355@qq.com> Date: Sun, 14 Sep 2025 23:27:48 +0800 Subject: [PATCH 46/51] =?UTF-8?q?=E2=9C=A8=20=E6=B7=BB=E5=8A=A0rsshub?= =?UTF-8?q?=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++ go.mod | 7 + go.sum | 20 ++ main.go | 1 + plugin/rsshub/domain/job.go | 135 ++++++++++++++ plugin/rsshub/domain/model.go | 123 ++++++++++++ plugin/rsshub/domain/rawFeed.go | 110 +++++++++++ plugin/rsshub/domain/rssHub.go | 192 +++++++++++++++++++ plugin/rsshub/domain/rssHub_test.go | 105 +++++++++++ plugin/rsshub/domain/storageImpl.go | 47 +++++ plugin/rsshub/domain/storageRepo.go | 280 ++++++++++++++++++++++++++++ plugin/rsshub/main.go | 163 ++++++++++++++++ plugin/rsshub/view.go | 100 ++++++++++ 13 files changed, 1294 insertions(+) create mode 100644 plugin/rsshub/domain/job.go create mode 100644 plugin/rsshub/domain/model.go create mode 100644 plugin/rsshub/domain/rawFeed.go create mode 100644 plugin/rsshub/domain/rssHub.go create mode 100644 plugin/rsshub/domain/rssHub_test.go create mode 100644 plugin/rsshub/domain/storageImpl.go create mode 100644 plugin/rsshub/domain/storageRepo.go create mode 100644 plugin/rsshub/main.go create mode 100644 plugin/rsshub/view.go diff --git a/README.md b/README.md index e923a77fdc..2e2faadb0b 100644 --- a/README.md +++ b/README.md @@ -1284,6 +1284,17 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 打劫[对方Q号|@对方QQ] +
+
+ RSSHub + +`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub"` + +- [x] 添加rsshub订阅-/bookfere/weekly +- [x] 删除rsshub订阅-/bookfere/weekly +- [x] 查看rsshub订阅列表 +- [x] rsshub同步 (使用job执行定时任务------记录在"@every 10m"触发的指令) +
在线代码运行 diff --git a/go.mod b/go.mod index bb0498903e..d73abcea98 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 github.com/lithammer/fuzzysearch v1.1.8 github.com/liuzl/gocc v0.0.0-20231231122217-0372e1059ca5 + github.com/mmcdole/gofeed v1.3.0 github.com/mroth/weightedrand v1.0.0 github.com/notnil/chess v1.9.0 github.com/pkg/errors v0.9.1 @@ -54,8 +55,10 @@ require ( ) require ( + github.com/PuerkitoBio/goquery v1.8.0 // indirect github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect github.com/antchfx/xpath v1.3.3 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/ericpauley/go-quantize v0.0.0-20200331213906-ae555eb2afa4 // indirect @@ -70,10 +73,14 @@ require ( github.com/jfreymuth/oggvorbis v1.0.1 // indirect github.com/jfreymuth/vorbis v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/liuzl/cedar-go v0.0.0-20170805034717-80a9c64b256d // indirect github.com/liuzl/da v0.0.0-20180704015230-14771aad5b1d // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect diff --git a/go.sum b/go.sum index 9a54a9651a..d337514073 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/FloatTech/zbpctrl v1.7.0/go.mod h1:xmM4dSwHA02Gei3ogCRiG+RTrw/7Z69Pfr github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f h1:5jnrFe9FTydb/pcUhxkWHuQVCwmYIZmneOkvmgHOwGI= github.com/FloatTech/zbputils v1.7.2-0.20250812085410-2741050f465f/go.mod h1:HG/yZwExV3b1Vqu4chbqwhfX4hx7gDS07QO436JkwIg= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU= github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w= github.com/RomiChan/websocket v1.4.3-0.20220227141055-9b2c6168c9c5 h1:bBmmB7he0iVN4m5mcehfheeRUEer/Avo4ujnxI3uCqs= @@ -31,6 +33,8 @@ github.com/adamzy/cedar-go v0.0.0-20170805034717-80a9c64b256d/go.mod h1:PRWNwWq0 github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA= github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/antchfx/htmlquery v1.3.4 h1:Isd0srPkni2iNTWCwVj/72t7uCphFeor5Q8nCzj1jdQ= github.com/antchfx/htmlquery v1.3.4/go.mod h1:K9os0BwIEmLAvTqaNSua8tXLWRWZpocZIH73OzWQbwM= github.com/antchfx/xpath v1.3.3 h1:tmuPQa1Uye0Ym1Zn65vxPgfltWb/Lxu2jeqIGteJSRs= @@ -108,6 +112,7 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4er github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hajimehoshi/go-mp3 v0.3.0/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= @@ -128,6 +133,8 @@ github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jozsefsallai/gophersauce v1.0.1 h1:BA3ovtQRrAb1qYU9JoRLbDHpxnDunlNcEkEfhCvDDCM= github.com/jozsefsallai/gophersauce v1.0.1/go.mod h1:YVEI7djliMTmZ1Vh01YPF8bUHi+oKhe3yXgKf1T49vg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5 h1:BXnB1Gz4y/zwQh+ZFNy7rgd+ZfMOrwRr4uZSHEI+ieY= github.com/kanrichan/resvg-go v0.0.2-0.20231001163256-63db194ca9f5/go.mod h1:c9+VS9GaommgIOzNWb5ze4lYwfT8BZ2UDyGiuQTT7yc= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= @@ -150,6 +157,15 @@ github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/ github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mewkiz/flac v1.0.7/go.mod h1:yU74UH277dBUpqxPouHSQIar3G1X/QIclVbFahSd1pU= github.com/mewkiz/pkg v0.0.0-20190919212034-518ade7978e2/go.mod h1:3E2FUC/qYUfM8+r9zAwpeHJzqRVVMIYnpzD/clwWxyA= +github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4= +github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mroth/weightedrand v1.0.0 h1:V8JeHChvl2MP1sAoXq4brElOcza+jxLkRuwvtQu8L3E= github.com/mroth/weightedrand v1.0.0/go.mod h1:3p2SIcC8al1YMzGhAIoXD+r9olo/g/cdJgAD905gyNE= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= @@ -179,6 +195,7 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -240,6 +257,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -265,6 +283,7 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -290,6 +309,7 @@ golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= diff --git a/main.go b/main.go index 4de143cfcc..01b5511e27 100644 --- a/main.go +++ b/main.go @@ -132,6 +132,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/realcugan" // realcugan清晰术 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/reborn" // 投胎 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/robbery" // 打劫群友的ATRI币 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub" // RSSHub订阅姬 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/runcode" // 在线运行代码 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/saucenao" // 以图搜图 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/score" // 分数 diff --git a/plugin/rsshub/domain/job.go b/plugin/rsshub/domain/job.go new file mode 100644 index 0000000000..b6e4fb642c --- /dev/null +++ b/plugin/rsshub/domain/job.go @@ -0,0 +1,135 @@ +// Package domain rsshub领域逻辑 +package domain + +import ( + "context" + + "github.com/mmcdole/gofeed" + "github.com/sirupsen/logrus" +) + +// syncRss 同步所有频道 +// 返回:更新的频道&订阅信息 map[int64]*RssClientView +// 1. 获取所有频道 +// 2. 遍历所有频道,检查频道是否更新 +// 3. 如果更新,获取更新的内容,但是返回的数据 +func (repo *rssDomain) syncRss(ctx context.Context) (updated map[int64]*RssClientView, err error) { + updated = make(map[int64]*RssClientView) + // 获取所有频道 + sources, err := repo.storage.GetSources(ctx) + if err != nil { + return + } + // 遍历所有源,获取每个channel对应的rss内容 + rssView := make([]*RssClientView, len(sources)) + for i, channel := range sources { + var feed *gofeed.Feed + // 从site获取rss内容 + feed, err = repo.rssHubClient.FetchFeed(channel.RssHubFeedPath) + // 如果获取失败,则跳过 + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub syncRss] fetch path(%+v) error: %v", channel.RssHubFeedPath, err) + continue + } + rv := convertFeedToRssView(0, channel.RssHubFeedPath, feed) + rssView[i] = rv + } + // 检查频道是否更新 + for _, cv := range rssView { + if cv == nil { + continue + } + var needUpdate bool + needUpdate, err = repo.checkSourceNeedUpdate(ctx, cv.Source) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub syncRss] checkSourceNeedUpdate error: %v", err) + err = nil + continue + } + // 保存 + logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %+v, need update(real): %v", cv.Source, needUpdate) + // 如果需要更新,更新channel 和 content + if needUpdate { + err = repo.storage.UpsertSource(ctx, cv.Source) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert source error: %v", err) + err = nil + // continue + } + } + var updateChannelView = &RssClientView{Source: cv.Source, Contents: []*RssContent{}} + err = repo.processContentsUpdate(ctx, cv, err, updateChannelView) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub syncRss] processContentsUpdate error: %v", err) + continue + } + if len(updateChannelView.Contents) == 0 { + logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %s, no new content", cv.Source.RssHubFeedPath) + continue + } + updateChannelView.Sort() + updated[updateChannelView.Source.ID] = updateChannelView + logrus.WithContext(ctx).Debugf("[rsshub syncRss] cv %s, new contents: %v", cv.Source.RssHubFeedPath, len(updateChannelView.Contents)) + } + return +} + +// checkSourceNeedUpdate 检查频道是否需要更新 +func (repo *rssDomain) checkSourceNeedUpdate(ctx context.Context, source *RssSource) (needUpdate bool, err error) { + var sourceInDB *RssSource + sourceInDB, err = repo.storage.GetSourceByRssHubFeedLink(ctx, source.RssHubFeedPath) + if err != nil { + return + } + if sourceInDB == nil { + logrus.WithContext(ctx).Errorf("[rsshub syncRss] source not found: %v", source.RssHubFeedPath) + return + } + source.ID = sourceInDB.ID + // 检查是否需要更新到db + if sourceInDB.IfNeedUpdate(source) { + needUpdate = true + } + return +} + +// processContentsUpdate 处理内容(s)更新 +func (repo *rssDomain) processContentsUpdate(ctx context.Context, cv *RssClientView, err error, updateChannelView *RssClientView) error { + for _, content := range cv.Contents { + if content == nil { + continue + } + content.RssSourceID = cv.Source.ID + var existed bool + existed, err = repo.processContentItemUpdate(ctx, content) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err) + err = nil + continue + } + if !existed { + updateChannelView.Contents = append(updateChannelView.Contents, content) + logrus.WithContext(ctx).Infof("[rsshub syncRss] cv %s, add new content: %v", cv.Source.RssHubFeedPath, content.Title) + } + } + return err +} + +// processContentItemUpdate 处理单个内容更新 +func (repo *rssDomain) processContentItemUpdate(ctx context.Context, content *RssContent) (existed bool, err error) { + existed, err = repo.storage.IsContentHashIDExist(ctx, content.HashID) + if err != nil { + return + } + // 不需要更新&不需要发送 + if existed { + return + } + // 保存 + err = repo.storage.UpsertContent(ctx, content) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert content error: %v", err) + return + } + return +} diff --git a/plugin/rsshub/domain/model.go b/plugin/rsshub/domain/model.go new file mode 100644 index 0000000000..64c9d33400 --- /dev/null +++ b/plugin/rsshub/domain/model.go @@ -0,0 +1,123 @@ +package domain + +import ( + "encoding/hex" + "hash/fnv" + "sort" + "time" +) + +// ======== RSS ========[START] + +// type SingleFeedItem gofeed.Item + +func genHashForFeedItem(link, guid string) string { + idString := link + "||" + guid + h := fnv.New32() + _, _ = h.Write([]byte(idString)) + encoded := hex.EncodeToString(h.Sum(nil)) + return encoded +} + +// RssClientView 频道视图 +type RssClientView struct { + Source *RssSource + Contents []*RssContent +} + +// ======== RSS ========[END] + +// ======== DB ========[START] + +const ( + tableNameRssSource = "rss_source" + tableNameRssContent = "rss_content" + tableNameRssSubscribe = "rss_subscribe" +) + +// RssSource RSS频道 +type RssSource struct { + // Id 自增id + ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"` + // RssHubFeedPath 频道路由 用于区分rss_hub 不同的频道 例如: `/bangumi/tv/calendar/today` + RssHubFeedPath string `gorm:"column:rss_hub_feed_path;not null;unique;" json:"rss_hub_feed_path"` + // Title 频道标题 + Title string `gorm:"column:title" json:"title"` + // ChannelDesc 频道描述 + ChannelDesc string `gorm:"column:channel_desc" json:"channel_desc"` + // ImageURL 频道图片 + ImageURL string `gorm:"column:image_url" json:"image_url"` + // Link 频道链接 + Link string `gorm:"column:link" json:"link"` + // UpdatedParsed RSS页面更新时间 + UpdatedParsed time.Time `gorm:"column:updated_parsed" json:"updated_parsed"` + //// Ctime create time + // Ctime int64 `gorm:"column:ctime;default:current_timestamp" json:"ctime"` + // Mtime update time + Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"` +} + +// TableName ... +func (RssSource) TableName() string { + return tableNameRssSource +} + +// IfNeedUpdate ... +func (r RssSource) IfNeedUpdate(cmp *RssSource) bool { + if r.Link != cmp.Link { + return false + } + return r.UpdatedParsed.Unix() < cmp.UpdatedParsed.Unix() +} + +// RssContent 订阅的RSS频道的推送信息 +type RssContent struct { + // Id 自增id + ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"` + HashID string `gorm:"column:hash_id;unique" json:"hash_id"` + RssSourceID int64 `gorm:"column:rss_source_id;not null" json:"rss_source_id"` + Title string `gorm:"column:title" json:"title"` + Description string `gorm:"column:description" json:"description"` + Link string `gorm:"column:link" json:"link"` + Date time.Time `gorm:"column:date" json:"date"` + Author string `gorm:"column:author" json:"author"` + Thumbnail string `gorm:"column:thumbnail" json:"thumbnail"` + Content string `gorm:"column:content" json:"content"` + //// Ctime create time + // Ctime int64 `gorm:"column:ctime;default:current_timestamp" json:"ctime"` + // Mtime update time + Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"` +} + +// TableName ... +func (RssContent) TableName() string { + return tableNameRssContent +} + +// Sort ... order by Date desc +func (r *RssClientView) Sort() { + sort.Slice(r.Contents, func(i, j int) bool { + return r.Contents[i].Date.Unix() > r.Contents[j].Date.Unix() + }) +} + +// RssSubscribe 订阅关系表:群组-RSS频道 +type RssSubscribe struct { + // Id 自增id + ID int64 `gorm:"column:id;primary_key;AUTO_INCREMENT"` + // 订阅群组 + GroupID int64 `gorm:"column:group_id;not null;uniqueIndex:uk_sid_gid"` + // 订阅频道 + RssSourceID int64 `gorm:"column:rss_source_id;not null;uniqueIndex:uk_sid_gid"` + //// Ctime create time + // Ctime int64 `gorm:"column:ctime;default:current_timestamp" json:"ctime"` + // Mtime update time + Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"` +} + +// TableName ... +func (RssSubscribe) TableName() string { + return tableNameRssSubscribe +} + +// ======== DB ========[END] diff --git a/plugin/rsshub/domain/rawFeed.go b/plugin/rsshub/domain/rawFeed.go new file mode 100644 index 0000000000..a8f6ee5d66 --- /dev/null +++ b/plugin/rsshub/domain/rawFeed.go @@ -0,0 +1,110 @@ +package domain + +import ( + "bytes" + "encoding/json" + "errors" + "net/http" + "time" + + "github.com/FloatTech/floatbox/web" + "github.com/mmcdole/gofeed" + "github.com/sirupsen/logrus" +) + +// const ( +// acceptHeader = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" +// userHeader = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Edg/84.0.522.63" +//) + +var ( + // RSSHubMirrors RSSHub镜像站地址列表,第一个为默认地址 + rssHubMirrors = []string{ + "https://rsshub.rssforever.com", + "https://rss.injahow.cn", + } +) + +// RssHubClient rss hub client (http) +type RssHubClient struct { + *http.Client +} + +// FetchFeed 获取rss feed信息 +func (c *RssHubClient) FetchFeed(path string) (feed *gofeed.Feed, err error) { + var data []byte + // 遍历 rssHubMirrors,直到获取成功 + for _, mirror := range rssHubMirrors { + data, err = web.RequestDataWith(c.Client, mirror+path, "GET", "", web.RandUA(), nil) + if err == nil && len(data) > 0 { + break + } + } + if err != nil { + logrus.Errorf("[rsshub FetchFeed] fetch feed error: %v", err) + return nil, err + } + if len(data) == 0 { + logrus.Errorf("[rsshub FetchFeed] fetch feed error: data is empty") + return nil, errors.New("feed data is empty") + } + // data, err = web.RequestDataWith(c.Client, domain+path, "GET", "", web.RandUA(), nil) + // if err != nil { + // return nil, err + //} + feed, err = gofeed.NewParser().Parse(bytes.NewBuffer(data)) + if err != nil { + return + } + return +} + +func convertFeedToRssView(channelID int64, cPath string, feed *gofeed.Feed) (view *RssClientView) { + var imgURL string + if feed.Image != nil { + imgURL = feed.Image.URL + } + view = &RssClientView{ + Source: &RssSource{ + ID: channelID, + RssHubFeedPath: cPath, + Title: feed.Title, + ChannelDesc: feed.Description, + ImageURL: imgURL, + Link: feed.Link, + UpdatedParsed: *(feed.UpdatedParsed), + Mtime: time.Now(), + }, + // 不用定长,后面可能会过滤一些元素再append + Contents: []*RssContent{}, + } + // convert feed items to rss content + for _, item := range feed.Items { + if item.Link == "" || item.Title == "" { + continue + } + var thumbnail string + if item.Image != nil { + thumbnail = item.Image.URL + } + var publishedParsed = item.PublishedParsed + if publishedParsed == nil { + publishedParsed = &time.Time{} + } + aus, _ := json.Marshal(item.Authors) + view.Contents = append(view.Contents, &RssContent{ + ID: 0, + HashID: genHashForFeedItem(item.Link, item.GUID), + RssSourceID: channelID, + Title: item.Title, + Description: item.Description, + Link: item.Link, + Date: *publishedParsed, + Author: string(aus), + Thumbnail: thumbnail, + Content: item.Content, + Mtime: time.Now(), + }) + } + return +} diff --git a/plugin/rsshub/domain/rssHub.go b/plugin/rsshub/domain/rssHub.go new file mode 100644 index 0000000000..1f652032ad --- /dev/null +++ b/plugin/rsshub/domain/rssHub.go @@ -0,0 +1,192 @@ +package domain + +import ( + "context" + "errors" + "net/http" + "os" + "time" + + "github.com/jinzhu/gorm" + "github.com/sirupsen/logrus" +) + +// RssDomain RssRepo定义 +type RssDomain interface { + // Subscribe 订阅Rss频道 + Subscribe(ctx context.Context, gid int64, route string) (rv *RssClientView, isChannelExisted, + isSubExisted bool, err error) + // Unsubscribe 取消订阅Rss频道 + Unsubscribe(ctx context.Context, gid int64, route string) (err error) + // GetSubscribedChannelsByGroupID 获取群组订阅的Rss频道 + GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) (rv []*RssClientView, err error) + // Sync 同步Rss频道 + // 返回群组-频道推送视图 map[群组]推送内容数组 + Sync(ctx context.Context) (groupView map[int64][]*RssClientView, err error) +} + +// rssDomain RssRepo定义 +type rssDomain struct { + storage RepoStorage + rssHubClient *RssHubClient +} + +// NewRssDomain 新建RssDomain,调用方保证单例模式 +func NewRssDomain(dbPath string) (RssDomain, error) { + return newRssDomain(dbPath) +} + +func newRssDomain(dbPath string) (*rssDomain, error) { + if _, err := os.Stat(dbPath); err != nil || os.IsNotExist(err) { + // 生成文件 + f, err := os.Create(dbPath) + if err != nil { + return nil, err + } + defer f.Close() + } + orm, err := gorm.Open("sqlite3", dbPath) + if err != nil { + logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err) + panic(err) + } + repo := &rssDomain{ + storage: &repoStorage{orm: orm}, + rssHubClient: &RssHubClient{Client: http.DefaultClient}, + } + err = repo.storage.initDB() + if err != nil { + logrus.Errorf("[rsshub NewRssDomain] open db error: %v", err) + panic(err) + } + return repo, nil +} + +// Subscribe QQ群订阅Rss频道 +func (repo *rssDomain) Subscribe(ctx context.Context, gid int64, feedPath string) ( + rv *RssClientView, isChannelExisted, isSubExisted bool, err error) { + // 验证 + feed, err := repo.rssHubClient.FetchFeed(feedPath) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] add source error: %v", err) + return + } + logrus.WithContext(ctx).Infof("[rsshub Subscribe] try get source success: %v", len(feed.Title)) + // 新建source结构体 + rv = convertFeedToRssView(0, feedPath, feed) + feedChannel, err := repo.storage.GetSourceByRssHubFeedLink(ctx, feedPath) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query source by feedPath error: %v", err) + return + } + // 如果已经存在 + if feedChannel != nil { + logrus.WithContext(ctx).Warningf("[rsshub Subscribe] source existed: %v", feedChannel) + isChannelExisted = true + } else { + // 不存在的情况,要把更新时间置空,保证下一次同步时能够更新 + rv.Source.UpdatedParsed = time.Time{} + } + // 保存 + err = repo.storage.UpsertSource(ctx, rv.Source) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] save source error: %v", err) + return + } + logrus.Infof("[rsshub Subscribe] save/update source success %v", rv.Source.ID) + // 添加群号到订阅 + subscribe, err := repo.storage.GetSubscribeByID(ctx, gid, rv.Source.ID) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query subscribe error: %v", err) + return + } + logrus.WithContext(ctx).Infof("[rsshub Subscribe] query subscribe success: %v", subscribe) + // 如果已经存在,直接返回 + if subscribe != nil { + isSubExisted = true + logrus.WithContext(ctx).Infof("[rsshub Subscribe] subscribe existed: %v", subscribe) + return + } + // 如果不存在,保存 + err = repo.storage.CreateSubscribe(ctx, gid, rv.Source.ID) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] save subscribe error: %v", err) + return + } + logrus.WithContext(ctx).Infof("[rsshub Subscribe] success: %v", len(rv.Contents)) + return +} + +// Unsubscribe 群组取消订阅 +func (repo *rssDomain) Unsubscribe(ctx context.Context, gid int64, feedPath string) (err error) { + existedSubscribes, ifExisted, err := repo.storage.GetIfExistedSubscribe(ctx, gid, feedPath) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query sub by route error: %v", err) + return errors.New("数据库错误") + } + logrus.WithContext(ctx).Infof("[rsshub Subscribe] query source by route success: %v", existedSubscribes) + // 如果不存在订阅关系,直接返回 + if !ifExisted || existedSubscribes == nil { + logrus.WithContext(ctx).Infof("[rsshub Subscribe] source existed: %v", ifExisted) + return errors.New("频道不存在") + } + err = repo.storage.DeleteSubscribe(ctx, existedSubscribes.ID) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] delete source error: %v", err) + return errors.New("删除失败") + } + // 查询是否还有群订阅这个频道 + subscribesNeedsToDel, err := repo.storage.GetSubscribesBySource(ctx, feedPath) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] query source by route error: %v", err) + return + } + // 没有群订阅的时候,把频道删除 + if len(subscribesNeedsToDel) == 0 { + err = repo.storage.DeleteSource(ctx, existedSubscribes.RssSourceID) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Subscribe] delete source error: %v", err) + return errors.New("清除频道信息失败") + } + } + return +} + +// GetSubscribedChannelsByGroupID 获取群对应的订阅的频道信息 +func (repo *rssDomain) GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) ([]*RssClientView, error) { + channels, err := repo.storage.GetSubscribedChannelsByGroupID(ctx, gid) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub GetSubscribedChannelsByGroupID] GetSubscribedChannelsByGroupID error: %v", err) + return nil, err + } + rv := make([]*RssClientView, len(channels)) + logrus.WithContext(ctx).Infof("[rsshub GetSubscribedChannelsByGroupID] query subscribe success: %v", len(channels)) + for i, cn := range channels { + rv[i] = &RssClientView{ + Source: cn, + } + } + return rv, nil +} + +// Sync 同步任务,按照群组订阅情况做好map切片 +func (repo *rssDomain) Sync(ctx context.Context) (groupView map[int64][]*RssClientView, err error) { + groupView = make(map[int64][]*RssClientView) + // 获取所有Rss频道 + // 获取所有频道 + updatedViews, err := repo.syncRss(ctx) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Sync] sync rss feed error: %v", err) + return + } + logrus.WithContext(ctx).Infof("[rsshub Sync] updated channels: %v", len(updatedViews)) + subscribes, err := repo.storage.GetSubscribes(ctx) + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub Sync] get subscribes error: %v", err) + return + } + for _, subscribe := range subscribes { + groupView[subscribe.GroupID] = append(groupView[subscribe.GroupID], updatedViews[subscribe.RssSourceID]) + } + return +} diff --git a/plugin/rsshub/domain/rssHub_test.go b/plugin/rsshub/domain/rssHub_test.go new file mode 100644 index 0000000000..451795931d --- /dev/null +++ b/plugin/rsshub/domain/rssHub_test.go @@ -0,0 +1,105 @@ +package domain + +import ( + "context" + "encoding/json" + "testing" +) + +func TestNewRssDomain(t *testing.T) { + dm, err := newRssDomain("rsshub.db") + if err != nil { + t.Fatal(err) + return + } + if dm == nil { + t.Fatal("domain is nil") + } +} + +//var testRssHubChannelUrl = "https://rsshub.rssforever.com/bangumi/tv/calendar/today" + +var dm, _ = newRssDomain("rsshub.db") + +func TestSub(t *testing.T) { + testCases := []struct { + name string + feedLink string + gid int64 + }{ + { + name: "test1", + feedLink: "/bangumi/tv/calendar/today", + gid: 99, + }, + { + name: "test2", + feedLink: "/go-weekly", + gid: 99, + }, + { + name: "test3", + feedLink: "/go-weekly", + gid: 123, + }, + { + name: "test3", + feedLink: "/go-weekly", + gid: 321, + }, + { + name: "test3", + feedLink: "/go-weekly", + gid: 4123, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + channel, ifExisted, ifSub, err := dm.Subscribe(ctx, tc.gid, tc.feedLink) + if err != nil { + t.Fatal(err) + return + } + t.Logf("[TEST] add sub res: %+v,%+v,%+v\n", channel, ifExisted, ifSub) + res, ext, err := dm.storage.GetIfExistedSubscribe(ctx, tc.gid, tc.feedLink) + if err != nil { + t.Fatal(err) + return + } + t.Logf("[TEST] if exist: %+v,%+v", res, ext) + channels, err := dm.GetSubscribedChannelsByGroupID(ctx, 2) + if err != nil { + t.Fatal(err) + return + } + t.Logf("[TEST] 2 channels: %+v", channels) + // del + //err = dm.Unsubscribe(ctx, tc.gid, tc.feedLink) + //if err != nil { + // t.Fatal(err) + // return + //} + //res, ext, err = dm.storage.GetIfExistedSubscribe(ctx, tc.gid, tc.feedLink) + //if err != nil { + // t.Fatal(err) + // return + //} + //t.Logf("[TEST] after del: %+v,%+v", res, ext) + //if res != nil || ext { + // t.Fatal("delete failed") + //} + + }) + } +} + +func Test_SyncFeed(t *testing.T) { + feed, err := dm.Sync(context.Background()) + if err != nil { + t.Fatal(err) + return + } + rs, _ := json.Marshal(feed) + t.Logf("[Test] feed: %+v", string(rs)) +} diff --git a/plugin/rsshub/domain/storageImpl.go b/plugin/rsshub/domain/storageImpl.go new file mode 100644 index 0000000000..842411defb --- /dev/null +++ b/plugin/rsshub/domain/storageImpl.go @@ -0,0 +1,47 @@ +package domain + +import "context" + +// RepoContent RSS 推送信息存储接口 +type RepoContent interface { + // UpsertContent 添加一条文章 + UpsertContent(ctx context.Context, content *RssContent) error + // DeleteSourceContents 删除订阅源的所有文章,返回被删除的文章数 + DeleteSourceContents(ctx context.Context, channelID int64) (int64, error) + // IsContentHashIDExist hash id 对应的文章是否已存在 + IsContentHashIDExist(ctx context.Context, hashID string) (bool, error) +} + +// RepoSource RSS 订阅源存储接口 +type RepoSource interface { + // UpsertSource 添加一个订阅源 + UpsertSource(ctx context.Context, rfc *RssSource) error + // GetSources 获取所有订阅源信息 + GetSources(ctx context.Context) ([]RssSource, error) + // GetSourceByRssHubFeedLink 通过 rssHub 的 feed 链接获取订阅源信息 + GetSourceByRssHubFeedLink(ctx context.Context, url string) (*RssSource, error) + // DeleteSource 删除一个订阅源 + DeleteSource(ctx context.Context, fID int64) error +} + +// RepoSubscribe RSS 订阅存储接口 +type RepoSubscribe interface { + // CreateSubscribe 添加一个订阅 + CreateSubscribe(ctx context.Context, gid, rssSourceID int64) error + // DeleteSubscribe 删除一个订阅 + DeleteSubscribe(ctx context.Context, subscribeID int64) error + // GetSubscribeByID 获取一个订阅 + GetSubscribeByID(ctx context.Context, gid int64, subscribeID int64) (*RssSubscribe, error) + // GetSubscribes 获取全部订阅 + GetSubscribes(ctx context.Context) ([]*RssSubscribe, error) +} + +// RepoMultiQuery 多表查询接口 +type RepoMultiQuery interface { + // GetSubscribesBySource 获取一个源对应的所有订阅群组 + GetSubscribesBySource(ctx context.Context, feedPath string) ([]*RssSubscribe, error) + // GetIfExistedSubscribe 判断一个群组是否已订阅了一个源 + GetIfExistedSubscribe(ctx context.Context, gid int64, feedPath string) (*RssSubscribe, bool, error) + // GetSubscribedChannelsByGroupID 获取该群所有的订阅 + GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) ([]*RssSource, error) +} diff --git a/plugin/rsshub/domain/storageRepo.go b/plugin/rsshub/domain/storageRepo.go new file mode 100644 index 0000000000..8698a8d996 --- /dev/null +++ b/plugin/rsshub/domain/storageRepo.go @@ -0,0 +1,280 @@ +package domain + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/jinzhu/gorm" + "github.com/sirupsen/logrus" +) + +// RepoStorage 定义RepoStorage接口 +type RepoStorage interface { + RepoContent + RepoSource + RepoSubscribe + RepoMultiQuery + initDB() error +} + +// repoStorage db struct for rss +type repoStorage struct { + orm *gorm.DB +} + +// initDB ... +func (s *repoStorage) initDB() (err error) { + err = s.orm.AutoMigrate(&RssSource{}, &RssContent{}, &RssSubscribe{}).Error + if err != nil { + logrus.Errorf("[rsshub initDB] error: %v", err) + return err + } + return nil + // s.orm.LogMode(true) +} + +// GetSubscribesBySource Impl +func (s *repoStorage) GetSubscribesBySource(ctx context.Context, feedPath string) ([]*RssSubscribe, error) { + logrus.WithContext(ctx).Infof("[rsshub GetSubscribesBySource] feedPath: %s", feedPath) + rs := make([]*RssSubscribe, 0) + err := s.orm.Model(&RssSubscribe{}).Joins(fmt.Sprintf("%s left join %s on %s.rss_source_id=%s.id", tableNameRssSubscribe, tableNameRssSource, tableNameRssSubscribe, tableNameRssSource)). + Where("rss_source.rss_hub_feed_path = ?", feedPath).Select("rss_subscribe.*").Find(&rs).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + logrus.WithContext(ctx).Errorf("[rsshub GetSubscribesBySource] error: %v", err) + return nil, err + } + return rs, nil +} + +// GetIfExistedSubscribe Impl +func (s *repoStorage) GetIfExistedSubscribe(ctx context.Context, gid int64, feedPath string) (*RssSubscribe, bool, error) { + rs := RssSubscribe{} + + err := s.orm.Table(tableNameRssSubscribe). + Select("rss_subscribe.id, rss_subscribe.group_id, rss_subscribe.rss_source_id, rss_subscribe.mtime"). + Joins(fmt.Sprintf("INNER JOIN %s ON %s.rss_source_id=%s.id", + tableNameRssSource, tableNameRssSubscribe, tableNameRssSource)). + Where("rss_source.rss_hub_feed_path = ? AND rss_subscribe.group_id = ?", feedPath, gid).Scan(&rs).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, false, nil + } + logrus.WithContext(ctx).Errorf("[rsshub GetIfExistedSubscribe] error: %v", err) + return nil, false, err + } + if rs.ID == 0 { + return nil, false, nil + } + return &rs, true, nil +} + +// ==================== RepoSource ==================== [Start] + +// UpsertSource Impl +func (s *repoStorage) UpsertSource(ctx context.Context, source *RssSource) (err error) { + // Update columns to default value on `id` conflict + querySource := &RssSource{RssHubFeedPath: source.RssHubFeedPath} + err = s.orm.First(querySource, "rss_hub_feed_path = ?", querySource.RssHubFeedPath).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = s.orm.Create(source).Omit("id").Error + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub] add source error: %v", err) + return + } + } + return + } + source.ID = querySource.ID + logrus.WithContext(ctx).Infof("[rsshub] update source: %+v", source.UpdatedParsed) + err = s.orm.Model(&source).Where(&RssSource{ID: source.ID}). + Updates(&RssSource{ + Title: source.Title, + ChannelDesc: source.ChannelDesc, + ImageURL: source.ImageURL, + Link: source.Link, + UpdatedParsed: source.UpdatedParsed, + Mtime: time.Now(), + }).Error + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub] update source error: %v", err) + return + } + logrus.Println("[rsshub] add source success: ", source.ID) + return nil +} + +// GetSources Impl +func (s *repoStorage) GetSources(ctx context.Context) (sources []RssSource, err error) { + sources = []RssSource{} + err = s.orm.Find(&sources, "id > 0").Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errors.New("source not found") + } + logrus.WithContext(ctx).Errorf("[rsshub] get sources error: %v", err) + return + } + logrus.WithContext(ctx).Infof("[rsshub] get sources success: %d", len(sources)) + return +} + +// GetSourceByRssHubFeedLink Impl +func (s *repoStorage) GetSourceByRssHubFeedLink(ctx context.Context, rssHubFeedLink string) (source *RssSource, err error) { + source = &RssSource{RssHubFeedPath: rssHubFeedLink} + err = s.orm.Take(source, source).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + logrus.WithContext(ctx).Errorf("[rsshub] get source error: %v", err) + return + } + return +} + +// DeleteSource Impl +func (s *repoStorage) DeleteSource(ctx context.Context, fID int64) (err error) { + err = s.orm.Delete(&RssSource{}, "id = ?", fID).Error + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSource: %v", err) + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("source not found") + } + return + } + return nil +} + +// ==================== RepoSource ==================== [End] + +// ==================== RepoContent ==================== [Start] + +// UpsertContent Impl +func (s *repoStorage) UpsertContent(ctx context.Context, content *RssContent) (err error) { + // check params + if content == nil { + err = errors.New("content is nil") + return + } + // check params.RssHubFeedPath and params.HashID + if content.RssSourceID < 0 || content.HashID == "" || content.Title == "" { + err = errors.New("content.RssSourceID or content.HashID or content.Title is empty") + return + } + err = s.orm.Create(content).Omit("id").Error + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub] storage.UpsertContent: %v", err) + return + } + return +} + +// DeleteSourceContents Impl +func (s *repoStorage) DeleteSourceContents(ctx context.Context, channelID int64) (rows int64, err error) { + err = s.orm.Delete(&RssSubscribe{}).Where(&RssSubscribe{RssSourceID: channelID}).Error + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSourceContents: %v", err) + return + } + return +} + +// IsContentHashIDExist Impl +func (s *repoStorage) IsContentHashIDExist(ctx context.Context, hashID string) (bool, error) { + wanted := &RssContent{HashID: hashID} + err := s.orm.Take(wanted, wanted).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return false, nil + } + logrus.WithContext(ctx).Errorf("[rsshub] storage.IsContentHashIDExist: %v", err) + return false, err + } + return true, nil +} + +// ==================== RepoContent ==================== [End] + +// ==================== RepoSubscribe ==================== [Start] + +// CreateSubscribe Impl +func (s *repoStorage) CreateSubscribe(ctx context.Context, gid, rssSourceID int64) (err error) { + // check subscribe + if rssSourceID < 0 || gid == 0 { + err = errors.New("gid or rssSourceID is empty") + return + } + err = s.orm.Create(&RssSubscribe{GroupID: gid, RssSourceID: rssSourceID}).Omit("id").Error + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub] storage.CreateSubscribe: %v", err) + return + } + return +} + +// DeleteSubscribe Impl +func (s *repoStorage) DeleteSubscribe(ctx context.Context, subscribeID int64) (err error) { + err = s.orm.Delete(&RssSubscribe{}, "id = ?", subscribeID).Error + if err != nil { + logrus.WithContext(ctx).Errorf("[rsshub] storage.DeleteSubscribe error: %v", err) + return + } + return +} + +// GetSubscribeByID Impl +func (s *repoStorage) GetSubscribeByID(ctx context.Context, gid int64, subscribeID int64) (res *RssSubscribe, err error) { + res = &RssSubscribe{} + err = s.orm.First(res, &RssSubscribe{GroupID: gid, RssSourceID: subscribeID}).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribeByID: %v", err) + return nil, err + } + return +} + +// GetSubscribedChannelsByGroupID Impl +func (s *repoStorage) GetSubscribedChannelsByGroupID(ctx context.Context, gid int64) (res []*RssSource, err error) { + res = make([]*RssSource, 0) + err = s.orm.Model(&RssSource{}). + Joins(fmt.Sprintf("join %s on rss_source_id=%s.id", tableNameRssSubscribe, tableNameRssSource)).Where("rss_subscribe.group_id = ?", gid). + Select("rss_source.*"). + Find(&res). + Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = nil + return + } + logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribedChannelsByGroupID: %v", err) + return + } + return +} + +// GetSubscribes Impl +func (s *repoStorage) GetSubscribes(ctx context.Context) (res []*RssSubscribe, err error) { + res = make([]*RssSubscribe, 0) + err = s.orm.Find(&res).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = nil + return + } + logrus.WithContext(ctx).Errorf("[rsshub] storage.GetSubscribes: %v", err) + return + } + return +} + +// ==================== RepoSubscribe ==================== [End] diff --git a/plugin/rsshub/main.go b/plugin/rsshub/main.go new file mode 100644 index 0000000000..26984eae44 --- /dev/null +++ b/plugin/rsshub/main.go @@ -0,0 +1,163 @@ +// Package rsshub rss_hub订阅插件 +package rsshub + +import ( + "context" + "fmt" + "regexp" + + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + zbpCtxExt "github.com/FloatTech/zbputils/ctxext" + "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + + "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub/domain" +) + +// 初始化 repo +var ( + rssRepo domain.RssDomain + initErr error + //// getRssRepo repo 初始化方法,单例 + // getRssRepo = ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { + // logrus.Infoln("RssHub订阅姬:初始化") + // rssRepo, initErr = domain.NewRssDomain(engine.DataFolder() + "rsshub.db") + // if initErr != nil { + // ctx.SendChain(message.Text("RssHub订阅姬:初始化失败", initErr.Error())) + // return false + // } + // return true + // }) + // regexpForSQL 防注入 + regexpForSQL = regexp.MustCompile(`[\^<>\[\]%&\*\(\)\{\}\|\=]|(union\s+select|update\s+|delete\s+|drop\s+|truncate\s+|insert\s+|exec\s+|declare\s+)`) +) + +var ( + // 注册插件 + engine = control.Register("rsshub", &ctrl.Options[*zero.Ctx]{ + // 默认不启动 + DisableOnDefault: false, + Brief: "RssHub订阅姬", + // 详细帮助 + Help: "RssHub订阅姬desu~ \n" + + "支持的详细订阅列表文档可见:\n" + + "https://rsshub.netlify.app/ \n" + + "- 添加rsshub订阅-/bookfere/weekly \n" + + "- 删除rsshub订阅-/bookfere/weekly \n" + + "- 查看rsshub订阅列表 \n" + + "- rsshub同步 \n" + + "Tips: 定时刷新rsshub订阅信息需要配合job一起使用, 全局只需要设置一个, 无视响应状态推送, 下为例子\n" + + "记录在\"@every 10m\"触发的指令)\n" + + "rsshub同步", + // 插件数据存储路径 + PrivateDataFolder: "rsshub", + OnEnable: func(ctx *zero.Ctx) { + ctx.SendChain(message.Text("RssHub订阅姬现在启动了哦")) + }, + OnDisable: func(ctx *zero.Ctx) { + ctx.SendChain(message.Text("RssHub订阅姬现在关闭了哦")) + }, + }).ApplySingle(zbpCtxExt.DefaultSingle) +) + +// init 命令路由 +func init() { + rssRepo, initErr = domain.NewRssDomain(engine.DataFolder() + "rsshub.db") + if initErr != nil { + logrus.Errorln("RssHub订阅姬:初始化失败", initErr) + panic(initErr) + } + engine.OnFullMatch("rsshub同步", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { + // 群组-频道推送视图 map[群组]推送内容数组 + groupToFeedsMap, err := rssRepo.Sync(context.Background()) + if err != nil { + logrus.Errorln("rsshub同步失败", err) + ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text("rsshub同步失败", err)) + return + } + // 没有更新的[群组-频道推送视图]则不推送 + if len(groupToFeedsMap) == 0 { + logrus.Info("rsshub未发现更新") + return + } + sendRssUpdateMsg(ctx, groupToFeedsMap) + }) + // 添加订阅 + engine.OnRegex(`^添加rsshub订阅-(.+)$`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { + routeStr := ctx.State["regex_matched"].([]string)[1] + input := regexpForSQL.ReplaceAllString(routeStr, "") + logrus.Debugf("添加rsshub订阅:raw(%s), replaced(%s)", routeStr, input) + rv, _, isSubExisted, err := rssRepo.Subscribe(context.Background(), ctx.Event.GroupID, input) + if err != nil { + ctx.SendChain(message.Text("RssHub订阅姬:添加失败", err.Error())) + return + } + if isSubExisted { + ctx.SendChain(message.Text("RssHub订阅姬:已存在,更新成功")) + } else { + ctx.SendChain(message.Text("RssHub订阅姬:添加成功\n", rv.Source.Title)) + } + // 添加成功,发送订阅源快照 + msg, err := newRssDetailsMsg(ctx, rv) + if len(msg) == 0 || err != nil { + ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text("RssHub推送错误", err)) + return + } + if id := ctx.Send(msg).ID(); id == 0 { + ctx.SendChain(message.Text("ERROR: 发送订阅源快照失败,可能被风控了")) + } + }) + engine.OnRegex(`^删除rsshub订阅-(.+)$`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { + routeStr := ctx.State["regex_matched"].([]string)[1] + input := regexpForSQL.ReplaceAllString(routeStr, "") + logrus.Debugf("删除rsshub订阅:raw(%s), replaced(%s)", routeStr, input) + err := rssRepo.Unsubscribe(context.Background(), ctx.Event.GroupID, input) + if err != nil { + ctx.SendChain(message.Text("RssHub订阅姬:删除失败 ", err.Error())) + return + } + ctx.SendChain(message.Text(fmt.Sprintf("RssHub订阅姬:删除%s成功", input))) + }) + engine.OnFullMatch("查看rsshub订阅列表", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { + rv, err := rssRepo.GetSubscribedChannelsByGroupID(context.Background(), ctx.Event.GroupID) + if err != nil { + ctx.SendChain(message.Text("RssHub订阅姬:查询失败 ", err.Error())) + return + } + // 添加成功,发送订阅源信息 + msg, err := newRssSourcesMsg(ctx, rv) + if err != nil { + ctx.SendChain(message.Text("RssHub订阅姬:查询失败 ", err.Error())) + return + } + if len(msg) == 0 { + ctx.SendChain(message.Text("ん? 没有订阅的频道哦~")) + return + } + ctx.SendChain(msg...) + }) +} + +// sendRssUpdateMsg 发送Rss更新消息 +func sendRssUpdateMsg(ctx *zero.Ctx, groupToFeedsMap map[int64][]*domain.RssClientView) { + for groupID, views := range groupToFeedsMap { + logrus.Infof("RssHub插件在群 %d 触发推送检查", groupID) + for _, view := range views { + if view == nil || len(view.Contents) == 0 { + continue + } + msg, err := newRssDetailsMsg(ctx, view) + if len(msg) == 0 || err != nil { + ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text(rssHubPushErrMsg, err)) + continue + } + logrus.Infof("RssHub插件在群 %d 开始推送 %s", groupID, view.Source.Title) + ctx.SendGroupMessage(groupID, message.Text(fmt.Sprintf("%s\n该RssHub频道下有更新了哦~", view.Source.Title))) + if res := ctx.SendGroupForwardMessage(groupID, msg); !res.Exists() { + ctx.SendPrivateMessage(zero.BotConfig.SuperUsers[0], message.Text(rssHubPushErrMsg)) + } + } + } +} diff --git a/plugin/rsshub/view.go b/plugin/rsshub/view.go new file mode 100644 index 0000000000..1bc2ae7c9b --- /dev/null +++ b/plugin/rsshub/view.go @@ -0,0 +1,100 @@ +package rsshub + +import ( + "fmt" + "time" + + "github.com/FloatTech/floatbox/binary" + "github.com/FloatTech/zbputils/img/text" + "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + + "github.com/FloatTech/ZeroBot-Plugin/plugin/rsshub/domain" +) + +const ( + rssHubPushErrMsg = "RssHub推送错误" +) + +// formatRssViewToMessagesSlice 格式化RssClientView为消息切片 +func formatRssViewToMessagesSlice(view *domain.RssClientView) ([]message.Message, error) { + // 取前20条 + cts := view.Contents + if len(cts) > 20 { + cts = cts[:20] + } + // 2n+1条消息 + fv := make([]message.Message, len(cts)*2+1) + // 订阅源头图 + toastPic, err := text.RenderToBase64(fmt.Sprintf("%s\n\n\n%s\n\n\n更新时间:%v\n\n\n", + view.Source.Title, view.Source.Link, view.Source.UpdatedParsed.Local().Format(time.DateTime)), + text.SakuraFontFile, 1200, 40) + if err != nil { + return nil, err + } + fv[0] = message.Message{message.Image("base64://" + binary.BytesToString(toastPic))} + // 元素信息 + for idx, item := range cts { + contentStr := fmt.Sprintf("%s\n\n\n", item.Title) + // Date为空时不显示 + if !item.Date.IsZero() { + contentStr += fmt.Sprintf("更新时间:\n%v\n", item.Date.Local().Format(time.DateTime)) + } + var content []byte + content, err = text.RenderToBase64(contentStr, text.SakuraFontFile, 1200, 40) + if err != nil { + logrus.WithError(err).Error("RssHub订阅姬渲染图片失败") + continue + } + itemMessagePic := message.Message{message.Image("base64://" + binary.BytesToString(content))} + fv[2*idx+1] = itemMessagePic + fv[2*idx+2] = message.Message{message.Text(item.Link)} + } + return fv, nil +} + +// newRssSourcesMsg Rss订阅源列表 +func newRssSourcesMsg(ctx *zero.Ctx, view []*domain.RssClientView) (message.Message, error) { + var msgSlice []message.Message + // 生成消息 + for _, v := range view { + if v == nil { + continue + } + item, err := formatRssViewToMessagesSlice(v) + if err != nil { + return nil, err + } + msgSlice = append(msgSlice, item...) + } + // 伪造一个发送者为RssHub订阅姬的消息节点 + msg := make(message.Message, len(msgSlice)) + for i, item := range msgSlice { + msg[i] = fakeSenderForwardNode(ctx.Event.SelfID, item...) + } + return msg, nil +} + +// newRssDetailsMsg Rss订阅源详情(包含文章信息列表) +func newRssDetailsMsg(ctx *zero.Ctx, view *domain.RssClientView) (message.Message, error) { + // 生成消息 + msgSlice, err := formatRssViewToMessagesSlice(view) + if err != nil { + return nil, err + } + // 伪造一个发送者为RssHub订阅姬的消息节点 + msg := make(message.Message, len(msgSlice)) + for i, item := range msgSlice { + msg[i] = fakeSenderForwardNode(ctx.Event.SelfID, item...) + } + return msg, nil +} + +// fakeSenderForwardNode 伪造一个发送者为RssHub订阅姬的消息节点 +func fakeSenderForwardNode(userID int64, msgs ...message.Segment) message.Segment { + return message.CustomNode( + "RssHub订阅姬", + userID, + msgs) +} From 5cbc9329d376bb9d7c5a0a57dcd0e623dbecc1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=94=85=E9=A5=AD?= <1156544355@qq.com> Date: Sun, 14 Sep 2025 23:46:01 +0800 Subject: [PATCH 47/51] =?UTF-8?q?=F0=9F=94=A5=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/rsshub/domain/model.go | 8 -------- plugin/rsshub/domain/rawFeed.go | 9 --------- plugin/rsshub/main.go | 15 ++------------- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/plugin/rsshub/domain/model.go b/plugin/rsshub/domain/model.go index 64c9d33400..8c7d34cec7 100644 --- a/plugin/rsshub/domain/model.go +++ b/plugin/rsshub/domain/model.go @@ -9,8 +9,6 @@ import ( // ======== RSS ========[START] -// type SingleFeedItem gofeed.Item - func genHashForFeedItem(link, guid string) string { idString := link + "||" + guid h := fnv.New32() @@ -51,8 +49,6 @@ type RssSource struct { Link string `gorm:"column:link" json:"link"` // UpdatedParsed RSS页面更新时间 UpdatedParsed time.Time `gorm:"column:updated_parsed" json:"updated_parsed"` - //// Ctime create time - // Ctime int64 `gorm:"column:ctime;default:current_timestamp" json:"ctime"` // Mtime update time Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"` } @@ -83,8 +79,6 @@ type RssContent struct { Author string `gorm:"column:author" json:"author"` Thumbnail string `gorm:"column:thumbnail" json:"thumbnail"` Content string `gorm:"column:content" json:"content"` - //// Ctime create time - // Ctime int64 `gorm:"column:ctime;default:current_timestamp" json:"ctime"` // Mtime update time Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"` } @@ -109,8 +103,6 @@ type RssSubscribe struct { GroupID int64 `gorm:"column:group_id;not null;uniqueIndex:uk_sid_gid"` // 订阅频道 RssSourceID int64 `gorm:"column:rss_source_id;not null;uniqueIndex:uk_sid_gid"` - //// Ctime create time - // Ctime int64 `gorm:"column:ctime;default:current_timestamp" json:"ctime"` // Mtime update time Mtime time.Time `gorm:"column:mtime;default:current_timestamp;" json:"mtime"` } diff --git a/plugin/rsshub/domain/rawFeed.go b/plugin/rsshub/domain/rawFeed.go index a8f6ee5d66..0b29fe32b6 100644 --- a/plugin/rsshub/domain/rawFeed.go +++ b/plugin/rsshub/domain/rawFeed.go @@ -12,11 +12,6 @@ import ( "github.com/sirupsen/logrus" ) -// const ( -// acceptHeader = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" -// userHeader = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Edg/84.0.522.63" -//) - var ( // RSSHubMirrors RSSHub镜像站地址列表,第一个为默认地址 rssHubMirrors = []string{ @@ -48,10 +43,6 @@ func (c *RssHubClient) FetchFeed(path string) (feed *gofeed.Feed, err error) { logrus.Errorf("[rsshub FetchFeed] fetch feed error: data is empty") return nil, errors.New("feed data is empty") } - // data, err = web.RequestDataWith(c.Client, domain+path, "GET", "", web.RandUA(), nil) - // if err != nil { - // return nil, err - //} feed, err = gofeed.NewParser().Parse(bytes.NewBuffer(data)) if err != nil { return diff --git a/plugin/rsshub/main.go b/plugin/rsshub/main.go index 26984eae44..7803d4ef38 100644 --- a/plugin/rsshub/main.go +++ b/plugin/rsshub/main.go @@ -18,19 +18,8 @@ import ( // 初始化 repo var ( - rssRepo domain.RssDomain - initErr error - //// getRssRepo repo 初始化方法,单例 - // getRssRepo = ctxext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { - // logrus.Infoln("RssHub订阅姬:初始化") - // rssRepo, initErr = domain.NewRssDomain(engine.DataFolder() + "rsshub.db") - // if initErr != nil { - // ctx.SendChain(message.Text("RssHub订阅姬:初始化失败", initErr.Error())) - // return false - // } - // return true - // }) - // regexpForSQL 防注入 + rssRepo domain.RssDomain + initErr error regexpForSQL = regexp.MustCompile(`[\^<>\[\]%&\*\(\)\{\}\|\=]|(union\s+select|update\s+|delete\s+|drop\s+|truncate\s+|insert\s+|exec\s+|declare\s+)`) ) From 641495b579a703d4407ad49ec503af2497df2f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=AB=E6=80=9D=E6=BD=8B?= <55676105+shudorcl@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:44:15 +0800 Subject: [PATCH 48/51] chore: update wife data (#1207) --- data | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data b/data index 6bcac0faab..328d7638e6 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit 6bcac0faaba9dc8664a84ae4de46154cfd9e740c +Subproject commit 328d7638e6947e8ac1bc8d3f5ecd6a351a4a3b6f From b9250ab82d48404171d0ede4f9643ca202c7c761 Mon Sep 17 00:00:00 2001 From: Kajiekazz <145256947+Kajiekazz@users.noreply.github.com> Date: Tue, 16 Sep 2025 21:49:34 +0800 Subject: [PATCH 49/51] doc: update zb version tag (#1206) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e923a77fdc..2d49be9b0c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ [![go](https://goreportcard.com/badge/github.com/FloatTech/ZeroBot-Plugin?style=flat-square&logo=go)](https://goreportcard.com/badge/github.com/FloatTech/ZeroBot-Plugin) [![onebot](https://img.shields.io/badge/onebot-v11-black?style=flat-square&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABwCAMAAADxPgR5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRF////29vbr6+vAAAAk1hCcwAAAAR0Uk5T////AEAqqfQAAAKcSURBVHja7NrbctswDATQXfD//zlpO7FlmwAWIOnOtNaTM5JwDMa8E+PNFz7g3waJ24fviyDPgfhz8fHP39cBcBL9KoJbQUxjA2iYqHL3FAnvzhL4GtVNUcoSZe6eSHizBcK5LL7dBr2AUZlev1ARRHCljzRALIEog6H3U6bCIyqIZdAT0eBuJYaGiJaHSjmkYIZd+qSGWAQnIaz2OArVnX6vrItQvbhZJtVGB5qX9wKqCMkb9W7aexfCO/rwQRBzsDIsYx4AOz0nhAtWu7bqkEQBO0Pr+Ftjt5fFCUEbm0Sbgdu8WSgJ5NgH2iu46R/o1UcBXJsFusWF/QUaz3RwJMEgngfaGGdSxJkE/Yg4lOBryBiMwvAhZrVMUUvwqU7F05b5WLaUIN4M4hRocQQRnEedgsn7TZB3UCpRrIJwQfqvGwsg18EnI2uSVNC8t+0QmMXogvbPg/xk+Mnw/6kW/rraUlvqgmFreAA09xW5t0AFlHrQZ3CsgvZm0FbHNKyBmheBKIF2cCA8A600aHPmFtRB1XvMsJAiza7LpPog0UJwccKdzw8rdf8MyN2ePYF896LC5hTzdZqxb6VNXInaupARLDNBWgI8spq4T0Qb5H4vWfPmHo8OyB1ito+AysNNz0oglj1U955sjUN9d41LnrX2D/u7eRwxyOaOpfyevCWbTgDEoilsOnu7zsKhjRCsnD/QzhdkYLBLXjiK4f3UWmcx2M7PO21CKVTH84638NTplt6JIQH0ZwCNuiWAfvuLhdrcOYPVO9eW3A67l7hZtgaY9GZo9AFc6cryjoeFBIWeU+npnk/nLE0OxCHL1eQsc1IciehjpJv5mqCsjeopaH6r15/MrxNnVhu7tmcslay2gO2Z1QfcfX0JMACG41/u0RrI9QAAAABJRU5ErkJggg==)](https://t.me/zerobotplugin) - [![zerobot](https://img.shields.io/badge/zerobot-v1.8.0-black?style=flat-square&logo=go)](https://github.com/wdvxdr1123/ZeroBot) + [![zerobot](https://img.shields.io/badge/zerobot-v1.8.1-black?style=flat-square&logo=go)](https://github.com/wdvxdr1123/ZeroBot) From c9033f59d290cddb2c2af4704f3bac24d342208c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=94=85=E9=A5=AD?= <1156544355@qq.com> Date: Tue, 16 Sep 2025 23:21:34 +0800 Subject: [PATCH 50/51] =?UTF-8?q?=F0=9F=8E=A8=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/rsshub/domain/job.go | 5 +++-- plugin/rsshub/domain/model.go | 7 +++++-- plugin/rsshub/main.go | 8 ++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/plugin/rsshub/domain/job.go b/plugin/rsshub/domain/job.go index b6e4fb642c..1763743a11 100644 --- a/plugin/rsshub/domain/job.go +++ b/plugin/rsshub/domain/job.go @@ -58,7 +58,7 @@ func (repo *rssDomain) syncRss(ctx context.Context) (updated map[int64]*RssClien } } var updateChannelView = &RssClientView{Source: cv.Source, Contents: []*RssContent{}} - err = repo.processContentsUpdate(ctx, cv, err, updateChannelView) + err = repo.processContentsUpdate(ctx, cv, updateChannelView) if err != nil { logrus.WithContext(ctx).Errorf("[rsshub syncRss] processContentsUpdate error: %v", err) continue @@ -94,7 +94,8 @@ func (repo *rssDomain) checkSourceNeedUpdate(ctx context.Context, source *RssSou } // processContentsUpdate 处理内容(s)更新 -func (repo *rssDomain) processContentsUpdate(ctx context.Context, cv *RssClientView, err error, updateChannelView *RssClientView) error { +func (repo *rssDomain) processContentsUpdate(ctx context.Context, cv *RssClientView, updateChannelView *RssClientView) error { + var err error for _, content := range cv.Contents { if content == nil { continue diff --git a/plugin/rsshub/domain/model.go b/plugin/rsshub/domain/model.go index 8c7d34cec7..3e3e2cd662 100644 --- a/plugin/rsshub/domain/model.go +++ b/plugin/rsshub/domain/model.go @@ -10,9 +10,12 @@ import ( // ======== RSS ========[START] func genHashForFeedItem(link, guid string) string { - idString := link + "||" + guid h := fnv.New32() - _, _ = h.Write([]byte(idString)) + // 分三次写入数据:link、分隔符、guid + _, _ = h.Write([]byte(link)) + _, _ = h.Write([]byte("||")) + _, _ = h.Write([]byte(guid)) + encoded := hex.EncodeToString(h.Sum(nil)) return encoded } diff --git a/plugin/rsshub/main.go b/plugin/rsshub/main.go index 7803d4ef38..ff4cce1088 100644 --- a/plugin/rsshub/main.go +++ b/plugin/rsshub/main.go @@ -74,8 +74,8 @@ func init() { sendRssUpdateMsg(ctx, groupToFeedsMap) }) // 添加订阅 - engine.OnRegex(`^添加rsshub订阅-(.+)$`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { - routeStr := ctx.State["regex_matched"].([]string)[1] + engine.OnPrefix("添加rsshub订阅-", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { + routeStr := ctx.State["args"].(string) input := regexpForSQL.ReplaceAllString(routeStr, "") logrus.Debugf("添加rsshub订阅:raw(%s), replaced(%s)", routeStr, input) rv, _, isSubExisted, err := rssRepo.Subscribe(context.Background(), ctx.Event.GroupID, input) @@ -98,8 +98,8 @@ func init() { ctx.SendChain(message.Text("ERROR: 发送订阅源快照失败,可能被风控了")) } }) - engine.OnRegex(`^删除rsshub订阅-(.+)$`, zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { - routeStr := ctx.State["regex_matched"].([]string)[1] + engine.OnPrefix("删除rsshub订阅-", zero.OnlyGroup).SetBlock(true).Handle(func(ctx *zero.Ctx) { + routeStr := ctx.State["args"].(string) input := regexpForSQL.ReplaceAllString(routeStr, "") logrus.Debugf("删除rsshub订阅:raw(%s), replaced(%s)", routeStr, input) err := rssRepo.Unsubscribe(context.Background(), ctx.Event.GroupID, input) From fe9db2d70f755a362ba08e47330d83fdb90f6cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E9=94=85=E9=A5=AD?= <1156544355@qq.com> Date: Tue, 16 Sep 2025 23:31:51 +0800 Subject: [PATCH 51/51] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=E4=BF=AE=E6=94=B9li?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugin/rsshub/domain/job.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugin/rsshub/domain/job.go b/plugin/rsshub/domain/job.go index 1763743a11..078523fa10 100644 --- a/plugin/rsshub/domain/job.go +++ b/plugin/rsshub/domain/job.go @@ -53,8 +53,6 @@ func (repo *rssDomain) syncRss(ctx context.Context) (updated map[int64]*RssClien err = repo.storage.UpsertSource(ctx, cv.Source) if err != nil { logrus.WithContext(ctx).Errorf("[rsshub syncRss] upsert source error: %v", err) - err = nil - // continue } } var updateChannelView = &RssClientView{Source: cv.Source, Contents: []*RssContent{}}