diff --git a/README.md b/README.md index 7f1c2adc..45ef2323 100644 --- a/README.md +++ b/README.md @@ -197,8 +197,9 @@ zerobot -h -t token -u url [-d|w] [-g 监听地址:端口] qq1 qq2 qq3 ... - [x] 搜卡[xxxx] - [x] [卡组代码xxx] - 注:更多搜卡指令参数:https://hs.fbigame.com/misc/searchhelp -- **青云客** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_qingyunke"` +- **人工智能回复** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_ai_reply"` - [x] @Bot 任意文本(任意一句话回复) + - [x] 设置回复模式[青云客|小爱] - **关键字搜图** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_image_finder"` - [x] 来张 [xxx] - **拼音首字母释义工具** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_nbnhhsh"` diff --git a/main.go b/main.go index 21f8c21a..1cce1304 100644 --- a/main.go +++ b/main.go @@ -13,9 +13,9 @@ import ( // webctrl "github.com/FloatTech/ZeroBot-Plugin/control/web" // web 后端控制 // 词库类 - _ "github.com/FloatTech/ZeroBot-Plugin/plugin_atri" // ATRI词库 - _ "github.com/FloatTech/ZeroBot-Plugin/plugin_chat" // 基础词库 - _ "github.com/FloatTech/ZeroBot-Plugin/plugin_qingyunke" // 青云客 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin_ai_reply" // 人工智能回复 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin_atri" // ATRI词库 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin_chat" // 基础词库 // 实用类 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_b14" // base16384加解密 diff --git a/plugin_ai_reply/ai_reply.go b/plugin_ai_reply/ai_reply.go new file mode 100644 index 00000000..b81241a0 --- /dev/null +++ b/plugin_ai_reply/ai_reply.go @@ -0,0 +1,147 @@ +// Package aireply 人工智能回复 +package aireply + +import ( + "errors" + "github.com/FloatTech/ZeroBot-Plugin/control" + log "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/extension/rate" + "github.com/wdvxdr1123/ZeroBot/message" + "math/rand" + "time" +) + +var ( + bucket = rate.NewManager(time.Minute, 20) // 青云客接口回复 + engine = control.Register(serviceName, &control.Options{ + DisableOnDefault: false, + Help: "人工智能回复\n" + + "- @Bot 任意文本(任意一句话回复)\n- 设置回复模式[青云客|小爱]\n- ", + }) + modeMap = map[string]int64{"青云客": 1, "小爱": 2} +) + +const ( + serviceName = "aireply" + qykURL = "http://api.qingyunke.com/api.php?key=free&appid=0&msg=%s" + qykBotName = "菲菲" + xiaoaiURL = "http://81.70.100.130/api/xiaoai.php?msg=%s&n=text" + xiaoaiBotName = "小爱" + prio = 256 +) + +// AIReply 公用智能回复类 +type AIReply interface { + // DealQuestion 把椛椛替换为各api接口的bot名字 + DealQuestion(preMsg string) (msg string) + // GetReply 取得回复消息 + GetReply(msg string) (reply string) + // DealReply 处理回复消息 + DealReply(reply string) (textReply string, faceReply int) +} + +// NewAIReply 智能回复简单工厂 +func NewAIReply(mode int64) AIReply { + if mode == 1 { + return &QYKReply{} + } else if mode == 2 { + return &XiaoAiReply{} + } + return &QYKReply{} +} + +func init() { // 插件主体 + // 回复 @和包括名字 + engine.OnMessage(zero.OnlyToMe).SetBlock(true).SetPriority(prio). + Handle(func(ctx *zero.Ctx) { + aireply := NewAIReply(GetReplyMode(ctx)) + if !bucket.Load(ctx.Event.UserID).Acquire() { + // 频繁触发,不回复 + return + } + msg := ctx.ExtractPlainText() + // 把消息里的椛椛替换成对应接口机器人的名字 + msg = aireply.DealQuestion(msg) + reply := aireply.GetReply(msg) + // 挑出 face 表情 + textReply, faceReply := aireply.DealReply(reply) + // 回复 + time.Sleep(time.Second * 1) + if ctx.Event.MessageType == "group" { + if faceReply != -1 { + ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(textReply), message.Face(faceReply)) + } else { + ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(textReply)) + } + } + if ctx.Event.MessageType == "private" { + if faceReply != -1 { + ctx.SendChain(message.Text(textReply), message.Face(faceReply)) + } else { + ctx.SendChain(message.Text(textReply)) + } + } + }) + engine.OnPrefix(`设置回复模式`).SetBlock(true).SetPriority(20). + Handle(func(ctx *zero.Ctx) { + param := ctx.State["args"].(string) + switch param { + case "青云客": + if err := setReplyMode(ctx, modeMap["青云客"]); err != nil { + log.Errorln("[aireply]:", err) + } + ctx.SendChain(message.Text("设置为青云客回复")) + case "小爱": + if err := setReplyMode(ctx, modeMap["小爱"]); err != nil { + log.Errorln("[aireply]:", err) + } + ctx.SendChain(message.Text("设置为小爱回复")) + default: + ctx.SendChain(message.Text("设置失败")) + } + }) +} + +func setReplyMode(ctx *zero.Ctx, mode int64) error { + gid := ctx.Event.GroupID + if gid == 0 { + gid = -ctx.Event.UserID + } + m, ok := control.Lookup(serviceName) + if ok { + return m.SetData(gid, mode) + } + return errors.New("no such plugin") +} + +// GetReplyMode 取得回复模式 +func GetReplyMode(ctx *zero.Ctx) (mode int64) { + gid := ctx.Event.GroupID + if gid == 0 { + gid = -ctx.Event.UserID + } + m, ok := control.Lookup(serviceName) + if ok { + mode = m.GetData(gid) + } + return mode +} + +func getAgent() string { + agent := [...]string{ + "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0", + "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11", + "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)", + "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", + "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)", + "User-Agent,Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", + "User-Agent, Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)", + "User-Agent,Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", + } + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + len1 := len(agent) + return agent[r.Intn(len1)] +} diff --git a/plugin_qingyunke/picture.go b/plugin_ai_reply/picture.go similarity index 98% rename from plugin_qingyunke/picture.go rename to plugin_ai_reply/picture.go index 37a50d7e..afe7d45c 100644 --- a/plugin_qingyunke/picture.go +++ b/plugin_ai_reply/picture.go @@ -1,4 +1,4 @@ -package qingyunke +package aireply // TODO: 待优化 diff --git a/plugin_ai_reply/qingyunke.go b/plugin_ai_reply/qingyunke.go new file mode 100644 index 00000000..6a616a4b --- /dev/null +++ b/plugin_ai_reply/qingyunke.go @@ -0,0 +1,68 @@ +package aireply + +import ( + "fmt" + log "github.com/sirupsen/logrus" + "github.com/tidwall/gjson" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/utils/helper" + "io/ioutil" + "net/http" + "net/url" + "regexp" + "strconv" + "strings" +) + +// QYKReply 青云客回复类 +type QYKReply struct{} + +// DealQuestion 把椛椛替换为菲菲 +func (*QYKReply) DealQuestion(preMsg string) (msg string) { + msg = strings.ReplaceAll(preMsg, zero.BotConfig.NickName[0], qykBotName) + return msg +} + +// GetReply 取得回复消息 +func (*QYKReply) GetReply(msg string) (reply string) { + u := fmt.Sprintf(qykURL, url.QueryEscape(msg)) + client := &http.Client{} + req, err := http.NewRequest("GET", u, nil) + if err != nil { + log.Errorln("[aireply-qingyunke]:", err) + return "" + } + // 自定义Header + req.Header.Set("User-Agent", getAgent()) + req.Header.Set("Connection", "keep-alive") + req.Header.Set("Host", "api.qingyunke.com") + resp, err := client.Do(req) + if err != nil { + log.Errorln("[aireply-qingyunke]:", err) + return + } + defer resp.Body.Close() + bytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Errorln("[aireply-qingyunke]:", err) + return + } + reply = gjson.Get(helper.BytesToString(bytes), "content").String() + log.Println("reply:", reply) + return +} + +// DealReply 处理回复消息 +func (*QYKReply) DealReply(reply string) (textReply string, faceReply int) { + reg := regexp.MustCompile(`\{face:(\d+)\}(.*)`) + faceReply = -1 + if reg.MatchString(reply) { + faceReply, _ = strconv.Atoi(reg.FindStringSubmatch(reply)[1]) + textReply = reg.FindStringSubmatch(reply)[2] + } else { + textReply = reply + } + textReply = strings.ReplaceAll(textReply, qykBotName, zero.BotConfig.NickName[0]) + textReply = strings.ReplaceAll(textReply, "{br}", "\n") + return +} diff --git a/plugin_ai_reply/xiaoai.go b/plugin_ai_reply/xiaoai.go new file mode 100644 index 00000000..896cd62d --- /dev/null +++ b/plugin_ai_reply/xiaoai.go @@ -0,0 +1,61 @@ +package aireply + +import ( + "fmt" + log "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/utils/helper" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// XiaoAiReply 小爱回复类 +type XiaoAiReply struct{} + +// DealQuestion 把椛椛替换为小爱 +func (*XiaoAiReply) DealQuestion(preMsg string) (msg string) { + msg = strings.ReplaceAll(preMsg, zero.BotConfig.NickName[0], xiaoaiBotName) + return msg +} + +// GetReply 取得回复消息 +func (*XiaoAiReply) GetReply(msg string) (reply string) { + u := fmt.Sprintf(xiaoaiURL, url.QueryEscape(msg)) + client := &http.Client{} + req, err := http.NewRequest("GET", u, nil) + if err != nil { + log.Errorln("[aireply-xiaoai]:", err) + return "" + } + // 自定义Header + req.Header.Set("User-Agent", getAgent()) + req.Header.Set("Connection", "keep-alive") + req.Header.Set("Host", "81.70.100.130") + resp, err := client.Do(req) + if err != nil { + log.Errorln("[aireply-xiaoai]:", err) + return + } + defer resp.Body.Close() + bytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Errorln("[aireply-xiaoai]:", err) + return + } + reply = helper.BytesToString(bytes) + log.Println("reply:", reply) + return +} + +// DealReply 处理回复消息 +func (*XiaoAiReply) DealReply(reply string) (textReply string, faceReply int) { + textReply = strings.ReplaceAll(reply, xiaoaiBotName, zero.BotConfig.NickName[0]) + if textReply == "" { + textReply = zero.BotConfig.NickName[0] + "听不懂你的话了,能再说一遍吗" + } + textReply = strings.ReplaceAll(textReply, "小米智能助理", "电子宠物") + faceReply = -1 + return +} diff --git a/plugin_mocking_bird/mocking_bird.go b/plugin_mocking_bird/mocking_bird.go index 917e0edd..f933fbe1 100644 --- a/plugin_mocking_bird/mocking_bird.go +++ b/plugin_mocking_bird/mocking_bird.go @@ -19,7 +19,7 @@ import ( "github.com/wdvxdr1123/ZeroBot/utils/helper" "github.com/FloatTech/ZeroBot-Plugin/control" - qingyunke "github.com/FloatTech/ZeroBot-Plugin/plugin_qingyunke" + aireply "github.com/FloatTech/ZeroBot-Plugin/plugin_ai_reply" fileutil "github.com/FloatTech/ZeroBot-Plugin/utils/file" "github.com/FloatTech/ZeroBot-Plugin/utils/web" ) @@ -47,14 +47,12 @@ func init() { engine.OnMessage(zero.OnlyToMe, getAcquire).SetBlock(true).SetPriority(prio). Handle(func(ctx *zero.Ctx) { msg := ctx.ExtractPlainText() - // 调用青云客接口 - reply, err := qingyunke.GetMessage(msg) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } + AIReply := aireply.NewAIReply(aireply.GetReplyMode(ctx)) + // 把消息里的椛椛替换成对应接口机器人的名字 + msg = AIReply.DealQuestion(msg) + reply := AIReply.GetReply(msg) // 挑出 face 表情 - textReply, _ := qingyunke.DealReply(reply) + textReply, _ := AIReply.DealReply(reply) // 拟声器生成音频 syntPath := getSyntPath() fileName := getWav(textReply, syntPath, vocoderList[1], ctx.Event.UserID) diff --git a/plugin_qingyunke/qingyunke.go b/plugin_qingyunke/qingyunke.go deleted file mode 100644 index a75011b6..00000000 --- a/plugin_qingyunke/qingyunke.go +++ /dev/null @@ -1,157 +0,0 @@ -// Package qingyunke 基于青云客接口的聊天对话功能 -package qingyunke - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "math/rand" - "net/http" - "net/url" - "regexp" - "strconv" - "strings" - "time" - - zero "github.com/wdvxdr1123/ZeroBot" - "github.com/wdvxdr1123/ZeroBot/extension/rate" - "github.com/wdvxdr1123/ZeroBot/message" - - "github.com/FloatTech/ZeroBot-Plugin/control" -) - -var ( - prio = 256 - bucket = rate.NewManager(time.Minute, 20) // 青云客接口回复 - engine *zero.Engine -) - -func init() { // 插件主体 - engine = control.Register("qingyunke", &control.Options{ - DisableOnDefault: false, - Help: "青云客\n" + - "- @Bot 任意文本(任意一句话回复)", - }) - // 回复 @和包括名字 - engine.OnMessage(zero.OnlyToMe).SetBlock(true).SetPriority(prio). - Handle(func(ctx *zero.Ctx) { - if !bucket.Load(ctx.Event.UserID).Acquire() { - // 频繁触发,不回复 - return - } - msg := ctx.ExtractPlainText() - // 调用青云客接口 - reply, err := GetMessage(msg) - if err != nil { - ctx.SendChain(message.Text("ERROR: ", err)) - return - } - // 挑出 face 表情 - textReply, faceReply := DealReply(reply) - // 回复 - time.Sleep(time.Second * 1) - if ctx.Event.MessageType == "group" { - if faceReply != -1 { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(textReply), message.Face(faceReply)) - } else { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Text(textReply)) - } - } - if ctx.Event.MessageType == "private" { - if faceReply != -1 { - ctx.SendChain(message.Text(textReply), message.Face(faceReply)) - } else { - ctx.SendChain(message.Text(textReply)) - } - } - }) - // TODO: 待优化 - /* - zero.OnRegex("CQ:image,file=|CQ:face,id=", zero.OnlyToMe, switchQYK()).SetBlock(false).SetPriority(prio). - Handle(func(ctx *zero.Ctx) { - imageURL := getPicture() - time.Sleep(time.Second * 1) - if ctx.Event.MessageType == "group" { - ctx.SendChain(message.Reply(ctx.Event.MessageID), message.Image(imageURL)) - } - if ctx.Event.MessageType == "private" { - ctx.SendChain(message.Image(imageURL)) - } - }) - */ -} - -// 青云客数据 -type dataQYK struct { - Result int `json:"result"` - Content string `json:"content"` -} - -const ( - qykURL = "http://api.qingyunke.com/api.php" - key = "free" - appid = "0" -) - -// GetMessage 青云客取消息 -func GetMessage(msg string) (string, error) { - u := fmt.Sprintf(qykURL+"?key=%s&appid=%s&msg=%s", key, appid, url.QueryEscape(msg)) - client := &http.Client{} - req, err := http.NewRequest("GET", u, nil) - if err != nil { - return "", err - } - // 自定义Header - req.Header.Set("User-Agent", getAgent()) - req.Header.Set("Connection", "keep-alive") - req.Header.Set("Host", "api.qingyunke.com") - resp, err := client.Do(req) - if err != nil { - return "", err - } - - defer resp.Body.Close() - bytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - fmt.Println(string(bytes)) - var dataQYK dataQYK - if err := json.Unmarshal(bytes, &dataQYK); err != nil { - return "", err - } - return dataQYK.Content, nil -} - -// DealReply 处理青云客返回文本 -func DealReply(reply string) (textReply string, faceReply int) { - reg := regexp.MustCompile(`\{face:(\d+)\}(.*)`) - faceReply = -1 - if reg.MatchString(reply) { - faceReply, _ = strconv.Atoi(reg.FindStringSubmatch(reply)[1]) - textReply = reg.FindStringSubmatch(reply)[2] - } else { - textReply = reply - } - textReply = strings.ReplaceAll(textReply, "菲菲", zero.BotConfig.NickName[0]) - textReply = strings.ReplaceAll(textReply, "{br}", "\n") - return -} - -func getAgent() string { - agent := [...]string{ - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0", - "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11", - "Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)", - "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)", - "User-Agent,Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", - "User-Agent, Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)", - "User-Agent,Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", - } - - r := rand.New(rand.NewSource(time.Now().UnixNano())) - len1 := len(agent) - return agent[r.Intn(len1)] -}