Merge branch 'FloatTech:master' into feature-rsshub-20250914

This commit is contained in:
himawari 2025-09-26 00:31:36 +08:00 committed by GitHub
commit b1d4bdfebf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 647 additions and 382 deletions

View File

@ -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

8
go.mod
View File

@ -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

16
go.sum
View File

@ -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=

View File

@ -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 "否"
}

View File

@ -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
}

141
plugin/aichat/storage.go Normal file
View File

@ -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("成功"))
}
}

View File

@ -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() {

View File

@ -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/"
// 用于存放用户的配置

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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【图片发送失败, 多半是被夹了,请联系维护者】"),
)
}
})

198
plugin/wife/wifegame.go Normal file
View File

@ -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())
}

View File

@ -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 {

View File

@ -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))}