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