diff --git a/README.md b/README.md index ddaf845a..20d2fede 100644 --- a/README.md +++ b/README.md @@ -1644,12 +1644,12 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置AI聊天触发概率10 - [x] 设置AI聊天温度80 - - [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI] + - [x] 设置AI聊天(识图)接口类型[OpenAI|OLLaMA|GenAI] - [x] 设置AI聊天(不)使用Agent模式 - [x] 设置AI聊天(不)支持系统提示词 - - [x] 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions - - [x] 设置AI聊天密钥xxx - - [x] 设置AI聊天模型名Qwen/Qwen3-8B + - [x] 设置AI聊天(识图)接口地址https://api.siliconflow.cn/v1/chat/completions + - [x] 设置AI聊天(识图)密钥xxx + - [x] 设置AI聊天(识图)模型名Qwen/Qwen3-8B - [x] 查看AI聊天系统提示词 - [x] 重置AI聊天系统提示词 - [x] 设置AI聊天系统提示词xxx diff --git a/go.mod b/go.mod index 9428c062..84e580b8 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.20250922144137-bf2b9bb6a8d9 + github.com/FloatTech/zbputils v1.7.2-0.20250925155009-638ed762e15e 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,9 +22,9 @@ 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-20250920170049-e3d1b92cc3a1 + github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa github.com/fumiama/go-base16384 v1.7.0 - github.com/fumiama/go-onebot-agent v0.0.0-20250922152742-c40bb3512d63 + github.com/fumiama/go-onebot-agent v0.0.0-20250925150209-46ace7c2b17a github.com/fumiama/go-registry v0.2.7 github.com/fumiama/gotracemoe v0.0.3 github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 @@ -66,7 +66,7 @@ require ( github.com/faiface/beep v1.1.0 // indirect github.com/fumiama/go-simple-protobuf v0.2.0 // indirect github.com/fumiama/gofastTEA v0.0.10 // indirect - github.com/fumiama/imgsz v0.0.2 // indirect + github.com/fumiama/imgsz v0.0.4 // indirect github.com/gabriel-vasile/mimetype v1.0.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/go.sum b/go.sum index b0b8fce9..d7b0cd78 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.20250922144137-bf2b9bb6a8d9 h1:iR36inettls14aMOADNQ7PHNlGvgyDRRYp2dBgZCp8A= -github.com/FloatTech/zbputils v1.7.2-0.20250922144137-bf2b9bb6a8d9/go.mod h1:L1Rvdf6JUXGRIdKaXVtBWa0iW481zccCjYdYeDSaMXs= +github.com/FloatTech/zbputils v1.7.2-0.20250925155009-638ed762e15e h1:M+pIxQFztHqrtUVmfctSs/D5ytn0ag6twP6iJg3gdEk= +github.com/FloatTech/zbputils v1.7.2-0.20250925155009-638ed762e15e/go.mod h1:AUDxqs7liBF2H7TpSs+OXZj1Akyh0moUN/J/j8iNFxc= 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= @@ -63,12 +63,12 @@ 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-20250920170049-e3d1b92cc3a1 h1:6PglFpNVm3DalGyRldacW2/v4jGWwn3v3q1tr2PhbVQ= -github.com/fumiama/deepinfra v0.0.0-20250920170049-e3d1b92cc3a1/go.mod h1:wW05PQSn8mo1mZIoa6LBUE+3xIBjkoONvnfPTV5ZOhY= +github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa h1:UMMNejpPp8dn92GPaVSZ2XKNSgp7+CVneOkZfExUilk= +github.com/fumiama/deepinfra v0.0.0-20250924162107-cf156d49a0fa/go.mod h1:uqsWK/GM9OvKV0pXZOQB63rWugBbiXInY8E1JoRKhkg= 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-onebot-agent v0.0.0-20250922152742-c40bb3512d63 h1:ZdPMPIgZMH4HV4A/JIBb8G7UpLM4iUHWQ8qGjKnKiVI= -github.com/fumiama/go-onebot-agent v0.0.0-20250922152742-c40bb3512d63/go.mod h1:wVMgFWkR3GpipL05FkokvrV/jWFIgoEWN1jzUGa0bWg= +github.com/fumiama/go-onebot-agent v0.0.0-20250925150209-46ace7c2b17a h1:PapkA1fkFCzBbcmFaxRQvRAHbRig3NIgstzG7OFcXjQ= +github.com/fumiama/go-onebot-agent v0.0.0-20250925150209-46ace7c2b17a/go.mod h1:FIhZxVeFAs201W06EgXxx/6b/l/ETSmu2sQOj10kjdk= github.com/fumiama/go-registry v0.2.7 h1:tLEqgEpsiybQMqBv0dLHm5leia/z1DhajMupwnOHeNs= github.com/fumiama/go-registry v0.2.7/go.mod h1:m+wp5fF8dYgVoFkBPZl+vlK90loymaJE0JCtocVQLEs= github.com/fumiama/go-simple-protobuf v0.2.0 h1:ACyN1MAlu7pDR3EszWgzUeNP+IRsSHwH6V9JCJA5R5o= @@ -77,8 +77,8 @@ github.com/fumiama/gofastTEA v0.0.10 h1:JJJ+brWD4kie+mmK2TkspDXKzqq0IjXm89aGYfoG github.com/fumiama/gofastTEA v0.0.10/go.mod h1:RIdbYZyB4MbH6ZBlPymRaXn3cD6SedlCu5W/HHfMPBk= github.com/fumiama/gotracemoe v0.0.3 h1:iI5EbE9A3UUbfukG6+/soYPjp1S31eCNYf4tw7s6/Jc= github.com/fumiama/gotracemoe v0.0.3/go.mod h1:tyqahdUzHf0bQIAVY/GYmDWvYYe5ik1ZbhnGYh+zl40= -github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak= -github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4= +github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI= +github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E= github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565 h1:sQuR2+N5HurnvsZhiKdEg+Ig354TaqgCQRxd/0KgIOQ= github.com/fumiama/jieba v0.0.0-20221203025406-36c17a10b565/go.mod h1:UUEvyLTJ7yoOA/viKG4wEis4ERydM7+Ny6gZUWgkS80= github.com/fumiama/libc v0.0.0-20240530081950-6f6d8586b5c5 h1:jDxsIupsT84A6WHcs6kWbst+KqrRQ8/o0VyoFMnbBOA= diff --git a/plugin/aichat/cfg.go b/plugin/aichat/cfg.go index 6299e169..14b2bc0f 100644 --- a/plugin/aichat/cfg.go +++ b/plugin/aichat/cfg.go @@ -1,6 +1,7 @@ package aichat import ( + "errors" "fmt" "strconv" "strings" @@ -18,19 +19,92 @@ var ( cfg = newconfig() ) +var ( + apitypes = map[string]uint8{ + "OpenAI": 0, + "OLLaMA": 1, + "GenAI": 2, + } + apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"} +) + +// ModelType 支持打印 string 并生产 protocal +type ModelType int + +func newModelType(typ string) (ModelType, error) { + t, ok := apitypes[typ] + if !ok { + return 0, errors.New("未知类型 " + typ) + } + return ModelType(t), nil +} + +func (mt ModelType) String() string { + return apilist[mt] +} + +func (mt ModelType) protocol(modn string, temp float32, topp float32, maxn uint) (mod model.Protocol, err error) { + switch cfg.Type { + case 0: + mod = model.NewOpenAI( + modn, cfg.Separator, + temp, topp, maxn, + ) + case 1: + mod = model.NewOLLaMA( + modn, cfg.Separator, + temp, topp, maxn, + ) + case 2: + mod = model.NewGenAI( + modn, + temp, topp, maxn, + ) + default: + err = errors.New("unsupported model type " + strconv.Itoa(int(cfg.Type))) + } + return +} + +// ModelBool 支持打印成 "是/否" +type ModelBool bool + +func (mb ModelBool) String() string { + if mb { + return "是" + } + return "否" +} + +// ModelKey 支持隐藏密钥 +type ModelKey string + +func (mk ModelKey) String() string { + if len(mk) == 0 { + return "未设置" + } + if len(mk) <= 4 { + return "****" + } + key := string(mk) + return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:] +} + type config struct { - ModelName string - Type int - MaxN uint - TopP float32 - SystemP string - API string - Key string - Separator string - NoReplyAT bool - NoSystemP bool - NoRecord bool - NoAgent bool + ModelName string + ImageModelName string + Type ModelType + ImageType ModelType + MaxN uint + TopP float32 + SystemP string + API string + ImageAPI string + Key ModelKey + ImageKey ModelKey + Separator string + NoReplyAT ModelBool + NoSystemP ModelBool } func newconfig() config { @@ -41,10 +115,47 @@ func newconfig() config { } } +func (c *config) String() string { + topp, maxn := c.mparams() + sb := strings.Builder{} + sb.WriteString(fmt.Sprintf("• 模型名:%s\n", c.ModelName)) + sb.WriteString(fmt.Sprintf("• 图像模型名:%s\n", c.ImageModelName)) + sb.WriteString(fmt.Sprintf("• 接口类型:%v\n", c.Type)) + sb.WriteString(fmt.Sprintf("• 图像接口类型:%v\n", c.ImageType)) + sb.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn)) + sb.WriteString(fmt.Sprintf("• TopP:%.1f\n", topp)) + sb.WriteString(fmt.Sprintf("• 系统提示词:%s\n", c.SystemP)) + sb.WriteString(fmt.Sprintf("• 接口地址:%s\n", c.API)) + sb.WriteString(fmt.Sprintf("• 图像接口地址:%s\n", c.ImageAPI)) + sb.WriteString(fmt.Sprintf("• 密钥:%v\n", c.Key)) + sb.WriteString(fmt.Sprintf("• 图像密钥:%v\n", c.ImageKey)) + sb.WriteString(fmt.Sprintf("• 分隔符:%s\n", c.Separator)) + sb.WriteString(fmt.Sprintf("• 响应@:%v\n", !c.NoReplyAT)) + sb.WriteString(fmt.Sprintf("• 支持系统提示词:%v\n", !c.NoSystemP)) + return sb.String() +} + func (c *config) isvalid() bool { return c.ModelName != "" && c.API != "" && c.Key != "" } +// 获取全局模型参数:TopP和最大长度 +func (c *config) mparams() (topp float32, maxn uint) { + // 处理TopP参数 + topp = c.TopP + if topp == 0 { + topp = 0.9 + } + + // 处理最大长度参数 + maxn = c.MaxN + if maxn == 0 { + maxn = 4096 + } + + return topp, maxn +} + func ensureconfig(ctx *zero.Ctx) bool { c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) if !ok { @@ -62,7 +173,7 @@ func ensureconfig(ctx *zero.Ctx) bool { return true } -func newextrasetstr(ptr *string) func(ctx *zero.Ctx) { +func newextrasetstr[T ~string](ptr *T) func(ctx *zero.Ctx) { return func(ctx *zero.Ctx) { args := strings.TrimSpace(ctx.State["args"].(string)) if args == "" { @@ -74,7 +185,7 @@ func newextrasetstr(ptr *string) func(ctx *zero.Ctx) { ctx.SendChain(message.Text("ERROR: no such plugin")) return } - *ptr = args + *ptr = T(args) err := c.SetExtra(&cfg) if err != nil { ctx.SendChain(message.Text("ERROR: set extra err: ", err)) @@ -84,7 +195,7 @@ func newextrasetstr(ptr *string) func(ctx *zero.Ctx) { } } -func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) { +func newextrasetbool[T ~bool](ptr *T) func(ctx *zero.Ctx) { return func(ctx *zero.Ctx) { args := ctx.State["regex_matched"].([]string) isno := args[1] == "不" @@ -93,7 +204,7 @@ func newextrasetbool(ptr *bool) func(ctx *zero.Ctx) { ctx.SendChain(message.Text("ERROR: no such plugin")) return } - *ptr = isno + *ptr = T(isno) err := c.SetExtra(&cfg) if err != nil { ctx.SendChain(message.Text("ERROR: set extra err: ", err)) @@ -156,44 +267,3 @@ 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 4292761b..98ac2f39 100644 --- a/plugin/aichat/main.go +++ b/plugin/aichat/main.go @@ -3,9 +3,7 @@ package aichat import ( "encoding/json" - "errors" "math/rand" - "reflect" "strconv" "strings" "time" @@ -17,6 +15,7 @@ import ( "github.com/tidwall/gjson" zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/extension/single" "github.com/wdvxdr1123/ZeroBot/message" "github.com/FloatTech/AnimeAPI/airecord" @@ -35,12 +34,12 @@ var ( Brief: "OpenAI聊天", Help: "- 设置AI聊天触发概率10\n" + "- 设置AI聊天温度80\n" + - "- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" + + "- 设置AI聊天(识图)接口类型[OpenAI|OLLaMA|GenAI]\n" + "- 设置AI聊天(不)使用Agent模式\n" + "- 设置AI聊天(不)支持系统提示词\n" + - "- 设置AI聊天接口地址https://api.siliconflow.cn/v1/chat/completions\n" + - "- 设置AI聊天密钥xxx\n" + - "- 设置AI聊天模型名Qwen/Qwen3-8B\n" + + "- 设置AI聊天(识图)接口地址https://api.siliconflow.cn/v1/chat/completions\n" + + "- 设置AI聊天(识图)密钥xxx\n" + + "- 设置AI聊天(识图)模型名Qwen/Qwen3-8B\n" + "- 查看AI聊天系统提示词\n" + "- 重置AI聊天系统提示词\n" + "- 设置AI聊天系统提示词xxx\n" + @@ -55,61 +54,36 @@ var ( "- /gpt [内容] (使用大模型聊天)\n", PrivateDataFolder: "aichat", - }) + }).ApplySingle(single.New( + single.WithKeyFn(func(ctx *zero.Ctx) int64 { + if ctx.Event.GroupID == 0 { + return -ctx.Event.UserID + } + return ctx.Event.GroupID + }), + // no post option, silently quit + )) ) var ( - apitypes = map[string]uint8{ - "OpenAI": 0, - "OLLaMA": 1, - "GenAI": 2, - } - apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"} - limit = ctxext.NewLimiterManager(time.Second*30, 1) + 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() != "" && - (!cfg.NoReplyAT || (cfg.NoReplyAT && !ctx.Event.IsToMe)) + (bool(!cfg.NoReplyAT) || (bool(cfg.NoReplyAT) && !ctx.Event.IsToMe)) }).SetBlock(false).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 { + stor, err := newstorage(ctx, gid) + if err != nil { + logrus.Warnln("ERROR: ", err) return } - rate := c.GetData(gid) - temp := (rate >> 8) & 0xff - rate &= 0xff + rate := stor.rate() if !ctx.Event.IsToMe && rand.Intn(100) >= int(rate) { return } @@ -120,33 +94,17 @@ func init() { logrus.Warnln("ERROR: get extra err: empty key") return } + temperature := stor.temp() + topp, maxn := cfg.mparams() - temperature, topp, maxn := getModelParams(temp) - - 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) + x := deepinfra.NewAPI(cfg.API, string(cfg.Key)) + mod, err := cfg.Type.protocol(cfg.ModelName, temperature, topp, maxn) + if err != nil { + logrus.Warnln("ERROR: ", err) return } - if !cfg.NoAgent { + if !stor.noagent() { role := goba.PermRoleUser if zero.AdminPermission(ctx) { role = goba.PermRoleAdmin @@ -154,36 +112,37 @@ func init() { role = goba.PermRoleOwner } } - reqs, err := chat.AgentOf(ctx.Event.SelfID).GetAction(x, mod, gid, role, false) - if err != nil { - logrus.Warnln("[aichat] agent err:", err, reqs) - return - } - logrus.Infoln("[aichat] agent do:", reqs) - for _, req := range reqs { - if req.Action == "send_group_msg" { - v, ok := req.Params["group_id"].(json.Number) - if !ok { - logrus.Warnln("[aichat] invalid group_id type", reflect.TypeOf(req.Params["group_id"])) - continue - } - gid, err = v.Int64() - if !ok { - logrus.Warnln("[aichat] agent conv req gid err:", err) - continue - } - if ctx.Event.GroupID != gid && !zero.SuperUserPermission(ctx) { - logrus.Warnln("[aichat] refuse to send out of grp from", ctx.Event.GroupID, "to", gid) - continue - } + ag := chat.AgentOf(ctx.Event.SelfID) + if cfg.ImageAPI != "" && !ag.CanViewImage() { + mod, err := cfg.ImageType.protocol(cfg.ImageModelName, temperature, topp, maxn) + if err != nil { + logrus.Warnln("ERROR: ", err) + return + } + ag.SetViewImageAPI(deepinfra.NewAPI(cfg.ImageAPI, string(cfg.ImageKey)), mod) + } + ctx.NoTimeout() + for i := 0; i < 8; i++ { // 最大运行 8 轮因为问答上下文只有 16 + reqs := chat.CallAgent(ag, zero.SuperUserPermission(ctx), x, mod, gid, role) + if len(reqs) == 0 { + return + } + for _, req := range reqs { + resp := ctx.CallAction(req.Action, req.Params) + logrus.Infoln("[aichat] agent get resp:", reqs) + ag.AddResponse(gid, &goba.APIResponse{ + Status: resp.Status, + Data: json.RawMessage(resp.Data.Raw), + Message: resp.Message, + Wording: resp.Wording, + RetCode: resp.RetCode, + }) } - ctx.CallAction(req.Action, req.Params) - process.SleepAbout1sTo2s() } return } - data, err := x.Request(chat.GetChatContext(mod, gid, cfg.SystemP, cfg.NoSystemP)) + data, err := x.Request(chat.GetChatContext(mod, gid, cfg.SystemP, bool(cfg.NoSystemP))) if err != nil { logrus.Warnln("[aichat] post err:", err) return @@ -206,7 +165,7 @@ func init() { logrus.Infoln("[aichat] 回复内容:", t) recCfg := airecord.GetConfig() record := "" - if !cfg.NoRecord { + if !stor.norecord() { record = ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, t) } if record != "" { @@ -222,72 +181,8 @@ func init() { } } }) - en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).Handle(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 - } - r, err := strconv.Atoi(args) - if err != nil { - ctx.SendChain(message.Text("ERROR: parse rate err: ", err)) - return - } - if r > 100 { - r = 100 - } else if r < 0 { - r = 0 - } - gid := ctx.Event.GroupID - if gid == 0 { - gid = -ctx.Event.UserID - } - val := c.GetData(gid) & (^0xff) - err = c.SetData(gid, val|int64(r&0xff)) - if err != nil { - ctx.SendChain(message.Text("ERROR: set data err: ", err)) - return - } - ctx.SendChain(message.Text("成功")) - }) - en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).Handle(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 - } - r, err := strconv.Atoi(args) - if err != nil { - ctx.SendChain(message.Text("ERROR: parse rate err: ", err)) - return - } - if r > 100 { - r = 100 - } else if r < 0 { - r = 0 - } - gid := ctx.Event.GroupID - if gid == 0 { - gid = -ctx.Event.UserID - } - val := c.GetData(gid) & (^0xff00) - err = c.SetData(gid, val|(int64(r&0xff)<<8)) - if err != nil { - ctx.SendChain(message.Text("ERROR: set data err: ", err)) - return - } - ctx.SendChain(message.Text("成功")) - }) + en.OnPrefix("设置AI聊天触发概率", zero.AdminPermission).SetBlock(true).Handle(newstoragebitmap(bitmaprate, 0, 100)) + en.OnPrefix("设置AI聊天温度", zero.AdminPermission).SetBlock(true).Handle(newstoragebitmap(bitmaptemp, 0, 100)) en.OnPrefix("设置AI聊天接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { args := strings.TrimSpace(ctx.State["args"].(string)) if args == "" { @@ -299,13 +194,37 @@ func init() { ctx.SendChain(message.Text("ERROR: no such plugin")) return } - typ, ok := apitypes[args] - if !ok { - ctx.SendChain(message.Text("ERROR: 未知类型 ", args)) + typ, err := newModelType(args) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) return } - cfg.Type = int(typ) - err := c.SetExtra(&cfg) + cfg.Type = typ + err = c.SetExtra(&cfg) + if err != nil { + ctx.SendChain(message.Text("ERROR: set extra err: ", err)) + return + } + ctx.SendChain(message.Text("成功")) + }) + en.OnPrefix("设置AI聊天识图接口类型", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(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 + } + typ, err := newModelType(args) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + cfg.ImageType = typ + err = c.SetExtra(&cfg) if err != nil { ctx.SendChain(message.Text("ERROR: set extra err: ", err)) return @@ -314,10 +233,16 @@ func init() { }) en.OnPrefix("设置AI聊天接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetstr(&cfg.API)) + en.OnPrefix("设置AI聊天识图接口地址", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(newextrasetstr(&cfg.ImageAPI)) en.OnPrefix("设置AI聊天密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetstr(&cfg.Key)) + en.OnPrefix("设置AI聊天识图密钥", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(newextrasetstr(&cfg.ImageKey)) en.OnPrefix("设置AI聊天模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetstr(&cfg.ModelName)) + en.OnPrefix("设置AI聊天识图模型名", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). + Handle(newextrasetstr(&cfg.ImageModelName)) en.OnPrefix("设置AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetstr(&cfg.SystemP)) en.OnFullMatch("查看AI聊天系统提示词", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { @@ -343,31 +268,32 @@ func init() { Handle(newextrasetbool(&cfg.NoReplyAT)) en.OnRegex("^设置AI聊天(不)?支持系统提示词$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). Handle(newextrasetbool(&cfg.NoSystemP)) - en.OnRegex("^设置AI聊天(不)?使用Agent模式$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). - Handle(newextrasetbool(&cfg.NoAgent)) + en.OnRegex("^设置AI聊天(不)?使用Agent模式$", ensureconfig, zero.SuperUserPermission).SetBlock(true). + Handle(newstoragebool(bitmapnagt)) 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)) - en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true). - Handle(newextrasetbool(&cfg.NoRecord)) + en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.AdminPermission).SetBlock(true). + Handle(newstoragebool(bitmapnrec)) 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")) + gid := ctx.Event.GroupID + stor, err := newstorage(ctx, gid) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) 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))) + ctx.SendChain( + message.Text( + "【当前AI聊天本群配置】\n", + "• 触发概率:", stor.rate(), "\n", + "• 温度:", stor.temp(), "\n", + "• 以AI语音输出:", ModelBool(!stor.norecord()), "\n", + "• 使用Agent:", ModelBool(!stor.noagent()), "\n", + ), + message.Text("【当前AI聊天全局配置】\n", &cfg), + ) }) en.OnFullMatch("重置AI聊天", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { chat.ResetChat() @@ -381,12 +307,6 @@ func init() { 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 @@ -421,8 +341,13 @@ func init() { summaryPrompt := "请总结这个群聊内容,要求按发言顺序梳理,明确标注每个发言者的昵称,并完整呈现其核心观点、提出的问题、发表的看法或做出的回应,确保不遗漏关键信息,且能体现成员间的对话逻辑和互动关系:\n" + strings.Join(messages, "\n") + stor, err := newstorage(ctx, gid) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } // 调用大模型API进行总结 - summary, err := llmchat(summaryPrompt, temp) + summary, err := llmchat(summaryPrompt, stor.temp()) if err != nil { ctx.SendChain(message.Text("ERROR: ", err)) @@ -469,12 +394,6 @@ func init() { 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 @@ -517,8 +436,13 @@ func init() { return } + stor, err := newstorage(ctx, gid) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } // 调用大模型API进行聊天 - reply, err := llmchat(query, temp) + reply, err := llmchat(query, stor.temp()) if err != nil { ctx.SendChain(message.Text("ERROR: ", err)) return @@ -549,33 +473,17 @@ func init() { } // llmchat 调用大模型API包装 -func llmchat(prompt string, temp int64) (string, error) { - temperature, topp, maxn := getModelParams(temp) // 使用默认温度70 +func llmchat(prompt string, temp float32) (string, error) { + topp, maxn := cfg.mparams() - 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类型") + x := deepinfra.NewAPI(cfg.API, string(cfg.Key)) + + mod, err := cfg.Type.protocol(cfg.ModelName, temp, topp, maxn) + if err != nil { + return "", nil } - data, err := x.Request(mod.User(prompt)) + data, err := x.Request(mod.User(model.NewContentText(prompt))) if err != nil { return "", err } diff --git a/plugin/aichat/storage.go b/plugin/aichat/storage.go new file mode 100644 index 00000000..7195a941 --- /dev/null +++ b/plugin/aichat/storage.go @@ -0,0 +1,141 @@ +package aichat + +import ( + "errors" + "math/bits" + "strconv" + "strings" + + ctrl "github.com/FloatTech/zbpctrl" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +const ( + bitmaprate = 0x0000ff + bitmaptemp = 0x00ff00 + bitmapnagt = 0x010000 + bitmapnrec = 0x020000 +) + +type storage int64 + +func newstorage(ctx *zero.Ctx, gid int64) (storage, error) { + c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) + if !ok { + return 0, errors.New("找不到 manager") + } + x := c.GetData(gid) + return storage(x), nil +} + +func (s storage) saveto(ctx *zero.Ctx, gid int64) error { + c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) + if !ok { + return errors.New("找不到 manager") + } + return c.SetData(int64(s), gid) +} + +func (s storage) getbybmp(bmp int64) int64 { + sft := bits.TrailingZeros64(uint64(bmp)) + return (int64(s) & bmp) >> int64(sft) +} + +func (s *storage) setbybmp(x int64, bmp int64) { + if bmp == 0 { + panic("cannot use bmp == 0") + } + sft := bits.TrailingZeros64(uint64(bmp)) + *s = storage((int64(*s) & (^bmp)) | ((x & bmp) << int64(sft))) +} + +func (s storage) rate() uint8 { + return uint8(s.getbybmp(bitmaprate)) +} + +func (s storage) temp() float32 { + temp := s.getbybmp(bitmaptemp) + // 处理温度参数 + if temp <= 0 { + temp = 70 // default setting + } + if temp > 100 { + temp = 100 + } + return float32(temp) / 100 +} + +func (s storage) noagent() bool { + return s.getbybmp(bitmapnagt) != 0 +} + +func (s storage) norecord() bool { + return s.getbybmp(bitmapnrec) != 0 +} + +func newstoragebitmap(bmp int64, minv, maxv int64) 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 + } + r, err := strconv.ParseInt(args, 10, 64) + if err != nil { + ctx.SendChain(message.Text("ERROR: parse int64 err: ", err)) + return + } + if r > maxv { + r = maxv + } else if r < minv { + r = minv + } + gid := ctx.Event.GroupID + if gid == 0 { + gid = -ctx.Event.UserID + } + stor, err := newstorage(ctx, gid) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + stor.setbybmp(r, bmp) + err = stor.saveto(ctx, gid) + if err != nil { + ctx.SendChain(message.Text("ERROR: set data err: ", err)) + return + } + ctx.SendChain(message.Text("成功")) + } +} + +func newstoragebool(bmp int64) func(ctx *zero.Ctx) { + if bits.OnesCount64(uint64(bmp)) != 1 { + panic("bool bmp must be 1-bit-long") + } + return func(ctx *zero.Ctx) { + args := ctx.State["regex_matched"].([]string) + iszero := args[1] == "不" + gid := ctx.Event.GroupID + if gid == 0 { + gid = -ctx.Event.UserID + } + stor, err := newstorage(ctx, gid) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + v := 1 + if iszero { + v = 0 + } + stor.setbybmp(int64(v), bmp) + err = stor.saveto(ctx, gid) + if err != nil { + ctx.SendChain(message.Text("ERROR: set data err: ", err)) + return + } + ctx.SendChain(message.Text("成功")) + } +} diff --git a/plugin/chess/chess.go b/plugin/chess/chess.go index f32e6bf2..4f693d7c 100644 --- a/plugin/chess/chess.go +++ b/plugin/chess/chess.go @@ -13,7 +13,6 @@ import ( "github.com/FloatTech/zbputils/control" "github.com/FloatTech/zbputils/ctxext" zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/extension/single" "github.com/wdvxdr1123/ZeroBot/message" ) @@ -35,16 +34,7 @@ var ( Brief: "国际象棋", Help: helpString, PrivateDataFolder: "chess", - }).ApplySingle(single.New( - single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), - single.WithPostFn[int64](func(ctx *zero.Ctx) { - ctx.Send( - message.ReplyWithMessage(ctx.Event.MessageID, - message.Text("有操作正在执行, 请稍后再试..."), - ), - ) - }), - )) + }).ApplySingle(ctxext.GroupSingle) ) func init() { diff --git a/plugin/guessmusic/main.go b/plugin/guessmusic/main.go index 3c758d55..0974aba7 100644 --- a/plugin/guessmusic/main.go +++ b/plugin/guessmusic/main.go @@ -15,7 +15,6 @@ import ( "github.com/FloatTech/zbputils/ctxext" "github.com/pkg/errors" zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/extension/single" "github.com/wdvxdr1123/ZeroBot/message" // 图片输出 @@ -65,17 +64,7 @@ var ( "- 下载歌单[网易云歌单链接/ID]到[歌单名称]\n" + "- 解除绑定 [歌单名称]", PrivateDataFolder: "guessmusic", - }).ApplySingle(single.New( - single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), - single.WithPostFn[int64](func(ctx *zero.Ctx) { - ctx.Break() - ctx.Send( - message.ReplyWithMessage(ctx.Event.MessageID, - message.Text("已经有正在进行的游戏..."), - ), - ) - }), - )) + }).ApplySingle(ctxext.NewGroupSingle("已经有正在进行的游戏...")) // 用于存放歌曲三个片段的缓存文件夹 cachePath = engine.DataFolder() + "cache/" // 用于存放用户的配置 diff --git a/plugin/qqwife/command.go b/plugin/qqwife/command.go index 9f548bd0..fe53b0c6 100644 --- a/plugin/qqwife/command.go +++ b/plugin/qqwife/command.go @@ -17,7 +17,7 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" // 反并发 - "github.com/wdvxdr1123/ZeroBot/extension/single" + // 数据库 sql "github.com/FloatTech/sqlite" // 画图 @@ -67,16 +67,7 @@ var ( "\"娶群友\"&\"(娶|嫁)@对方QQ\"指令好感度随机增加1~5。\n\"A牛B的C\"会导致C恨A, 好感度-5;\nB为了报复A, 好感度+5(什么柜子play)\nA为BC做媒,成功B、C对A好感度+1反之-1\n做媒成功BC好感度+1" + "\nTips: 群老婆列表过0点刷新", PrivateDataFolder: "qqwife", - }).ApplySingle(single.New( - single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), - single.WithPostFn[int64](func(ctx *zero.Ctx) { - ctx.Send( - message.ReplyWithMessage(ctx.Event.MessageID, - message.Text("别着急,民政局门口排长队了!"), - ), - ) - }), - )) + }).ApplySingle(ctxext.NewGroupSingle("别着急,民政局门口排长队了!")) getdb = fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { 民政局.db = sql.New(engine.DataFolder() + "结婚登记表.db") err := 民政局.db.Open(time.Hour) diff --git a/plugin/robbery/robbery.go b/plugin/robbery/robbery.go index e393161b..dfda1c48 100644 --- a/plugin/robbery/robbery.go +++ b/plugin/robbery/robbery.go @@ -11,7 +11,6 @@ import ( sql "github.com/FloatTech/sqlite" ctrl "github.com/FloatTech/zbpctrl" "github.com/FloatTech/zbputils/control" - "github.com/wdvxdr1123/ZeroBot/extension/single" "github.com/FloatTech/AnimeAPI/wallet" "github.com/FloatTech/floatbox/math" @@ -45,16 +44,7 @@ func init() { "7. 每日可打劫或被打劫一次\n" + "8. 打劫失败不计入次数\n", PrivateDataFolder: "robbery", - }).ApplySingle(single.New( - single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), - single.WithPostFn[int64](func(ctx *zero.Ctx) { - ctx.Send( - message.ReplyWithMessage(ctx.Event.MessageID, - message.Text("别着急,警察局门口排长队了!"), - ), - ) - }), - )) + }).ApplySingle(ctxext.NewGroupSingle("别着急,警察局门口排长队了!")) getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { police.db = sql.New(engine.DataFolder() + "robbery.db") err := police.db.Open(time.Hour) diff --git a/plugin/wenxinvilg/main.go b/plugin/wenxinvilg/main.go index 36d64f82..0f510b85 100644 --- a/plugin/wenxinvilg/main.go +++ b/plugin/wenxinvilg/main.go @@ -14,7 +14,6 @@ import ( "github.com/FloatTech/zbputils/control" "github.com/FloatTech/zbputils/ctxext" zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/extension/single" "github.com/wdvxdr1123/ZeroBot/message" // 数据库 @@ -83,17 +82,7 @@ func init() { // 插件主体 "指令示例:\n" + name + "帮我画几张金凤凰,背景绚烂,高饱和,古风,仙境,高清,4K,古风的油画方图", PrivateDataFolder: "wenxinAI", - }).ApplySingle(single.New( - single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), - single.WithPostFn[int64](func(ctx *zero.Ctx) { - ctx.Break() - ctx.Send( - message.ReplyWithMessage(ctx.Event.MessageID, - message.Text(zero.BotConfig.NickName[0], "正在给别人画图,请不要打扰哦"), - ), - ) - }), - )) + }).ApplySingle(ctxext.NewGroupSingle("正在给别人画图,请不要打扰哦")) getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { vilginfo.db = sql.New(engine.DataFolder() + "ernieVilg.db") err := vilginfo.db.Open(time.Hour) @@ -285,17 +274,7 @@ func init() { // 插件主体 "文心自定义 请写出下面这道题的解题过程。\\n题目:养殖场养鸭376只,养鸡的只数比鸭多258只,这个养殖场一共养鸭和鸡多少只?\\n解:\n\n" + "文心自定义 1+1=?\n" + "文心自定义 歌曲名:大风车转啊转\\n歌词:", - }).ApplySingle(single.New( - single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), - single.WithPostFn[int64](func(ctx *zero.Ctx) { - ctx.Break() - ctx.Send( - message.ReplyWithMessage(ctx.Event.MessageID, - message.Text(zero.BotConfig.NickName[0], "正在给别人编辑,请不要打扰哦"), - ), - ) - }), - )) + }).ApplySingle(ctxext.NewGroupSingle("正在给别人编辑,请不要打扰哦")) getmodeldb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { modelinfo.db = sql.New(engine.DataFolder() + "ernieModel.db") err := modelinfo.db.Open(time.Hour) diff --git a/plugin/wife/main.go b/plugin/wife/main.go index a353ee3a..75bd1e89 100644 --- a/plugin/wife/main.go +++ b/plugin/wife/main.go @@ -3,8 +3,9 @@ package wife import ( "encoding/json" + "fmt" "os" - "strings" + "regexp" fcext "github.com/FloatTech/floatbox/ctxext" ctrl "github.com/FloatTech/zbpctrl" @@ -15,15 +16,27 @@ import ( "github.com/wdvxdr1123/ZeroBot/message" ) -func init() { - engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ +var ( + cards = []string{} + re = regexp.MustCompile(`^\[(.*?)\](.*)\..*$`) + engine = control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, Help: "- 抽老婆", Brief: "从老婆库抽每日老婆", PublicDataFolder: "Wife", }).ApplySingle(ctxext.DefaultSingle) +) + +func card2name(card string) (string, string) { + match := re.FindStringSubmatch(card) + if len(match) >= 3 { + return match[1], match[2] + } + return "", "" +} + +func init() { _ = os.MkdirAll(engine.DataFolder()+"wives", 0755) - cards := []string{} engine.OnFullMatch("抽老婆", fcext.DoOnceOnSuccess( func(ctx *zero.Ctx) bool { data, err := engine.GetLazyData("wife.json", true) @@ -43,22 +56,28 @@ func init() { Handle(func(ctx *zero.Ctx) { card := cards[fcext.RandSenderPerDayN(ctx.Event.UserID, len(cards))] data, err := engine.GetLazyData("wives/"+card, true) - card, _, _ = strings.Cut(card, ".") + var msgText string + work, name := card2name(card) + if work != "" && name != "" { + msgText = fmt.Sprintf("今天的二次元老婆是~来自【%s】的【%s】哒", work, name) + } else { + msgText = fmt.Sprintf("今天的二次元老婆是~【%s】哒", card) + } if err != nil { ctx.SendChain( message.At(ctx.Event.UserID), - message.Text("今天的二次元老婆是~【", card, "】哒\n【图片下载失败: ", err, "】"), + message.Text(msgText, "\n【图片下载失败: ", err, "】"), ) return } if id := ctx.SendChain( message.At(ctx.Event.UserID), - message.Text("今天的二次元老婆是~【", card, "】哒"), + message.Text(msgText), message.ImageBytes(data), ); id.ID() == 0 { ctx.SendChain( message.At(ctx.Event.UserID), - message.Text("今天的二次元老婆是~【", card, "】哒\n【图片发送失败, 请联系维护者】"), + message.Text(msgText, "\n【图片发送失败, 多半是被夹了,请联系维护者】"), ) } }) diff --git a/plugin/wife/wifegame.go b/plugin/wife/wifegame.go new file mode 100644 index 00000000..f2b1c286 --- /dev/null +++ b/plugin/wife/wifegame.go @@ -0,0 +1,198 @@ +// Package wife 抽老婆 +package wife + +import ( + "errors" + "image/color" + "io/fs" + "math/rand" + "os" + "strings" + "time" + + "github.com/FloatTech/floatbox/file" + "github.com/FloatTech/gg" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/ctxext" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" + + zbmath "github.com/FloatTech/floatbox/math" + "github.com/FloatTech/imgfactory" +) + +var ( + sizeList = []int{0, 3, 5, 8} + enguess = control.Register("wifegame", &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Help: "- 猜老婆", + Brief: "从老婆库猜老婆", + }).ApplySingle(ctxext.NewGroupSingle("已经有正在进行的游戏...")) +) + +func init() { + // _ = os.MkdirAll(engine.DataFolder()+"wives", 0755) + enguess.OnFullMatch("猜老婆").SetBlock(true).Limit(ctxext.LimitByUser).Handle(func(ctx *zero.Ctx) { + var err error + class := 3 + + fileName, err := lottery() + if err != nil { + ctx.SendChain(message.Text("[猜老婆]error:\n", err)) + return + } + + work, name := card2name(fileName) + picFile := file.BOTPATH + "/" + engine.DataFolder() + "wives/" + fileName + pic, err := os.ReadFile(picFile) + if err != nil { + ctx.SendChain(message.Text("[猜老婆]error:\n", err)) + return + } + img, err := gg.LoadImage(picFile) + if err != nil { + ctx.SendChain(message.Text("[猜老婆]error:\n", err)) + return + } + dst := imgfactory.Size(img, img.Bounds().Dx(), img.Bounds().Dy()) + q, err := mosaic(dst, class) + if err != nil { + ctx.SendChain( + message.Reply(ctx.Event.MessageID), + message.Text("[猜老婆]图片生成失败:\n", err), + ) + return + } + if id := ctx.SendChain( + message.ImageBytes(q), + ); id.ID() != 0 { + ctx.SendChain(message.Text("请回答该二次元角色名字\n以“xxx酱”格式回答")) + } + var next *zero.FutureEvent + if ctx.Event.GroupID == 0 { + next = zero.NewFutureEvent("message", 999, false, zero.RegexRule(`(·)?.+酱$`), ctx.CheckSession()) + } else { + next = zero.NewFutureEvent("message", 999, false, zero.RegexRule(`(·)?.+酱$`), zero.CheckGroup(ctx.Event.GroupID)) + } + recv, cancel := next.Repeat() + defer cancel() + tick := time.NewTimer(105 * time.Second) + after := time.NewTimer(120 * time.Second) + for { + select { + case <-tick.C: + ctx.SendChain(message.Text("[猜老婆]你还有15s作答时间")) + case <-after.C: + ctx.Send( + message.ReplyWithMessage(ctx.Event.MessageID, + message.ImageBytes(pic), + message.Text("[猜老婆]倒计时结束,游戏结束...\n角色是:\n", name, "\n出自《", work, "》\n"), + ), + ) + return + case c := <-recv: + tick.Reset(105 * time.Second) + after.Reset(120 * time.Second) + msg := c.Event.Message.String() + msg, _, _ = strings.Cut(msg, "酱") + class-- + if strings.Contains(name, msg) { + if msgID := ctx.Send(message.ReplyWithMessage(c.Event.MessageID, + message.Text("太棒了,你猜对了!\n角色是:\n", name, "\n出自《", work, "》\n"), + message.ImageBytes(pic))); msgID.ID() == 0 { + ctx.SendChain(message.Text("太棒了,你猜对了!\n图片发送失败,可能被风控\n角色是:\n", name, "\n出自《", work, "》")) + } + return + } + if class < 1 { + if msgID := ctx.Send(message.ReplyWithMessage(c.Event.MessageID, + message.Text("很遗憾,次数到了,游戏结束!\n角色是:\n", name, "\n出自《", work, "》\n"), + message.ImageBytes(pic))); msgID.ID() == 0 { + ctx.SendChain(message.Text("很遗憾,次数到了,游戏结束!\n图片发送失败,可能被风控\n角色是:\n", name, "\n出自《", work, "》")) + } + return + } + q, err = mosaic(dst, class) + if err != nil { + ctx.SendChain( + message.Text("回答错误,你还有", class, "次机会\n请继续作答\n(提示:", work, ")"), + ) + continue + } + ctx.SendChain( + message.Text("回答错误,你还有", class, "次机会\n请继续作答(难度降低)\n"), + message.ImageBytes(q), + ) + continue + } + } + }) +} + +// 从本地图库随机抽取,规避网络问题 +func lottery() (fileName string, err error) { + path := engine.DataFolder() + "wives" + "/" + if file.IsNotExist(path) { + err = errors.New("图库文件夹不存在,请先发送“抽老婆”扩展图库") + return + } + files, err := os.ReadDir(path) + if err != nil { + return + } + // 如果本地列表为空 + if len(files) == 0 { + err = errors.New("本地数据为0,请先发送“抽老婆”扩展图库") + return + } + fileName = randPicture(files, 10) + if fileName == "" { + err = errors.New("抽取图库轮空了,请重试") + } + return +} + +func randPicture(files []fs.DirEntry, indexMax int) (fileName string) { + if len(files) > 1 { + picture := files[rand.Intn(len(files))] + // 如果是文件夹就递归 + if picture.IsDir() { + indexMax-- + if indexMax <= 0 { + return + } + fileName = randPicture(files, indexMax) + } else { + fileName = picture.Name() + } + } else { + music := files[0] + if !music.IsDir() { + fileName = files[0].Name() + } + } + return +} + +// 马赛克生成 +func mosaic(dst *imgfactory.Factory, level int) ([]byte, error) { + b := dst.Image().Bounds() + p := imgfactory.NewFactoryBG(dst.W(), dst.H(), color.NRGBA{255, 255, 255, 255}) + markSize := zbmath.Max(b.Max.X, b.Max.Y) * sizeList[level] / 200 + + for yOfMarknum := 0; yOfMarknum <= zbmath.Ceil(b.Max.Y, markSize); yOfMarknum++ { + for xOfMarknum := 0; xOfMarknum <= zbmath.Ceil(b.Max.X, markSize); xOfMarknum++ { + a := dst.Image().At(xOfMarknum*markSize+markSize/2, yOfMarknum*markSize+markSize/2) + cc := color.NRGBAModel.Convert(a).(color.NRGBA) + for y := 0; y < markSize; y++ { + for x := 0; x < markSize; x++ { + xOfPic := xOfMarknum*markSize + x + yOfPic := yOfMarknum*markSize + y + p.Image().Set(xOfPic, yOfPic, cc) + } + } + } + } + return imgfactory.ToBytes(p.Blur(3).Image()) +} diff --git a/plugin/wordle/wordle.go b/plugin/wordle/wordle.go index 491f27de..0f12bddb 100644 --- a/plugin/wordle/wordle.go +++ b/plugin/wordle/wordle.go @@ -22,7 +22,6 @@ import ( "github.com/FloatTech/zbputils/control" "github.com/FloatTech/zbputils/ctxext" zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/extension/single" "github.com/wdvxdr1123/ZeroBot/message" ) @@ -69,16 +68,7 @@ func init() { "- 团队六阶猜单词\n" + "- 团队七阶猜单词", PublicDataFolder: "Wordle", - }).ApplySingle(single.New( - single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), - single.WithPostFn[int64](func(ctx *zero.Ctx) { - ctx.Send( - message.ReplyWithMessage(ctx.Event.MessageID, - message.Text("已经有正在进行的游戏..."), - ), - ) - }), - )) + }).ApplySingle(ctxext.NewGroupSingle("已经有正在进行的游戏...")) en.OnRegex(`^(个人|团队)(五阶|六阶|七阶)?猜单词$`, zero.OnlyGroup, fcext.DoOnceOnSuccess( func(ctx *zero.Ctx) bool { diff --git a/plugin/ymgal/ymgal.go b/plugin/ymgal/ymgal.go index 4b492a49..56017277 100644 --- a/plugin/ymgal/ymgal.go +++ b/plugin/ymgal/ymgal.go @@ -72,7 +72,7 @@ func init() { func sendYmgal(y ymgal, ctx *zero.Ctx) { if y.PictureList == "" { - ctx.SendChain(message.Text(zero.BotConfig.NickName[0] + "暂时没有这样的图呢")) + ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "暂时没有这样的图呢")) return } m := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Text(y.Title))}