diff --git a/README.md b/README.md index 0cf67195f6..61de32326f 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,24 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 设置默认限速为每 m [分钟 | 秒] n 次触发 + +
+ ai绘图 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aipaint"` + + - [x] [ ai绘图 | 生成色图 | 生成涩图 | ai画图 ] xxx + + - [x] [ 以图绘图 | 以图生图 | 以图画图 ] xxx [图片]|@xxx|[qq号] + + - [ ] 设置ai绘图配置 [server] [token] + + 例1: 设置ai绘图配置 http://91.216.169.75:5010 abc + + 例2: 设置ai绘图配置 http://91.217.139.190:5010 abc + + 通过 http://91.217.139.190:5010/token 获取token +
AIWife diff --git a/main.go b/main.go index 37c34f07ff..4d9758dd2a 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ahsai" // ahsai tts _ "github.com/FloatTech/ZeroBot-Plugin/plugin/ai_false" // 服务器监控 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aipaint" // ai绘图 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/alipayvoice" // 支付宝到账语音 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/b14" // base16384加解密 diff --git a/plugin/aipaint/aipaint.go b/plugin/aipaint/aipaint.go new file mode 100644 index 0000000000..38d2445dbd --- /dev/null +++ b/plugin/aipaint/aipaint.go @@ -0,0 +1,167 @@ +// Package aipaint ai绘图 +package aipaint + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "image" + "net/url" + "os" + "regexp" + "strconv" + "strings" + + "github.com/FloatTech/floatbox/binary" + "github.com/FloatTech/floatbox/file" + "github.com/FloatTech/floatbox/img/writer" + "github.com/FloatTech/floatbox/web" + 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" +) + +var ( + datapath string + predictRe = regexp.MustCompile(`{"steps".+?}`) + // 参考host http://91.217.139.190:5010 http://91.216.169.75:5010 + aipaintTxt2ImgURL = "/got_image?token=%v&tags=%v" + aipaintImg2ImgURL = "/got_image2image?token=%v&tags=%v" + cfg = newServerConfig("data/aipaint/config.json") +) + +type result struct { + Steps int `json:"steps"` + Sampler string `json:"sampler"` + Seed int `json:"seed"` + Strength float64 `json:"strength"` + Noise float64 `json:"noise"` + Scale float64 `json:"scale"` + Uc string `json:"uc"` +} + +func (r *result) String() string { + return fmt.Sprintf("steps: %v\nsampler: %v\nseed: %v\nstrength: %v\nnoise: %v\nscale: %v\nuc: %v\n", r.Steps, r.Sampler, r.Seed, r.Strength, r.Noise, r.Scale, r.Uc) +} + +func init() { // 插件主体 + engine := control.Register("aipaint", &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Help: "ai绘图\n" + + "- [ ai绘图 | 生成色图 | 生成涩图 | ai画图 ] xxx\n" + + "- [ 以图绘图 | 以图生图 | 以图画图 ] xxx [图片]|@xxx|[qq号]\n" + + "- 设置ai绘图配置 [server] [token]\n" + + "例1: 设置ai绘图配置 http://91.216.169.75:5010 abc\n" + + "例2: 设置ai绘图配置 http://91.217.139.190:5010 abc\n" + + "通过 http://91.217.139.190:5010/token 获取token", + PrivateDataFolder: "aipaint", + }) + datapath = file.BOTPATH + "/" + engine.DataFolder() + engine.OnPrefixGroup([]string{`ai绘图`, `生成色图`, `生成涩图`, `ai画图`}).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + server, token, err := cfg.load() + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + ctx.SendChain(message.Text("少女祈祷中...")) + args := ctx.State["args"].(string) + data, err := web.GetData(server + fmt.Sprintf(aipaintTxt2ImgURL, token, url.QueryEscape(strings.TrimSpace(strings.ReplaceAll(args, " ", "%20"))))) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + sendAiImg(ctx, data) + }) + engine.OnRegex(`^(以图绘图|以图生图|以图画图)[\s\S]*?(\[CQ:(image\,file=([0-9a-zA-Z]{32}).*|at.+?(\d{5,11}))\].*|(\d+))$`).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + server, token, err := cfg.load() + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + c := newContext(ctx.Event.UserID) + list := ctx.State["regex_matched"].([]string) + err = c.prepareLogos(list[4]+list[5]+list[6], strconv.FormatInt(ctx.Event.UserID, 10)) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + args := strings.TrimSuffix(strings.TrimPrefix(list[0], list[1]), list[2]) + if args == "" { + ctx.SendChain(message.Text("ERROR: 以图绘图必须添加tag")) + return + } + ctx.SendChain(message.Text("少女祈祷中...")) + postURL := server + fmt.Sprintf(aipaintImg2ImgURL, token, url.QueryEscape(strings.TrimSpace(strings.ReplaceAll(args, " ", "%20")))) + + f, err := os.Open(c.headimgsdir[0]) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + defer f.Close() + + img, _, err := image.Decode(f) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + imageShape := "" + switch { + case img.Bounds().Dx() > img.Bounds().Dy(): + imageShape = "Landscape" + case img.Bounds().Dx() == img.Bounds().Dy(): + imageShape = "Square" + default: + imageShape = "Portrait" + } + + // 图片转base64 + base64Bytes, err := writer.ToBase64(img) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + data, err := web.PostData(postURL+"&shape="+imageShape, "text/plain", bytes.NewReader(base64Bytes)) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + sendAiImg(ctx, data) + }) + engine.OnRegex(`^设置ai绘图配置\s(.*[^\s$])\s(.+)$`, zero.SuperUserPermission).SetBlock(true). + Handle(func(ctx *zero.Ctx) { + regexMatched := ctx.State["regex_matched"].([]string) + err := cfg.save(regexMatched[1], regexMatched[2]) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + ctx.SendChain(message.Text("成功设置server为", regexMatched[1], ", token为", regexMatched[2])) + }) +} + +func sendAiImg(ctx *zero.Ctx, data []byte) { + var loadData string + if predictRe.MatchString(binary.BytesToString(data)) { + loadData = predictRe.FindStringSubmatch(binary.BytesToString(data))[0] + } + var r result + if loadData != "" { + err := json.Unmarshal(binary.StringToBytes(loadData), &r) + if err != nil { + ctx.SendChain(message.Text("ERROR: ", err)) + return + } + } + encodeStr := base64.StdEncoding.EncodeToString(data) + m := message.Message{ctxext.FakeSenderForwardNode(ctx, message.Image("base64://"+encodeStr))} + m = append(m, ctxext.FakeSenderForwardNode(ctx, message.Text(r.String()))) + if id := ctx.Send(m).ID(); id == 0 { + ctx.SendChain(message.Text("ERROR: 可能被风控或下载图片用时过长,请耐心等待")) + } +} diff --git a/plugin/aipaint/config.go b/plugin/aipaint/config.go new file mode 100644 index 0000000000..445a0cec2f --- /dev/null +++ b/plugin/aipaint/config.go @@ -0,0 +1,54 @@ +package aipaint + +import ( + "encoding/json" + "errors" + "os" + + "github.com/FloatTech/floatbox/file" +) + +// 配置结构体 +type serverConfig struct { + BaseURL string `json:"base_url"` + Token string `json:"token"` + file string +} + +func newServerConfig(file string) *serverConfig { + return &serverConfig{ + file: file, + } +} + +func (cfg *serverConfig) save(baseURL, token string) (err error) { + cfg.BaseURL = baseURL + cfg.Token = token + reader, err := os.Create(cfg.file) + if err != nil { + return err + } + defer reader.Close() + return json.NewEncoder(reader).Encode(cfg) +} + +func (cfg *serverConfig) load() (aipaintServer, token string, err error) { + if cfg.BaseURL != "" && cfg.Token != "" { + aipaintServer = cfg.BaseURL + token = cfg.Token + return + } + if file.IsNotExist(cfg.file) { + err = errors.New("no server config") + return + } + reader, err := os.Open(cfg.file) + if err != nil { + return + } + defer reader.Close() + err = json.NewDecoder(reader).Decode(cfg) + aipaintServer = cfg.BaseURL + token = cfg.Token + return +} diff --git a/plugin/aipaint/context.go b/plugin/aipaint/context.go new file mode 100644 index 0000000000..30082e9c6e --- /dev/null +++ b/plugin/aipaint/context.go @@ -0,0 +1,39 @@ +package aipaint + +import ( + "os" + "strconv" + "strings" + + "github.com/FloatTech/floatbox/file" +) + +type context struct { + usrdir string + headimgsdir []string +} + +func newContext(user int64) *context { + c := new(context) + c.usrdir = datapath + "users/" + strconv.FormatInt(user, 10) + `/` + _ = os.MkdirAll(c.usrdir, 0755) + c.headimgsdir = make([]string, 2) + c.headimgsdir[0] = c.usrdir + "0.gif" + c.headimgsdir[1] = c.usrdir + "1.gif" + return c +} + +func (cc *context) prepareLogos(s ...string) error { + for i, v := range s { + _, err := strconv.Atoi(v) + if err != nil { + err = file.DownloadTo("https://gchat.qpic.cn/gchatpic_new//--"+strings.ToUpper(v)+"/0", cc.usrdir+strconv.Itoa(i)+".gif", true) + } else { + err = file.DownloadTo("http://q4.qlogo.cn/g?b=qq&nk="+v+"&s=640", cc.usrdir+strconv.Itoa(i)+".gif", true) + } + if err != nil { + return err + } + } + return nil +}