diff --git a/README.md b/README.md index e5b57e36c0..d423a46a00 100644 --- a/README.md +++ b/README.md @@ -1542,18 +1542,6 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 大力骂我 - -
- 人工智能回复 - - `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aireply"` - - - [x] @Bot 任意文本(任意一句话回复) - - - [x] 设置文字回复模式[婧枫|沫沫|青云客|小爱|ChatGPT] - - - [x] 设置 ChatGPT api key xxx -
词典匹配回复 diff --git a/main.go b/main.go index 9bf8b1f493..444f5d8acd 100644 --- a/main.go +++ b/main.go @@ -169,8 +169,6 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/curse" // 骂人 - _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aireply" // 人工智能回复 - _ "github.com/FloatTech/ZeroBot-Plugin/plugin/thesaurus" // 词典匹配回复 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/breakrepeat" // 打断复读 diff --git a/plugin/aireply/ai_tts.go b/plugin/aireply/ai_tts.go deleted file mode 100644 index 51f230bab8..0000000000 --- a/plugin/aireply/ai_tts.go +++ /dev/null @@ -1,263 +0,0 @@ -package aireply - -import ( - "errors" - "strings" - - "github.com/RomiChan/syncx" - zero "github.com/wdvxdr1123/ZeroBot" - - "github.com/FloatTech/AnimeAPI/aireply" - "github.com/FloatTech/AnimeAPI/tts" - "github.com/FloatTech/AnimeAPI/tts/baidutts" - "github.com/FloatTech/AnimeAPI/tts/lolimi" - "github.com/FloatTech/AnimeAPI/tts/ttscn" - ctrl "github.com/FloatTech/zbpctrl" - "github.com/FloatTech/zbputils/control" -) - -// 数据结构: [8 bits] [8 bits] [8 bits] -// [具体人物] [tts模式] [回复模式] - -// defaultttsindexkey -// 数据结构: [8 bits] [8 bits] -// [具体人物] [tts模式] - -// [tts模式]: 0~200 genshin 201 baidu 202 ttscn 203 lolimi - -const ( - baiduttsindex = 201 + iota - ttscnttsindex - lolimittsindex -) - -// extrattsname is the tts other than genshin vits -var extrattsname = []string{"百度", "TTSCN", "桑帛云"} - -var ttscnspeakers = [...]string{ - "晓晓(女 - 年轻人)", - "云扬(男 - 年轻人)", - "晓辰(女 - 年轻人 - 抖音热门)", - "晓涵(女 - 年轻人)", - "晓墨(女 - 年轻人)", - "晓秋(女 - 中年人)", - "晓睿(女 - 老年)", - "晓双(女 - 儿童)", - "晓萱(女 - 年轻人)", - "晓颜(女 - 年轻人)", - "晓悠(女 - 儿童)", - "云希(男 - 年轻人 - 抖音热门)", - "云野(男 - 中年人)", - "晓梦(女 - 年轻人)", - "晓伊(女 - 儿童)", - "晓甄(女 - 年轻人)", -} - -const defaultttsindexkey = -2905 - -var ( - 原 = newapikeystore("./data/tts/o.txt") - ཆཏ = newapikeystore("./data/tts/c.txt") - 百 = newapikeystore("./data/tts/b.txt") - 桑 = newapikeystore("./data/tts/s.txt") -) - -type replymode []string - -func (r replymode) setReplyMode(ctx *zero.Ctx, name string) error { - gid := ctx.Event.GroupID - if gid == 0 { - gid = -ctx.Event.UserID - } - var ok bool - var index int64 - for i, s := range r { - if s == name { - ok = true - index = int64(i) - break - } - } - if !ok { - return errors.New("no such mode") - } - m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) - if !ok { - return errors.New("no such plugin") - } - return m.SetData(gid, (m.GetData(gid)&^0xff)|(index&0xff)) -} - -func (r replymode) getReplyMode(ctx *zero.Ctx) aireply.AIReply { - k := 桑.k - gid := ctx.Event.GroupID - if gid == 0 { - gid = -ctx.Event.UserID - } - m, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) - if ok { - switch m.GetData(gid) & 0xff { - case 0: - return aireply.NewLolimiAi(aireply.JingfengURL, aireply.JingfengBotName, k, false, 0) - case 1: - return aireply.NewLolimiAi(aireply.MomoURL, aireply.MomoBotName, k, false, 0) - case 2: - return aireply.NewQYK(aireply.QYKURL, aireply.QYKBotName) - case 3: - return aireply.NewXiaoAi(aireply.XiaoAiURL, aireply.XiaoAiBotName) - case 4: - if ཆཏ.k != "" { - return aireply.NewChatGPT(aireply.ChatGPTURL, ཆཏ.k) - } - return aireply.NewLolimiAi(aireply.JingfengURL, aireply.JingfengBotName, k, false, 0) - } - } - return aireply.NewLolimiAi(aireply.JingfengURL, aireply.JingfengBotName, k, false, 0) -} - -var ttsins = func() map[string]tts.TTS { - m := make(map[string]tts.TTS, 512) - for _, mode := range extrattsname { - m[mode] = nil - } - return m -}() - -var ttsModes = func() []string { - s := make([]string, baiduttsindex) // 0-200 - s = append(s, extrattsname...) // 201 202 ... - return s -}() - -type ttsmode syncx.Map[int64, int64] - -func list(list []string, num int) string { - s := "" - for i, value := range list { - s += value - if (i+1)%num == 0 { - s += "\n" - } else { - s += " | " - } - } - return s -} - -func newttsmode() *ttsmode { - t := &ttsmode{} - m, ok := control.Lookup("tts") - (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, 0) - if ok { - index := m.GetData(defaultttsindexkey) - msk := index & 0xff - if msk >= 0 && (msk < int64(len(ttsModes))) { - (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, index) - } - } - return t -} - -func (t *ttsmode) setSoundMode(ctx *zero.Ctx, name string, character int) error { - gid := ctx.Event.GroupID - if gid == 0 { - gid = -ctx.Event.UserID - } - _, ok := ttsins[name] - if !ok { - return errors.New("不支持设置语音人物" + name) - } - var index = int64(-1) - switch name { - case extrattsname[0]: - index = baiduttsindex - case extrattsname[1]: - index = ttscnttsindex - case extrattsname[2]: - index = lolimittsindex - default: - return errors.New("语音人物" + name + "未注册index") - } - m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) - // 按原来的逻辑map存的是前16位 - storeIndex := (m.GetData(gid) &^ 0xffff00) | ((index << 8) & 0xff00) | ((int64(character) << 16) & 0xff0000) - (*syncx.Map[int64, int64])(t).Store(gid, (storeIndex>>8)&0xffff) - return m.SetData(gid, storeIndex) -} - -func (t *ttsmode) getSoundMode(ctx *zero.Ctx) (tts.TTS, error) { - gid := ctx.Event.GroupID - if gid == 0 { - gid = -ctx.Event.UserID - } - i, ok := (*syncx.Map[int64, int64])(t).Load(gid) - if !ok { - m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) - i = m.GetData(gid) >> 8 - } - m := i & 0xff - if m <= 0 || (m >= int64(len(ttsModes))) { - i, _ = (*syncx.Map[int64, int64])(t).Load(defaultttsindexkey) - if i == 0 { - i = ctx.State["manager"].(*ctrl.Control[*zero.Ctx]).GetData(defaultttsindexkey) - (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, i) - } - m = i & 0xff - } - mode := ttsModes[m] - ins, ok := ttsins[mode] - if !ok || ins == nil { - switch mode { - case extrattsname[0]: - id, sec, _ := strings.Cut(百.k, ",") - ins = baidutts.NewBaiduTTS(int(i&0xff00)>>8, id, sec) - case extrattsname[1]: - var err error - ins, err = ttscn.NewTTSCN("中文(普通话,简体)", ttscnspeakers[int(i&0xff00)>>8], ttscn.KBRates[0]) - if err != nil { - return nil, err - } - case extrattsname[2]: - ins = lolimi.NewLolimi(int(i&0xff00) >> 8) - default: // 原神 - return nil, errors.New("no such mode") - } - } - return ins, nil -} - -func (t *ttsmode) resetSoundMode(ctx *zero.Ctx) error { - gid := ctx.Event.GroupID - if gid == 0 { - gid = -ctx.Event.UserID - } - m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx]) - // 只保留后面8位 - (*syncx.Map[int64, int64])(t).Delete(gid) - return m.SetData(gid, (m.GetData(gid) & 0xff)) // 重置数据 -} - -func (t *ttsmode) setDefaultSoundMode(name string, character int) error { - _, ok := ttsins[name] - if !ok { - return errors.New("不支持设置语音人物" + name) - } - index := int64(-1) - switch name { - case extrattsname[0]: - index = baiduttsindex - case extrattsname[1]: - index = ttscnttsindex - case extrattsname[2]: - index = lolimittsindex - default: - return errors.New("语音人物" + name + "未注册index") - } - m, ok := control.Lookup("tts") - if !ok { - return errors.New("[tts] service not found") - } - storeIndex := (index & 0xff) | ((int64(character) << 8) & 0xff00) - (*syncx.Map[int64, int64])(t).Store(defaultttsindexkey, storeIndex) - return m.SetData(defaultttsindexkey, storeIndex) -} diff --git a/plugin/aireply/main.go b/plugin/aireply/main.go deleted file mode 100644 index 6ad8d84641..0000000000 --- a/plugin/aireply/main.go +++ /dev/null @@ -1,229 +0,0 @@ -// Package aireply AI 回复 -package aireply - -import ( - "os" - "regexp" - "strconv" - "strings" - "time" - - ctrl "github.com/FloatTech/zbpctrl" - "github.com/FloatTech/zbputils/control" - "github.com/FloatTech/zbputils/ctxext" - "github.com/sirupsen/logrus" - zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/message" -) - -var replmd = replymode([]string{"婧枫", "沫沫", "青云客", "小爱", "ChatGPT"}) - -var ttsmd = newttsmode() - -func init() { // 插件主体 - ent := control.Register("tts", &ctrl.Options[*zero.Ctx]{ - DisableOnDefault: true, - Brief: "人工智能语音回复", - Help: "- @Bot 任意文本(任意一句话回复)\n" + - "- 设置语音模式[百度/TTSCN/桑帛云] 数字(百度/TTSCN说话人/桑帛云)\n" + - "- 设置默认语音模式[百度/TTSCN/桑帛云] 数字(百度/TTSCN说话人/桑帛云)\n" + - "- 恢复成默认语音模式\n" + - "- 设置语音回复模式[沫沫|婧枫|青云客|小爱|ChatGPT]\n" + - "- 设置百度语音 api id xxxxxx secret xxxxxx (请自行获得)\n" + - "\n当前适用的TTSCN人物含有以下(以数字顺序代表): \n" + list(ttscnspeakers[:], 5), - PrivateDataFolder: "tts", - }) - - enr := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ - DisableOnDefault: false, - Brief: "人工智能回复", - Help: "- @Bot 任意文本(任意一句话回复)\n- 设置文字回复模式[婧枫|沫沫|青云客|小爱|ChatGPT]\n- 设置 ChatGPT api key xxx", - PrivateDataFolder: "aireply", - }) - - enr.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser). - Handle(func(ctx *zero.Ctx) { - aireply := replmd.getReplyMode(ctx) - reply := message.ParseMessageFromString(aireply.Talk(ctx.Event.UserID, ctx.ExtractPlainText(), zero.BotConfig.NickName[0])) - // 回复 - time.Sleep(time.Second * 1) - reply = append(reply, message.Reply(ctx.Event.MessageID)) - ctx.Send(reply) - }) - setReplyMode := func(ctx *zero.Ctx) { - param := ctx.State["args"].(string) - err := replmd.setReplyMode(ctx, param) - if err != nil { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err)) - return - } - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("成功")) - } - enr.OnPrefix("设置文字回复模式", zero.AdminPermission).SetBlock(true).Handle(setReplyMode) - enr.OnRegex(`^设置\s*桑帛云\s*api\s*key\s*(.*)$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { - err := 桑.set(ctx.State["regex_matched"].([]string)[1]) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - ctx.SendChain(message.Text("设置成功")) - }) - enr.OnRegex(`^设置\s*ChatGPT\s*api\s*key\s*(.*)$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { - err := ཆཏ.set(ctx.State["regex_matched"].([]string)[1]) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - ctx.SendChain(message.Text("设置成功")) - }) - - endpre := regexp.MustCompile(`\pP$`) - ttscachedir := ent.DataFolder() + "cache/" - _ = os.RemoveAll(ttscachedir) - err := os.MkdirAll(ttscachedir, 0755) - if err != nil { - panic(err) - } - ent.OnMessage(zero.OnlyToMe).SetBlock(true).Limit(ctxext.LimitByUser). - Handle(func(ctx *zero.Ctx) { - msg := ctx.ExtractPlainText() - // 获取回复模式 - r := replmd.getReplyMode(ctx) - // 获取回复的文本 - reply := message.ParseMessageFromString(r.TalkPlain(ctx.Event.UserID, msg, zero.BotConfig.NickName[0])) - // 过滤掉文字消息 - filterMsg := make([]message.Segment, 0, len(reply)) - sb := strings.Builder{} - for _, v := range reply { - if v.Type != "text" { - filterMsg = append(filterMsg, v) - } else { - sb.WriteString(v.Data["text"]) - } - } - // 纯文本 - plainReply := sb.String() - plainReply = strings.ReplaceAll(plainReply, "\n", "") - // 获取语音 - speaker, err := ttsmd.getSoundMode(ctx) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - rec, err := speaker.Speak(ctx.Event.UserID, func() string { - if !endpre.MatchString(plainReply) { - return plainReply + "。" - } - return plainReply - }) - // 发送前面的图片 - if len(filterMsg) != 0 { - filterMsg = append(filterMsg, message.Reply(ctx.Event.MessageID)) - ctx.Send(filterMsg) - } - if err != nil { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(plainReply)) - return - } - // 发送语音 - if id := ctx.SendChain(message.Record(rec)); id.ID() == 0 { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(plainReply)) - } - }) - ent.OnPrefix("设置语音回复模式", zero.AdminPermission).SetBlock(true).Handle(setReplyMode) - ent.OnRegex(`^设置语音模式\s*([\S\D]*)\s*(\d*)$`, zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { - param := ctx.State["regex_matched"].([]string)[1] - num := ctx.State["regex_matched"].([]string)[2] - n := 0 - var err error - if num != "" { - n, err = strconv.Atoi(num) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - } - // 保存设置 - logrus.Debugln("[tts] t.setSoundMode( ctx", param, n, ")") - err = ttsmd.setSoundMode(ctx, param, n) - if err != nil { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err)) - return - } - banner := "这是一段测试语音" - logrus.Debugln("[tts] banner:", banner, "get sound mode...") - // 设置验证 - speaker, err := ttsmd.getSoundMode(ctx) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - logrus.Debugln("[tts] got sound mode, speaking...") - rec, err := speaker.Speak(ctx.Event.UserID, func() string { return banner }) - if err != nil { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("无法发送测试语音,请重试。")) - return - } - logrus.Debugln("[tts] sending...") - if id := ctx.SendChain(message.Record(rec).Add("cache", 0)); id.ID() == 0 { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("无法发送测试语音,请重试。")) - return - } - time.Sleep(time.Second * 2) - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,当前为", speaker)) - }) - - ent.OnRegex(`^设置默认语音模式\s*([\S\D]*)\s+(\d*)$`, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { - param := ctx.State["regex_matched"].([]string)[1] - num := ctx.State["regex_matched"].([]string)[2] - n := 0 - var err error - if num != "" { - n, err = strconv.Atoi(num) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - } - // 保存设置 - err = ttsmd.setDefaultSoundMode(param, n) - if err != nil { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err)) - return - } - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功")) - }) - - ent.OnFullMatch("恢复成默认语音模式", zero.AdminPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { - err := ttsmd.resetSoundMode(ctx) - if err != nil { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(err)) - return - } - // 设置验证 - speaker, err := ttsmd.getSoundMode(ctx) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text("设置成功,当前为", speaker)) - }) - - ent.OnRegex(`^设置原神语音\s*api\s*key\s*([0-9a-zA-Z-_]{54}==)$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { - err := 原.set(ctx.State["regex_matched"].([]string)[1]) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - ctx.SendChain(message.Text("设置成功")) - }) - - ent.OnRegex(`^设置百度语音\s*api\s*id\s*(.*)\s*secret\s*(.*)\s*$`, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).Handle(func(ctx *zero.Ctx) { - err := 百.set(ctx.State["regex_matched"].([]string)[1] + "," + ctx.State["regex_matched"].([]string)[2]) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - ctx.SendChain(message.Text("设置成功")) - }) -} diff --git a/plugin/aireply/model.go b/plugin/aireply/model.go deleted file mode 100644 index 1e79d5e3ff..0000000000 --- a/plugin/aireply/model.go +++ /dev/null @@ -1,29 +0,0 @@ -package aireply - -import ( - "os" - - "github.com/FloatTech/floatbox/binary" - "github.com/FloatTech/floatbox/file" -) - -type apikeystore struct { - k string - p string -} - -func newapikeystore(p string) (s apikeystore) { - s.p = p - if file.IsExist(p) { - data, err := os.ReadFile(p) - if err == nil { - s.k = binary.BytesToString(data) - } - } - return -} - -func (s *apikeystore) set(k string) error { - s.k = k - return os.WriteFile(s.p, binary.StringToBytes(k), 0644) -}