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 {