diff --git a/README.md b/README.md index 10ad0c47..6470528e 100644 --- a/README.md +++ b/README.md @@ -1083,6 +1083,26 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 来份网易云热评 + +
+ 百度文心AI画图 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenxinAI"` + + - 基于百度文心的免费AI画图插件(因为是免费的,图片质量你懂的) + + - key申请链接:https://wenxin.baidu.com/moduleApi/key + + - [x] 为[自己/本群/QQ号/群+群号]设置AI画图key [API Key] [Secret Key] + + - 例:“为10086设置AI画图key 123 456”;“为群10010设置AI画图key 789 101” + + - [x] [bot名称]画几张[图片描述]的[图片类型][图片尺寸] + + - 指令示例: + + - 椛椛帮我画几张金凤凰,背景绚烂,高饱和,古风,仙境,高清,4K,古风的油画方图 +
月幕galgame图 diff --git a/main.go b/main.go index c0bea4a5..50cc6c06 100644 --- a/main.go +++ b/main.go @@ -121,6 +121,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/translation" // 翻译 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/vtb_quotation" // vtb语录 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wangyiyun" // 网易云音乐热评 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wenxinAI" // 百度文心AI画图 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/word_count" // 聊天热词 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/wordle" // 猜单词 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ymgal" // 月幕galgame diff --git a/plugin/wenxinAI/ernievilg.go b/plugin/wenxinAI/ernievilg.go new file mode 100644 index 00000000..06c57241 --- /dev/null +++ b/plugin/wenxinAI/ernievilg.go @@ -0,0 +1,397 @@ +// Package ernie AI画图 +package ernie + +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/extension/single" + "github.com/wdvxdr1123/ZeroBot/message" + + // 数据库 + sql "github.com/FloatTech/sqlite" + // 百度文心AI画图API + wenxin "github.com/FloatTech/AnimeAPI/wenxinAI/ernievilg" +) + +const ( + serviceName = "AIdraw" + serviceErr = "[" + serviceName + "]ERROR:\n" +) + +type keydb struct { + db *sql.Sqlite + sync.RWMutex +} + +// 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 ( + groupinfo = &keydb{ + db: &sql.Sqlite{}, + } + limit = 50 + dtype = [...]string{ + "古风", "油画", "水彩画", "卡通画", "二次元", "浮世绘", "蒸汽波艺术", "low poly", "像素风格", "概念艺术", "未来主义", "赛博朋克", "写实风格", "洛丽塔风格", "巴洛克风格", "超现实主义", + } +) + +func init() { // 插件主体 + engine := control.Register(serviceName, &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Help: "AI画图\n" + + "基于百度文心的免费AI画图插件,\n因为是免费的,图片质量你懂的。\n" + + "key申请链接:https://wenxin.baidu.com/moduleApi/key\n" + + "注意:每个apikey每日上限50次,总上限500次请求。次数超过了请自行更新apikey\n" + + "- 为[自己/本群/QQ号/群+群号]设置AI画图key [API Key] [Secret Key]\n" + + "例:\n[为10086设置AI画图key 123 456]\n[为群10010设置AI画图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" + + "椛椛帮我画几张金凤凰,背景绚烂,高饱和,古风,仙境,高清,4K,古风的油画方图", + PrivateDataFolder: "ernievilg", + }).ApplySingle(single.New( + single.WithKeyFn(func(ctx *zero.Ctx) int64 { return ctx.Event.GroupID }), + single.WithPostFn[int64](func(ctx *zero.Ctx) { + ctx.Break() + ctx.Send( + message.ReplyWithMessage(ctx.Event.MessageID, + message.Text(zero.BotConfig.NickName[0], "正在给别人画图,请不要打扰哦"), + ), + ) + }), + )) + getdb := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { + groupinfo.db.DBPath = engine.DataFolder() + "keydb.db" + err := groupinfo.db.Open(time.Hour * 24) + 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 := groupinfo.checkGroup(uid) + info, err2 := groupinfo.checkGroup(gid) + 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" { + msg := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Text("我画好了!"))} + 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 = groupinfo.update(gid) + if err != nil { + ctx.SendChain(message.Text(serviceErr, err)) + } + process.SleepAbout1sTo2s() + ctx.SendChain(message.Text("累死了,今天我最多只能画", info.DayLimit-1, "张图哦")) + }) + engine.OnRegex(`^为(群)?(自己|本群|\d+)设置AI画图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 := groupinfo.insert(dbID, aKey, sKey) + if err != nil { + ctx.SendChain(message.Text(serviceErr, err)) + return + } + ctx.SendChain(message.Text("成功!")) + }) +} + +// 登记group的key +func (sql *keydb) insert(gid int64, 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 is "+strconv.FormatInt(gid, 10)) + if err != nil { + // 如果该group没有注册过 + err = sql.db.Find("groupinfo", &groupinfo, "where APIKey is '"+akey+"' and SecretKey is '"+skey+"'") + if err == nil { + // 如果key存在过将当前的数据迁移过去 + groupinfo.ID = gid + } else { + groupinfo = apikey{ + ID: gid, + APIKey: akey, + SecretKey: skey, + MaxLimit: 500, + } + } + return sql.db.Insert("groupinfo", &groupinfo) + } + // 进行更新 + groupinfo.APIKey = akey + groupinfo.SecretKey = skey + groupinfo.MaxLimit = 500 + return sql.db.Insert("groupinfo", &groupinfo) +} + +// 获取group信息 +func (sql *keydb) checkGroup(gid int64) (groupinfo apikey, err error) { + sql.Lock() + defer sql.Unlock() + // 给db文件创建表格(没有才创建),表格名称groupinfo,表格结构apikey + err = sql.db.Create("groupinfo", &apikey{}) + if err != nil { + return + } + // 先判断该群是否已经设置过key了 + if ok := sql.db.CanFind("groupinfo", "where ID is "+strconv.FormatInt(gid, 10)); !ok { + if gid > 0 { + err = errors.New("该群没有设置过apikey,请前往https://wenxin.baidu.com/moduleApi/key获取key值后,发送指令:\n为本群设置AI画图key [API Key] [Secret Key]") + } else { + err = errors.New("你没有设置过apikey,请前往https://wenxin.baidu.com/moduleApi/key获取key值后,发送指令:\n为自己设置AI画图key [API Key] [Secret Key]") + } + return + } + // 获取group信息 + err = sql.db.Find("groupinfo", &groupinfo, "where ID is "+strconv.FormatInt(gid, 10)) + 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 { + 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的他人次数 + condition := "where not ID is " + strconv.FormatInt(gid, 10) + + " and APIKey = '" + groupinfo.APIKey + + "' and SecretKey = '" + groupinfo.SecretKey + "'" + otherinfo := apikey{} + var groups []int64 // 将相同的key的ID暂存 + // 无视没有找到相同的key的err + _ = sql.db.FindFor("groupinfo", &otherinfo, condition, func() error { + groups = append(groups, otherinfo.ID) + return nil + }) + if len(groups) != 0 { // 如果有相同的key就更新 + for _, group := range groups { + err = sql.db.Find("groupinfo", &otherinfo, "where ID is "+strconv.FormatInt(group, 10)) + if err == nil { + otherinfo.Token = groupinfo.Token + otherinfo.Updatetime = groupinfo.Updatetime + err = sql.db.Insert("groupinfo", &otherinfo) + } + } + } + } + } + return +} + +// 记录次数(-1) +func (sql *keydb) update(gid int64) 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 is "+strconv.FormatInt(gid, 10)) + if err != nil { + return err + } + groupinfo.MaxLimit-- + groupinfo.DayLimit-- + err = sql.db.Insert("groupinfo", &groupinfo) + if err != nil { + return err + } + // 更新相同key的他人次数 + condition := "where not ID is " + strconv.FormatInt(gid, 10) + + " and APIKey = '" + groupinfo.APIKey + + "' and SecretKey = '" + groupinfo.SecretKey + "'" + otherinfo := apikey{} + var groups []int64 // 将相同的key的ID暂存 + // 无视没有找到相同的key的err + _ = sql.db.FindFor("groupinfo", &otherinfo, condition, func() error { + groups = append(groups, otherinfo.ID) + return nil + }) + if len(groups) != 0 { // 如果有相同的key就更新 + for _, group := range groups { + err = sql.db.Find("groupinfo", &otherinfo, "where ID is "+strconv.FormatInt(group, 10)) + if err == nil { + otherinfo.MaxLimit = groupinfo.MaxLimit + otherinfo.DayLimit = groupinfo.DayLimit + otherinfo.Lasttime = groupinfo.Lasttime + err = sql.db.Insert("groupinfo", &otherinfo) + } + } + } + return err +}