chore(lint): 改进代码样式 (#1167)
Some checks failed
打包最新版为 Docker Image / build docker (push) Waiting to run
最新版 / Build binary CI (386, linux) (push) Failing after 1s
最新版 / Build binary CI (386, windows) (push) Failing after 1s
最新版 / Build binary CI (amd64, linux) (push) Failing after 1s
最新版 / Build binary CI (amd64, windows) (push) Failing after 1s
最新版 / Build binary CI (arm, linux) (push) Failing after 1s
最新版 / Build binary CI (arm64, linux) (push) Failing after 1s
PushLint / lint (push) Failing after 1s

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
github-actions[bot] 2025-05-14 15:42:18 +09:00 committed by GitHub
parent 076b113455
commit 42fe124b09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,214 +1,214 @@
// Package wordcount 聊天热词 // Package wordcount 聊天热词
package wordcount package wordcount
import ( import (
"fmt" "fmt"
"os" "os"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/go-ego/gse" "github.com/go-ego/gse"
"github.com/golang/freetype" "github.com/golang/freetype"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"github.com/wcharczuk/go-chart/v2" "github.com/wcharczuk/go-chart/v2"
"github.com/FloatTech/floatbox/binary" "github.com/FloatTech/floatbox/binary"
fcext "github.com/FloatTech/floatbox/ctxext" fcext "github.com/FloatTech/floatbox/ctxext"
"github.com/FloatTech/floatbox/file" "github.com/FloatTech/floatbox/file"
ctrl "github.com/FloatTech/zbpctrl" ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control" "github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext" "github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/img/text" "github.com/FloatTech/zbputils/img/text"
zero "github.com/wdvxdr1123/ZeroBot" zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message" "github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper" "github.com/wdvxdr1123/ZeroBot/utils/helper"
) )
var ( var (
re = regexp.MustCompile(`^[一-龥]+$`) re = regexp.MustCompile(`^[一-龥]+$`)
stopwords []string stopwords []string
seg gse.Segmenter seg gse.Segmenter
) )
func init() { func init() {
engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false, DisableOnDefault: false,
Brief: "聊天热词", Brief: "聊天热词",
Help: "- 热词 [群号] [消息数目]|热词 123456 1000", Help: "- 热词 [群号] [消息数目]|热词 123456 1000",
PublicDataFolder: "WordCount", PublicDataFolder: "WordCount",
}) })
cachePath := engine.DataFolder() + "cache/" cachePath := engine.DataFolder() + "cache/"
// 读取gse内置中文词典 // 读取gse内置中文词典
err := seg.LoadDictEmbed() err := seg.LoadDictEmbed()
if err != nil { if err != nil {
panic(err) panic(err)
} }
_ = os.RemoveAll(cachePath) _ = os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755) _ = os.MkdirAll(cachePath, 0755)
engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { engine.OnRegex(`^热词\s?(\d*)\s?(\d*)$`, zero.OnlyGroup, fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool {
_, err := engine.GetLazyData("stopwords.txt", false) _, err := engine.GetLazyData("stopwords.txt", false)
if err != nil { if err != nil {
ctx.SendChain(message.Text("ERROR: ", err)) ctx.SendChain(message.Text("ERROR: ", err))
return false return false
} }
data, err := os.ReadFile(engine.DataFolder() + "stopwords.txt") data, err := os.ReadFile(engine.DataFolder() + "stopwords.txt")
if err != nil { if err != nil {
ctx.SendChain(message.Text("ERROR: ", err)) ctx.SendChain(message.Text("ERROR: ", err))
return false return false
} }
stopwords = strings.Split(strings.ReplaceAll(binary.BytesToString(data), "\r", ""), "\n") stopwords = strings.Split(strings.ReplaceAll(binary.BytesToString(data), "\r", ""), "\n")
sort.Strings(stopwords) sort.Strings(stopwords)
logrus.Infoln("[wordcount]加载", len(stopwords), "条停用词") logrus.Infoln("[wordcount]加载", len(stopwords), "条停用词")
return true return true
})).Limit(ctxext.LimitByUser).SetBlock(true). })).Limit(ctxext.LimitByUser).SetBlock(true).
Handle(func(ctx *zero.Ctx) { Handle(func(ctx *zero.Ctx) {
_, err := file.GetLazyData(text.FontFile, control.Md5File, true) _, err := file.GetLazyData(text.FontFile, control.Md5File, true)
if err != nil { if err != nil {
ctx.SendChain(message.Text("ERROR: ", err)) ctx.SendChain(message.Text("ERROR: ", err))
return return
} }
b, err := os.ReadFile(text.FontFile) b, err := os.ReadFile(text.FontFile)
if err != nil { if err != nil {
ctx.SendChain(message.Text("ERROR: ", err)) ctx.SendChain(message.Text("ERROR: ", err))
return return
} }
font, err := freetype.ParseFont(b) font, err := freetype.ParseFont(b)
if err != nil { if err != nil {
ctx.SendChain(message.Text("ERROR: ", err)) ctx.SendChain(message.Text("ERROR: ", err))
return return
} }
ctx.SendChain(message.Text("少女祈祷中...")) ctx.SendChain(message.Text("少女祈祷中..."))
gid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64) 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)[2], 10, 64)
if p > 10000 { if p > 10000 {
p = 10000 p = 10000
} }
if p == 0 { if p == 0 {
p = 1000 p = 1000
} }
if gid == 0 { if gid == 0 {
gid = ctx.Event.GroupID gid = ctx.Event.GroupID
} }
group := ctx.GetGroupInfo(gid, false) group := ctx.GetGroupInfo(gid, false)
if group.MemberCount == 0 { 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 return
} }
today := time.Now().Format("20060102") today := time.Now().Format("20060102")
drawedFile := fmt.Sprintf("%s%d%s%dwordCount.png", cachePath, gid, today, p) drawedFile := fmt.Sprintf("%s%d%s%dwordCount.png", cachePath, gid, today, p)
if file.IsExist(drawedFile) { if file.IsExist(drawedFile) {
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
return return
} }
messageMap := make(map[string]int, 256) messageMap := make(map[string]int, 256)
msghists := make(chan *gjson.Result, 256) msghists := make(chan *gjson.Result, 256)
go func() { go func() {
h := ctx.GetLatestGroupMessageHistory(gid) h := ctx.GetLatestGroupMessageHistory(gid)
messageSeq := h.Get("messages.0.message_seq").Int() messageSeq := h.Get("messages.0.message_seq").Int()
msghists <- &h msghists <- &h
for i := 1; i < int(p/20) && messageSeq != 0; i++ { for i := 1; i < int(p/20) && messageSeq != 0; i++ {
h := ctx.GetGroupMessageHistory(gid, messageSeq) h := ctx.GetGroupMessageHistory(gid, messageSeq)
msghists <- &h msghists <- &h
messageSeq = h.Get("messages.0.message_seq").Int() messageSeq = h.Get("messages.0.message_seq").Int()
} }
close(msghists) close(msghists)
}() }()
var wg sync.WaitGroup var wg sync.WaitGroup
var mapmu sync.Mutex var mapmu sync.Mutex
for h := range msghists { for h := range msghists {
wg.Add(1) wg.Add(1)
go func(h *gjson.Result) { go func(h *gjson.Result) {
for _, v := range h.Get("messages.#.message").Array() { for _, v := range h.Get("messages.#.message").Array() {
tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText()) tex := strings.TrimSpace(message.ParseMessageFromString(v.Str).ExtractPlainText())
if tex == "" { if tex == "" {
continue continue
} }
segments := seg.Segment(helper.StringToBytes(tex)) segments := seg.Segment(helper.StringToBytes(tex))
words := gse.ToSlice(segments, true) words := gse.ToSlice(segments, true)
for _, word := range words { for _, word := range words {
word = strings.TrimSpace(word) word = strings.TrimSpace(word)
i := sort.SearchStrings(stopwords, word) i := sort.SearchStrings(stopwords, word)
if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) { if re.MatchString(word) && (i >= len(stopwords) || stopwords[i] != word) {
mapmu.Lock() mapmu.Lock()
messageMap[word]++ messageMap[word]++
mapmu.Unlock() mapmu.Unlock()
} }
} }
} }
wg.Done() wg.Done()
}(h) }(h)
} }
wg.Wait() wg.Wait()
wc := rankByWordCount(messageMap) wc := rankByWordCount(messageMap)
if len(wc) > 20 { if len(wc) > 20 {
wc = wc[:20] wc = wc[:20]
} }
// 绘图 // 绘图
if len(wc) == 0 { if len(wc) == 0 {
ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息")) ctx.SendChain(message.Text("ERROR: 历史消息为空或者无法获得历史消息"))
return return
} }
bars := make([]chart.Value, len(wc)) bars := make([]chart.Value, len(wc))
for i, v := range wc { for i, v := range wc {
bars[i] = chart.Value{ bars[i] = chart.Value{
Value: float64(v.Value), Value: float64(v.Value),
Label: v.Key, Label: v.Key,
} }
} }
graph := chart.BarChart{ graph := chart.BarChart{
Font: font, Font: font,
Title: fmt.Sprintf("%s(%d)在%s号的%d条消息的热词top20", group.Name, gid, time.Now().Format("2006-01-02"), p), Title: fmt.Sprintf("%s(%d)在%s号的%d条消息的热词top20", group.Name, gid, time.Now().Format("2006-01-02"), p),
Background: chart.Style{ Background: chart.Style{
Padding: chart.Box{ Padding: chart.Box{
Top: 40, Top: 40,
}, },
}, },
Height: 500, Height: 500,
BarWidth: 25, BarWidth: 25,
Bars: bars, Bars: bars,
} }
f, err := os.Create(drawedFile) f, err := os.Create(drawedFile)
if err != nil { if err != nil {
ctx.SendChain(message.Text("ERROR: ", err)) ctx.SendChain(message.Text("ERROR: ", err))
return return
} }
err = graph.Render(chart.PNG, f) err = graph.Render(chart.PNG, f)
_ = f.Close() _ = f.Close()
if err != nil { if err != nil {
_ = os.Remove(drawedFile) _ = os.Remove(drawedFile)
ctx.SendChain(message.Text("ERROR: ", err)) ctx.SendChain(message.Text("ERROR: ", err))
return return
} }
ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile)) ctx.SendChain(message.Image("file:///" + file.BOTPATH + "/" + drawedFile))
}) })
} }
func rankByWordCount(wordFrequencies map[string]int) pairlist { func rankByWordCount(wordFrequencies map[string]int) pairlist {
pl := make(pairlist, len(wordFrequencies)) pl := make(pairlist, len(wordFrequencies))
i := 0 i := 0
for k, v := range wordFrequencies { for k, v := range wordFrequencies {
pl[i] = pair{k, v} pl[i] = pair{k, v}
i++ i++
} }
sort.Sort(sort.Reverse(pl)) sort.Sort(sort.Reverse(pl))
return pl return pl
} }
type pair struct { type pair struct {
Key string Key string
Value int Value int
} }
type pairlist []pair type pairlist []pair
func (p pairlist) Len() int { return len(p) } 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) 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] } func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] }