// Package wenxin 百度文心AI package wenxin import ( "errors" "strconv" "strings" "sync" "time" fcext "github.com/FloatTech/floatbox/ctxext" "github.com/FloatTech/floatbox/process" 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" // 数据库 sql "github.com/FloatTech/sqlite" // 百度文心大模型 model "github.com/FloatTech/AnimeAPI/wenxinAI/erniemodle" // 百度文心AI画图API wenxin "github.com/FloatTech/AnimeAPI/wenxinAI/ernievilg" ) const ( serviceErr = "[wenxinvilg]ERROR:\n" modelErr = "[wenxinmodel]ERROR:\n" ) type keydb struct { sync.RWMutex db sql.Sqlite } // db内容 type apikey struct { ID int64 // 群号 APIKey string // API Key SecretKey string // Secret Key Token string // AccessToken Updatetime int64 // token的有效时间 MaxLimit int // 总使用次数 DayLimit int // 当天的使用次数 Lasttime string // 记录使用的时间,用于刷新使用次数 } var ( name = "椛椛" limit int vilginfo keydb modelinfo keydb dtype = [...]string{ "古风", "油画", "水彩画", "卡通画", "二次元", "浮世绘", "蒸汽波艺术", "low poly", "像素风格", "概念艺术", "未来主义", "赛博朋克", "写实风格", "洛丽塔风格", "巴洛克风格", "超现实主义", } ) func init() { // 插件主体 go func() { process.GlobalInitMutex.Lock() defer process.GlobalInitMutex.Unlock() name = zero.BotConfig.NickName[0] }() engine := control.AutoRegister(&ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, Brief: "文心AI画图", Help: "基于百度文心的免费AI画图插件,\n因为是免费的,图片质量你懂的。\n" + "key申请链接:https://wenxin.baidu.com/moduleApi/key\n" + "key和erniemodel插件的key相同。\n" + "注意:每个apikey每日上限50次,总上限500次请求。次数超过了请自行更新apikey\n" + "- 为[自己/本群/QQ号/群+群号]设置画图key [API Key] [Secret Key]\n" + "例:\n为自己设置画图key 123 456\n为10086设置画图key 123 456\n为群10010设置画图key 789 101\n" + "- [bot名称]画几张[图片描述]的[图片类型][图片尺寸]\n" + "————————————————————\n" + "图片描述指南:\n图片主体,细节词(请用逗号连接)\n官方prompt指南:https://wenxin.baidu.com/wenxin/docs#Ol7ece95m\n" + "————————————————————\n" + "图片类型当前支持:" + strings.Join(dtype[:], "、") + "\n————————————————————\n" + "图片尺寸当前只支持:方图/长图/横图\n" + "————————————————————\n" + "指令示例:\n" + name + "帮我画几张金凤凰,背景绚烂,高饱和,古风,仙境,高清,4K,古风的油画方图", PrivateDataFolder: "wenxinAI", }).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) if err != nil { ctx.SendChain(message.Text(serviceErr, err)) return false } return true }) // 画图 engine.OnRegex(`画几张(.*[^的$])的(.*[^\s$])(方图|长图|横图)$`, zero.OnlyToMe, getdb).SetBlock(true). Handle(func(ctx *zero.Ctx) { uid := -ctx.Event.UserID gid := ctx.Event.GroupID // 获取个人和群的key userinfo, err1 := vilginfo.checkGroup(uid, "vilg") info, err2 := vilginfo.checkGroup(gid, "vilg") switch { // 如果是个人请求且报错 case gid == 0 && err1 != nil: ctx.SendChain(message.Text(serviceErr, err1)) return // 如果群报错而个人没有,就切换成个人的 case err2 != nil && err1 == nil: gid = uid info = userinfo // 如果都报错就以群为优先级 case err1 != nil && err2 != nil: ctx.SendChain(message.Text(serviceErr, err2)) return } // 判断使用次数 check := false switch { // 群和个人都没有次数了 case info.DayLimit == 0 && userinfo.DayLimit == 0: ctx.SendChain(message.Text("我已经画了", limit, "张了!我累了!不画不画,就不画!")) return // 个人还有次数的话 case info.DayLimit == 0 && userinfo.DayLimit != 0: check = true } switch { // 群和个人都没有总次数了 case info.MaxLimit == 0 && userinfo.MaxLimit == 0: ctx.SendChain(message.Text("设置的key使用次数超过了限额,请更换key。")) return // 个人还有总次数的话 case info.MaxLimit == 0 && userinfo.MaxLimit != 0: check = true } if check { // 如果只有个人有次数就切换回个人key gid = uid info = userinfo } // 创建任务 keyword := ctx.State["regex_matched"].([]string)[1] if len([]rune(keyword)) >= 64 { // 描述不能超过64个字 ctx.SendChain(message.Text("要求太多了啦!减少点!")) return } picType := ctx.State["regex_matched"].([]string)[2] chooseSize := ctx.State["regex_matched"].([]string)[3] wtime := 3 picSize := "1024*1024" switch chooseSize { case "长图": wtime = 5 picSize = "1024*1536" case "横图": wtime = 5 picSize = "1536*1024" } taskID, err := wenxin.BuildWork(info.Token, keyword, picType, picSize) if err != nil { ctx.SendChain(message.Text(serviceErr, err)) return } if taskID < 1 { ctx.SendChain(message.Text("要求太复杂力!想不出来...")) return } // 开始画图 ctx.SendChain(message.Text(zero.BotConfig.NickName[0], "知道了,我可能需要", time.Duration(wtime*10)*time.Second, "左右才能画好哦,请等待...")) i := 0 for range time.NewTicker(10 * time.Second).C { // 等待 wtime * 10秒 i++ if i <= wtime { continue } /* if i > 60{// 十分钟还不出图就放弃 ctx.SendChain(message.Text("呜呜呜,要求太复杂力!画不出来...")) return } // 获取结果*/ picURL, status, err := wenxin.GetPic(info.Token, taskID) if err != nil { ctx.SendChain(message.Text(serviceErr, err)) return } if status == "0" { lastTime := time.Duration(i * 10 * int(time.Second)) msg := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Text("我画好了!\n本次绘画用了", lastTime))} for _, imginfo := range picURL { msg = append(msg, ctxext.FakeSenderForwardNode(ctx, message.Image(imginfo.Image))) } if id := ctx.Send(msg).ID(); id == 0 { ctx.SendChain(message.Text("ERROR: 可能被风控了")) } break } } err = vilginfo.update(gid, 1) if err != nil { ctx.SendChain(message.Text(serviceErr, err)) } process.SleepAbout1sTo2s() ctx.SendChain(message.Text("累死了,今天我最多只能画", info.DayLimit-1, "张图哦")) }) engine.OnRegex(`^为(群)?(自己|本群|\d+)设置画图key\s(.*[^\s$])\s(.+)$`, getdb).SetBlock(true). Handle(func(ctx *zero.Ctx) { mode := ctx.State["regex_matched"].([]string)[1] user := ctx.State["regex_matched"].([]string)[2] aKey := ctx.State["regex_matched"].([]string)[3] sKey := ctx.State["regex_matched"].([]string)[4] dbID := -ctx.Event.UserID // 默认给自己 switch { case mode != "": // 指定群的话 gid, err := strconv.ParseInt(user, 10, 64) if err != nil { ctx.SendChain(message.Text(serviceErr, err)) return } dbID = gid case user == "本群": // 用于本群 gid := ctx.Event.GroupID if gid == 0 { ctx.SendChain(message.Text(serviceErr, "请指定群聊,或者使用指令;\n为群xxx设置AI画图key xxx xxx")) return } dbID = gid case user != "自己": // 给别人开key uid, err := strconv.ParseInt(user, 10, 64) if err != nil { ctx.SendChain(message.Text(serviceErr, err)) return } dbID = -uid } err := vilginfo.insert(dbID, "vilg", aKey, sKey) if err != nil { ctx.SendChain(message.Text(serviceErr, err)) return } ctx.SendChain(message.Text("成功!")) }) /*********************************************************/ en := control.Register("wenxinmodel", &ctrl.Options[*zero.Ctx]{ DisableOnDefault: false, Brief: "文心AI文本处理", Help: "基于百度文心AI的API文本处理\n" + "key申请链接:https://wenxin.baidu.com/moduleApi/key\n" + "key和ernievilg插件的key相同。\n" + "注意:每个apikey每日上限200条,总上限2000条。次数超过了请自行更新apikey\n" + "- 为[自己/本群/QQ号/群+群号]设置文心key [API Key] [Secret Key]\n" + "例:\n为自己设置文心key 123 456\n为10086设置文心key 123 456\n为群10010设置文心key 789 101\n" + "————————————————————\n" + "- 文心作文 (x字的)[作文题目]\n" + "————————————————————\n" + "- 文心提案 (x字的)[文案标题]\n" + "————————————————————\n" + "- 文心摘要 (x字的)[文章内容]\n" + "————————————————————\n" + "- 文心小说 (x字的)[小说上文]\n" + "————————————————————\n" + "- 文心对联 [上联]\n" + "————————————————————\n" + "- 文心问答 [问题]\n" + "————————————————————\n" + "- 文心补全 [带“_”的填空题]\n" + "————————————————————\n" + "- 文心自定义 [prompt]\n\n" + "prompt: [问题描述] [问题类型]:[题目] [解答类型]:[解题必带内容]\n" + "指令示例:\n" + "文心自定义 请写出下面这道题的解题过程。\\n题目:养殖场养鸭376只,养鸡的只数比鸭多258只,这个养殖场一共养鸭和鸡多少只?\\n解:\n\n" + "文心自定义 1+1=?\n" + "文心自定义 歌曲名:大风车转啊转\\n歌词:", }).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) if err != nil { ctx.SendChain(message.Text(modelErr, err)) return false } return true }) en.OnRegex(`^为(群)?(自己|本群|\d+)设置文心key\s(.*[^\s$])\s(.+)$`, getmodeldb).SetBlock(true). Handle(func(ctx *zero.Ctx) { mode := ctx.State["regex_matched"].([]string)[1] user := ctx.State["regex_matched"].([]string)[2] aKey := ctx.State["regex_matched"].([]string)[3] sKey := ctx.State["regex_matched"].([]string)[4] dbID := -ctx.Event.UserID // 默认给自己 switch { case mode != "": // 指定群的话 gid, err := strconv.ParseInt(user, 10, 64) if err != nil { ctx.SendChain(message.Text(modelErr, err)) return } dbID = gid case user == "本群": // 用于本群 gid := ctx.Event.GroupID if gid == 0 { ctx.SendChain(message.Text(modelErr, "请指定群聊,或者使用指令;\n为群xxx设置AI画图key xxx xxx")) return } dbID = gid case user != "自己": // 给别人开key uid, err := strconv.ParseInt(user, 10, 64) if err != nil { ctx.SendChain(message.Text(modelErr, err)) return } dbID = -uid } err := modelinfo.insert(dbID, "model", aKey, sKey) if err != nil { ctx.SendChain(message.Text(modelErr, err)) return } ctx.SendChain(message.Text("成功!")) }) var erniemodel = map[string]int{ "作文": 1, "提案": 2, "摘要": 3, "对联": 4, "问答": 5, "小说": 6, "补全": 7, "自定义": 8} var erniePrompt = map[string]string{ "作文": "zuowen", "提案": "adtext", "摘要": "Summarization", "对联": "couplet", "问答": "Dialogue", "小说": "novel", "补全": "cloze"} en.OnRegex(`^文心(作文|提案|摘要|小说)\s?((\d+)字的)?(.*)$`, getmodeldb).SetBlock(true). Handle(func(ctx *zero.Ctx) { uid := -ctx.Event.UserID gid := ctx.Event.GroupID // 获取个人和群的key userinfo, err1 := modelinfo.checkGroup(uid, "model") info, err2 := modelinfo.checkGroup(gid, "model") switch { // 如果是个人请求且报错 case gid == 0 && err1 != nil: ctx.SendChain(message.Text(modelErr, err1)) return // 如果群报错而个人没有,就切换成个人的 case err2 != nil && err1 == nil: gid = uid info = userinfo // 如果都报错就以群为优先级 case err1 != nil && err2 != nil: ctx.SendChain(message.Text(modelErr, err2)) return } // 判断使用次数 check := false switch { // 群和个人都没有次数了 case info.DayLimit == 0 && userinfo.DayLimit == 0: ctx.SendChain(message.Text("今日请求次数已到200次了,明天在玩吧")) return // 个人还有次数的话 case info.DayLimit == 0 && userinfo.DayLimit != 0: check = true } switch { // 群和个人都没有总次数了 case info.MaxLimit == 0 && userinfo.MaxLimit == 0: ctx.SendChain(message.Text("设置的key使用次数超过了限额,请更换key。")) return // 个人还有总次数的话 case info.MaxLimit == 0 && userinfo.MaxLimit != 0: check = true } if check { // 如果只有个人有次数就切换回个人key gid = uid info = userinfo } // 调用API modelStr := ctx.State["regex_matched"].([]string)[1] mun := ctx.State["regex_matched"].([]string)[3] minlen := 1 maxlen := 128 if mun != "" { maxNum, err := strconv.Atoi(mun) if err != nil { ctx.SendChain(message.Text(modelErr, err)) return } minlen = maxNum if maxNum > 128 { maxlen = maxNum } } keyword := ctx.State["regex_matched"].([]string)[4] if len([]rune(keyword)) >= 1000 { // 描述不能超过1000 ctx.SendChain(message.Text("是你写作文还是我写?减少点!")) return } result, err := model.GetResult(info.Token, erniemodel[modelStr], keyword, minlen, maxlen, erniePrompt[modelStr]) if err != nil { ctx.SendChain(message.Text(modelErr, err)) return } if id := ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(keyword, ",", result))); id.ID() == 0 { ctx.SendChain(message.Text("ERROR: 请求超时!")) } err = modelinfo.update(gid, 1) if err != nil { ctx.SendChain(message.Text(modelErr, err)) } }) en.OnRegex(`^文心(对联|问答|补全|自定义)\s?(.*)$`, getmodeldb).SetBlock(true). Handle(func(ctx *zero.Ctx) { uid := -ctx.Event.UserID gid := ctx.Event.GroupID // 获取个人和群的key userinfo, err1 := modelinfo.checkGroup(uid, "model") info, err2 := modelinfo.checkGroup(gid, "model") switch { // 如果是个人请求且报错 case gid == 0 && err1 != nil: ctx.SendChain(message.Text(modelErr, err1)) return // 如果群报错而个人没有,就切换成个人的 case err2 != nil && err1 == nil: gid = uid info = userinfo // 如果都报错就以群为优先级 case err1 != nil && err2 != nil: ctx.SendChain(message.Text(modelErr, err2)) return } // 判断使用次数 check := false switch { // 群和个人都没有次数了 case info.DayLimit == 0 && userinfo.DayLimit == 0: ctx.SendChain(message.Text("今日请求次数已到200次了,明天在玩吧")) return // 个人还有次数的话 case info.DayLimit == 0 && userinfo.DayLimit != 0: check = true } switch { // 群和个人都没有总次数了 case info.MaxLimit == 0 && userinfo.MaxLimit == 0: ctx.SendChain(message.Text("设置的key使用次数超过了限额,请更换key。")) return // 个人还有总次数的话 case info.MaxLimit == 0 && userinfo.MaxLimit != 0: check = true } if check { // 如果只有个人有次数就切换回个人key gid = uid info = userinfo } // 创建任务 modelStr := ctx.State["regex_matched"].([]string)[1] keyword := ctx.State["regex_matched"].([]string)[2] if len([]rune(keyword)) >= 1000 { // 描述不能超过1000 ctx.SendChain(message.Text("你在写作文吗?减少点!")) return } result, err := model.GetResult(info.Token, erniemodel[modelStr], keyword, 1, 128, erniePrompt[modelStr]) if err != nil { ctx.SendChain(message.Text(modelErr, err)) return } if id := ctx.Send(message.ReplyWithMessage(ctx.Event.MessageID, message.Text(result))); id.ID() == 0 { ctx.SendChain(message.Text("ERROR: 请求超时!")) } err = modelinfo.update(gid, 1) if err != nil { ctx.SendChain(message.Text(modelErr, err)) } }) } // 登记group的key func (sql *keydb) insert(gid int64, model, akey, skey string) error { sql.Lock() defer sql.Unlock() // 给db文件创建表格(没有才创建),表格名称groupinfo,表格结构apikey err := sql.db.Create("groupinfo", &apikey{}) if err != nil { return err } // 获取group信息 groupinfo := apikey{} // 用于暂存数据 err = sql.db.Find("groupinfo", &groupinfo, "WHERE ID = ?", gid) if err != nil { // 如果该group没有注册过 err = sql.db.Find("groupinfo", &groupinfo, "WHERE APIKey = ? and SecretKey = ?", akey, skey) if err == nil { // 如果key存在过将当前的数据迁移过去 groupinfo.ID = gid } else { groupinfo = apikey{ ID: gid, APIKey: akey, SecretKey: skey, } switch model { case "vilg": groupinfo.MaxLimit = 500 case "model": groupinfo.MaxLimit = 2000 } } return sql.db.Insert("groupinfo", &groupinfo) } // 进行更新 groupinfo.APIKey = akey groupinfo.SecretKey = skey groupinfo.Token = "" groupinfo.Updatetime = 0 switch model { case "vilg": groupinfo.MaxLimit = 500 case "model": groupinfo.MaxLimit = 2000 } return sql.db.Insert("groupinfo", &groupinfo) } // 获取group信息 func (sql *keydb) checkGroup(gid int64, model string) (groupinfo apikey, err error) { sql.Lock() defer sql.Unlock() // 给db文件创建表格(没有才创建),表格名称groupinfo,表格结构apikey err = sql.db.Create("groupinfo", &apikey{}) if err != nil { return } switch model { case "vilg": limit = 50 model = "画图" case "model": limit = 200 model = "文心" } // 先判断该群是否已经设置过key了 if ok := sql.db.CanFind("groupinfo", "WHERE ID = ?", gid); !ok { if gid > 0 { err = errors.New("该群没有设置过apikey,请前往https://wenxin.baidu.com/moduleApi/key获取key值后,发送指令:\n为本群设置" + model + "key [API Key] [Secret Key]\n或\n为自己设置" + model + "key [API Key] [Secret Key]") } else { err = errors.New("你没有设置过apikey,请前往https://wenxin.baidu.com/moduleApi/key获取key值后,发送指令:\n为自己设置" + model + "key [API Key] [Secret Key]") } return } // 获取group信息 err = sql.db.Find("groupinfo", &groupinfo, "WHERE ID = ?", gid) if err != nil { return } // 如果隔天使用刷新次数 if time.Now().Format("2006/01/02") != groupinfo.Lasttime { groupinfo.DayLimit = limit groupinfo.Lasttime = time.Now().Format("2006/01/02") } if err = sql.db.Insert("groupinfo", &groupinfo); err != nil { return } // 如果token有效期过期 if time.Since(time.Unix(groupinfo.Updatetime, 0)).Hours() > 24 || groupinfo.Token == "" { token, err1 := wenxin.GetToken(groupinfo.APIKey, groupinfo.SecretKey) if err1 != nil { err = err1 return } groupinfo.Token = token groupinfo.Updatetime = time.Now().Unix() err = sql.db.Insert("groupinfo", &groupinfo) if err == nil { // 更新相同key的他人次数 otherinfo := apikey{} var groups []int64 // 将相同的key的ID暂存 // 无视没有找到相同的key的err _ = sql.db.FindFor("groupinfo", &otherinfo, "WHERE ID <> ? AND APIKey = ? AND SecretKey = ?", func() error { groups = append(groups, otherinfo.ID) return nil }, gid, groupinfo.APIKey, groupinfo.SecretKey) if len(groups) != 0 { // 如果有相同的key就更新 for _, group := range groups { err = sql.db.Find("groupinfo", &otherinfo, "WHERE ID = ?", group) if err == nil { otherinfo.Token = groupinfo.Token otherinfo.Updatetime = groupinfo.Updatetime err = sql.db.Insert("groupinfo", &otherinfo) } } } } } return } // 记录次数(-sub) func (sql *keydb) update(gid int64, sub int) error { sql.Lock() defer sql.Unlock() // 给db文件创建表格(没有才创建),表格名称groupinfo,表格结构apikey err := sql.db.Create("groupinfo", &apikey{}) if err != nil { return err } groupinfo := apikey{} // 用于暂存数据 // 获取group信息 err = sql.db.Find("groupinfo", &groupinfo, "WHERE ID = ?", gid) if err != nil { return err } groupinfo.MaxLimit -= sub groupinfo.DayLimit -= sub err = sql.db.Insert("groupinfo", &groupinfo) if err != nil { return err } // 更新相同key的他人次数 otherinfo := apikey{} var groups []int64 // 将相同的key的ID暂存 // 无视没有找到相同的key的err _ = sql.db.FindFor("groupinfo", &otherinfo, "WHERE ID <> ? AND APIKey = ? AND SecretKey = ?", func() error { groups = append(groups, otherinfo.ID) return nil }, gid, groupinfo.APIKey, groupinfo.SecretKey) if len(groups) != 0 { // 如果有相同的key就更新 for _, group := range groups { err = sql.db.Find("groupinfo", &otherinfo, "WHERE ID = ?", group) if err == nil { otherinfo.MaxLimit = groupinfo.MaxLimit otherinfo.DayLimit = groupinfo.DayLimit otherinfo.Lasttime = groupinfo.Lasttime err = sql.db.Insert("groupinfo", &otherinfo) } } } return err }