Skip to content

Commit 617d4f5

Browse files
guohuiyuanfumiama
andauthored
feat: airecord (FloatTech#1180)
Co-authored-by: 源文雨 <[email protected]>
1 parent cb0ffa0 commit 617d4f5

File tree

10 files changed

+267
-20
lines changed

10 files changed

+267
-20
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
255255
- [x] 翻牌
256256

257257
- [x] 赞我
258+
259+
- [x] 群签到
258260

259261
- [x] [开启 | 关闭]入群验证
260262

@@ -276,6 +278,20 @@ zerobot [-h] [-m] [-n nickname] [-t token] [-u url] [-g url] [-p prefix] [-d|w]
276278

277279
- 设置欢迎语可选添加参数说明:{at}可在发送时艾特被欢迎者 {nickname}是被欢迎者名字 {avatar}是被欢迎者头像 {uid}是被欢迎者QQ号 {gid}是当前群群号 {groupname} 是当前群群名
278280

281+
</details>
282+
<details>
283+
<summary>群应用:AI声聊</summary>
284+
285+
`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/airecord"`
286+
287+
- [x] 设置AI语音群号1048452984(tips:机器人任意所在群聊即可)
288+
289+
- [x] 设置AI语音模型
290+
291+
- [x] 查看AI语音配置
292+
293+
- [x] 发送AI语音xxx
294+
279295
</details>
280296
<details>
281297
<summary>定时指令触发器</summary>
@@ -1584,7 +1600,7 @@ print("run[CQ:image,file="+j["img"]+"]")
15841600
- [x] 设置AI聊天温度80
15851601
- [x] 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]
15861602
- [x] 设置AI聊天(不)支持系统提示词
1587-
- [x] 设置AI聊天接口地址https://xxx
1603+
- [x] 设置AI聊天接口地址https://api.deepseek.com/chat/completions
15881604
- [x] 设置AI聊天密钥xxx
15891605
- [x] 设置AI聊天模型名xxx
15901606
- [x] 查看AI聊天系统提示词
@@ -1594,6 +1610,8 @@ print("run[CQ:image,file="+j["img"]+"]")
15941610
- [x] 设置AI聊天(不)响应AT
15951611
- [x] 设置AI聊天最大长度4096
15961612
- [x] 设置AI聊天TopP 0.9
1613+
- [x] 设置AI聊天(不)以AI语音输出
1614+
- [x] 查看AI聊天配置
15971615

15981616
</details>
15991617
<details>

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.20
44

55
require (
66
github.com/Baidu-AIP/golang-sdk v1.1.1
7-
github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b
7+
github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46
88
github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80
99
github.com/FloatTech/gg v1.1.3
1010
github.com/FloatTech/imgfactory v0.2.2-0.20230413152719-e101cc3606ef
@@ -45,7 +45,7 @@ require (
4545
github.com/sirupsen/logrus v1.9.3
4646
github.com/tidwall/gjson v1.18.0
4747
github.com/wcharczuk/go-chart/v2 v2.1.2
48-
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5
48+
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7
4949
gitlab.com/gomidi/midi/v2 v2.1.7
5050
golang.org/x/image v0.24.0
5151
golang.org/x/sys v0.30.0

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
github.com/Baidu-AIP/golang-sdk v1.1.1 h1:RQsAmgDSAkiq22I6n7XJ2t3afgzFeqjY46FGhvrx4cw=
22
github.com/Baidu-AIP/golang-sdk v1.1.1/go.mod h1:bXnGw7xPeKt8aF7UCELKrV6UZ/46spItONK1RQBQj1Y=
33
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
4-
github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b h1:H/1xpchTGmdoHqrszH4gjafCyHIhsGSFryAkBNsu8OI=
5-
github.com/FloatTech/AnimeAPI v1.7.1-0.20250530055006-50f5c7587c5b/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js=
4+
github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46 h1:X6ZbOWoZJIoHCin+CeU92Q3EwpvglyQ4gc5BZhOtAwo=
5+
github.com/FloatTech/AnimeAPI v1.7.1-0.20250717123723-d300df538b46/go.mod h1:XXG1eBJf+eeWacQx5azsQKL5Gg7jDYTFyyZGIa/56js=
66
github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80 h1:lFD1pd8NkYCrw0QpTX/T5pJ67I7AL5eGxQ4v0r9f81Q=
77
github.com/FloatTech/floatbox v0.0.0-20250513111443-adba80e84e80/go.mod h1:IWoFFqu+0FeaHHQdddyiTRL5z7gJME6qHC96qh0R2sc=
88
github.com/FloatTech/gg v1.1.3 h1:+GlL02lTKsxJQr4WCuNwVxC1/eBZrCvypCIBtxuOFb4=
@@ -199,8 +199,8 @@ github.com/vcaesar/cedar v0.20.2/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFe
199199
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
200200
github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
201201
github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
202-
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5 h1:HsMcBsVpYuQv+W8pjX5WdwYROrFQP9c5Pbf4x4adDus=
203-
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250330133859-27c25d9412b5/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
202+
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7 h1:ya+lVbCC/EN5JumpQDDlVCSrWzLwHl4CHzlTANKDvrU=
203+
github.com/wdvxdr1123/ZeroBot v1.8.2-0.20250707133321-6197b8ee5df7/go.mod h1:C86nQ0gIdAri4K2vg8IIQIslt08zzrKMcqYt8zhkx1M=
204204
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
205205
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
206206
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import (
3838

3939
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/sleepmanage" // 统计睡眠时间
4040

41+
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/airecord" // 群应用:AI声聊
42+
4143
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/atri" // ATRI词库
4244

4345
_ "github.com/FloatTech/ZeroBot-Plugin/plugin/manager" // 群管

plugin/aichat/cfg.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package aichat
22

33
import (
4+
"fmt"
45
"strconv"
56
"strings"
67

@@ -13,7 +14,9 @@ import (
1314
"github.com/wdvxdr1123/ZeroBot/message"
1415
)
1516

16-
var cfg = newconfig()
17+
var (
18+
cfg = newconfig()
19+
)
1720

1821
type config struct {
1922
ModelName string
@@ -26,6 +29,7 @@ type config struct {
2629
Separator string
2730
NoReplyAT bool
2831
NoSystemP bool
32+
NoRecord bool
2933
}
3034

3135
func newconfig() config {
@@ -151,3 +155,44 @@ func newextrasetfloat32(ptr *float32) func(ctx *zero.Ctx) {
151155
ctx.SendChain(message.Text("成功"))
152156
}
153157
}
158+
159+
func printConfig(rate int64, temperature int64, cfg config) string {
160+
maxn := cfg.MaxN
161+
if maxn == 0 {
162+
maxn = 4096
163+
}
164+
topp := cfg.TopP
165+
if topp == 0 {
166+
topp = 0.9
167+
}
168+
var builder strings.Builder
169+
builder.WriteString("当前AI聊天配置:\n")
170+
builder.WriteString(fmt.Sprintf("• 模型名:%s\n", cfg.ModelName))
171+
builder.WriteString(fmt.Sprintf("• 接口类型:%d(%s)\n", cfg.Type, apilist[cfg.Type]))
172+
builder.WriteString(fmt.Sprintf("• 触发概率:%d%%\n", rate))
173+
builder.WriteString(fmt.Sprintf("• 温度:%.2f\n", float32(temperature)/100))
174+
builder.WriteString(fmt.Sprintf("• 最大长度:%d\n", maxn))
175+
builder.WriteString(fmt.Sprintf("• TopP:%.1f\n", topp))
176+
builder.WriteString(fmt.Sprintf("• 系统提示词:%s\n", cfg.SystemP))
177+
builder.WriteString(fmt.Sprintf("• 接口地址:%s\n", cfg.API))
178+
builder.WriteString(fmt.Sprintf("• 密钥:%s\n", maskKey(cfg.Key)))
179+
builder.WriteString(fmt.Sprintf("• 分隔符:%s\n", cfg.Separator))
180+
builder.WriteString(fmt.Sprintf("• 响应@:%s\n", yesNo(!cfg.NoReplyAT)))
181+
builder.WriteString(fmt.Sprintf("• 支持系统提示词:%s\n", yesNo(!cfg.NoSystemP)))
182+
builder.WriteString(fmt.Sprintf("• 以AI语音输出:%s\n", yesNo(!cfg.NoRecord)))
183+
return builder.String()
184+
}
185+
186+
func maskKey(key string) string {
187+
if len(key) <= 4 {
188+
return "****"
189+
}
190+
return key[:2] + strings.Repeat("*", len(key)-4) + key[len(key)-2:]
191+
}
192+
193+
func yesNo(b bool) string {
194+
if b {
195+
return "是"
196+
}
197+
return "否"
198+
}

plugin/aichat/main.go

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
zero "github.com/wdvxdr1123/ZeroBot"
1414
"github.com/wdvxdr1123/ZeroBot/message"
1515

16+
"github.com/FloatTech/AnimeAPI/airecord"
1617
"github.com/FloatTech/floatbox/process"
1718
ctrl "github.com/FloatTech/zbpctrl"
1819
"github.com/FloatTech/zbputils/chat"
@@ -29,7 +30,7 @@ var (
2930
"- 设置AI聊天温度80\n" +
3031
"- 设置AI聊天接口类型[OpenAI|OLLaMA|GenAI]\n" +
3132
"- 设置AI聊天(不)支持系统提示词\n" +
32-
"- 设置AI聊天接口地址https://xxx\n" +
33+
"- 设置AI聊天接口地址https://api.deepseek.com/chat/completions\n" +
3334
"- 设置AI聊天密钥xxx\n" +
3435
"- 设置AI聊天模型名xxx\n" +
3536
"- 查看AI聊天系统提示词\n" +
@@ -38,16 +39,21 @@ var (
3839
"- 设置AI聊天分隔符</think>(留空则清除)\n" +
3940
"- 设置AI聊天(不)响应AT\n" +
4041
"- 设置AI聊天最大长度4096\n" +
41-
"- 设置AI聊天TopP 0.9",
42+
"- 设置AI聊天TopP 0.9\n" +
43+
"- 设置AI聊天(不)以AI语音输出\n" +
44+
"- 查看AI聊天配置\n",
4245
PrivateDataFolder: "aichat",
4346
})
4447
)
4548

46-
var apitypes = map[string]uint8{
47-
"OpenAI": 0,
48-
"OLLaMA": 1,
49-
"GenAI": 2,
50-
}
49+
var (
50+
apitypes = map[string]uint8{
51+
"OpenAI": 0,
52+
"OLLaMA": 1,
53+
"GenAI": 2,
54+
}
55+
apilist = [3]string{"OpenAI", "OLLaMA", "GenAI"}
56+
)
5157

5258
func init() {
5359
en.OnMessage(ensureconfig, func(ctx *zero.Ctx) bool {
@@ -135,10 +141,20 @@ func init() {
135141
if t == "" {
136142
continue
137143
}
138-
if id != nil {
139-
id = ctx.SendChain(message.Reply(id), message.Text(t))
144+
logrus.Infoln("[aichat] 回复内容:", t)
145+
recCfg := airecord.GetConfig()
146+
record := ""
147+
if !cfg.NoRecord {
148+
record = ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, t)
149+
}
150+
if record != "" {
151+
ctx.SendChain(message.Record(record))
140152
} else {
141-
id = ctx.SendChain(message.Text(t))
153+
if id != nil {
154+
id = ctx.SendChain(message.Reply(id), message.Text(t))
155+
} else {
156+
id = ctx.SendChain(message.Text(t))
157+
}
142158
}
143159
process.SleepAbout1sTo2s()
144160
}
@@ -269,4 +285,24 @@ func init() {
269285
Handle(newextrasetuint(&cfg.MaxN))
270286
en.OnPrefix("设置AI聊天TopP", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
271287
Handle(newextrasetfloat32(&cfg.TopP))
288+
en.OnRegex("^设置AI聊天(不)?以AI语音输出$", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
289+
Handle(newextrasetbool(&cfg.NoRecord))
290+
en.OnFullMatch("查看AI聊天配置", ensureconfig, zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
291+
Handle(func(ctx *zero.Ctx) {
292+
c, ok := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
293+
if !ok {
294+
ctx.SendChain(message.Text("ERROR: no such plugin"))
295+
return
296+
}
297+
gid := ctx.Event.GroupID
298+
rate := c.GetData(gid) & 0xff
299+
temp := (c.GetData(gid) >> 8) & 0xff
300+
if temp <= 0 {
301+
temp = 70 // default setting
302+
}
303+
if temp > 100 {
304+
temp = 100
305+
}
306+
ctx.SendChain(message.Text(printConfig(rate, temp, cfg)))
307+
})
272308
}

plugin/airecord/record.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Package airecord 群应用:AI声聊
2+
package airecord
3+
4+
import (
5+
"strconv"
6+
"strings"
7+
"time"
8+
9+
"github.com/tidwall/gjson"
10+
11+
zero "github.com/wdvxdr1123/ZeroBot"
12+
"github.com/wdvxdr1123/ZeroBot/message"
13+
14+
"github.com/FloatTech/AnimeAPI/airecord"
15+
ctrl "github.com/FloatTech/zbpctrl"
16+
"github.com/FloatTech/zbputils/control"
17+
)
18+
19+
func init() {
20+
en := control.AutoRegister(&ctrl.Options[*zero.Ctx]{
21+
DisableOnDefault: false,
22+
Extra: control.ExtraFromString("airecord"),
23+
Brief: "群应用:AI声聊",
24+
Help: "- 设置AI语音群号1048452984(tips:机器人任意所在群聊即可)\n" +
25+
"- 设置AI语音模型\n" +
26+
"- 查看AI语音配置\n" +
27+
"- 发送AI语音xxx",
28+
PrivateDataFolder: "airecord",
29+
})
30+
31+
en.OnPrefix("设置AI语音群号", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
32+
Handle(func(ctx *zero.Ctx) {
33+
u := strings.TrimSpace(ctx.State["args"].(string))
34+
num, err := strconv.ParseInt(u, 10, 64)
35+
if err != nil {
36+
ctx.SendChain(message.Text("ERROR: parse gid err: ", err))
37+
return
38+
}
39+
err = airecord.SetCustomGID(num)
40+
if err != nil {
41+
ctx.SendChain(message.Text("ERROR: set gid err: ", err))
42+
return
43+
}
44+
ctx.SendChain(message.Text("设置AI语音群号为", num))
45+
})
46+
en.OnFullMatch("设置AI语音模型", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
47+
Handle(func(ctx *zero.Ctx) {
48+
next := zero.NewFutureEvent("message", 999, false, ctx.CheckSession())
49+
recv, cancel := next.Repeat()
50+
defer cancel()
51+
jsonData := ctx.GetAICharacters(0, 1)
52+
53+
// 转换为字符串数组
54+
var names []string
55+
// 初始化两个映射表
56+
nameToID := make(map[string]string)
57+
nameToURL := make(map[string]string)
58+
characters := jsonData.Get("#.characters")
59+
60+
// 遍历每个角色对象
61+
characters.ForEach(func(_, group gjson.Result) bool {
62+
group.ForEach(func(_, character gjson.Result) bool {
63+
// 提取当前角色的三个字段
64+
name := character.Get("character_name").String()
65+
names = append(names, name)
66+
// 存入映射表(重复名称会覆盖,保留最后出现的条目)
67+
nameToID[name] = character.Get("character_id").String()
68+
nameToURL[name] = character.Get("preview_url").String()
69+
return true // 继续遍历
70+
})
71+
return true // 继续遍历
72+
})
73+
var builder strings.Builder
74+
// 写入开头文本
75+
builder.WriteString("请选择语音模型序号:\n")
76+
77+
// 遍历names数组,拼接序号和名称
78+
for i, v := range names {
79+
// 将数字转换为字符串(不依赖fmt)
80+
numStr := strconv.Itoa(i)
81+
// 拼接格式:"序号. 名称\n"
82+
builder.WriteString(numStr)
83+
builder.WriteString(". ")
84+
builder.WriteString(v)
85+
builder.WriteString("\n")
86+
}
87+
// 获取最终字符串
88+
ctx.SendChain(message.Text(builder.String()))
89+
for {
90+
select {
91+
case <-time.After(time.Second * 120):
92+
ctx.SendChain(message.Text("设置AI语音模型指令过期"))
93+
return
94+
case ct := <-recv:
95+
msg := ct.Event.Message.ExtractPlainText()
96+
num, err := strconv.Atoi(msg)
97+
if err != nil {
98+
ctx.SendChain(message.Text("请输入数字!"))
99+
continue
100+
}
101+
if num < 0 || num >= len(names) {
102+
ctx.SendChain(message.Text("序号非法!"))
103+
continue
104+
}
105+
err = airecord.SetRecordModel(names[num], nameToID[names[num]])
106+
if err != nil {
107+
ctx.SendChain(message.Text("ERROR: set model err: ", err))
108+
continue
109+
}
110+
ctx.SendChain(message.Text("已选择语音模型: ", names[num]))
111+
ctx.SendChain(message.Record(nameToURL[names[num]]))
112+
return
113+
}
114+
}
115+
})
116+
en.OnFullMatch("查看AI语音配置", zero.OnlyPrivate, zero.SuperUserPermission).SetBlock(true).
117+
Handle(func(ctx *zero.Ctx) {
118+
ctx.SendChain(message.Text(airecord.PrintRecordConfig()))
119+
})
120+
en.OnPrefix("发送AI语音", zero.UserOrGrpAdmin).SetBlock(true).
121+
Handle(func(ctx *zero.Ctx) {
122+
u := strings.TrimSpace(ctx.State["args"].(string))
123+
recCfg := airecord.GetConfig()
124+
record := ctx.GetAIRecord(recCfg.ModelID, recCfg.Customgid, u)
125+
if record == "" {
126+
id := ctx.SendGroupAIRecord(recCfg.ModelID, ctx.Event.GroupID, u)
127+
if id == "" {
128+
ctx.SendChain(message.Text("ERROR: get record err: empty record"))
129+
return
130+
}
131+
}
132+
ctx.SendChain(message.Record(record))
133+
})
134+
}

0 commit comments

Comments
 (0)