From 7515983b555353d2048c0b1d3c3772cabbf2f87a Mon Sep 17 00:00:00 2001 From: yexiaoyu <54971249+yexiaoyu123@users.noreply.github.com> Date: Sun, 30 Mar 2025 22:48:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=EF=BC=9AAnimeTrace=20?= =?UTF-8?q?=E5=8A=A8=E7=94=BB/Galgame=E8=AF=86=E5=88=AB=20(#1141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 插件:AnimeTrace 动画/Galgame识别 * update: 插件:AnimeTrace 动画/Galgame识别 --- README.md | 12 ++++ main.go | 1 + plugin/animetrace/main.go | 145 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 plugin/animetrace/main.go diff --git a/README.md b/README.md index 34b79d22..7056ca96 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,18 @@ print("run[CQ:image,file="+j["img"]+"]") - [x] 支付宝到账 1 + +
+ AnimeTrace 动画/Galgame识别 + + `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace"` + + 基于[AnimeTrace](https://ai.animedb.cn/)API 的识图搜索插件 + + - [x] Gal识图 | Gal识图 [模型名] + + - [x] 动漫识图 | 动漫识图 2 | 动漫识图 [模型名] +
触发者撤回时也自动撤回 diff --git a/main.go b/main.go index 9a71aad9..34e7130c 100644 --- a/main.go +++ b/main.go @@ -67,6 +67,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aifalse" // 服务器监控 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/aiwife" // 随机老婆 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/alipayvoice" // 支付宝到账语音 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace" // AnimeTrace 动画/Galgame识别 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/autowithdraw" // 触发者撤回时也自动撤回 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/baiduaudit" // 百度内容审核 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/base16384" // base16384加解密 diff --git a/plugin/animetrace/main.go b/plugin/animetrace/main.go new file mode 100644 index 00000000..d980a256 --- /dev/null +++ b/plugin/animetrace/main.go @@ -0,0 +1,145 @@ +// Package animetrace AnimeTrace 动画/Galgame识别 +package animetrace + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "image" + "image/jpeg" + "mime/multipart" + "strings" + + "github.com/FloatTech/floatbox/web" + "github.com/FloatTech/imgfactory" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + "github.com/FloatTech/zbputils/ctxext" + "github.com/disintegration/imaging" + "github.com/tidwall/gjson" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +func init() { + engine := control.Register("animetrace", &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Brief: "AnimeTrace 动画/Galgame识别插件", + Help: "- Gal识图\n- 动漫识图\n- 动漫识图 2\n- 动漫识图 [模型名]\n- Gal识图 [模型名]", + }) + + engine.OnPrefix("gal识图", zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true).Handle(func(ctx *zero.Ctx) { + args := ctx.State["args"].(string) + var model string + switch strings.TrimSpace(args) { + case "": + model = "full_game_model_kira" // 默认使用的模型 + default: + model = args // 自定义设置模型 + } + processImageRecognition(ctx, model) + }) + + engine.OnPrefix("动漫识图", zero.OnlyGroup, zero.MustProvidePicture).SetBlock(true).Handle(func(ctx *zero.Ctx) { + args := ctx.State["args"].(string) + var model string + switch strings.TrimSpace(args) { + case "": + model = "anime_model_lovelive" + case "2": + model = "pre_stable" + default: + model = args + } + processImageRecognition(ctx, model) + }) +} + +// 处理图片识别 +func processImageRecognition(ctx *zero.Ctx, model string) { + urls := ctx.State["image_url"].([]string) + if len(urls) == 0 { + return + } + imageData, err := imgfactory.Load(urls[0]) + if err != nil { + ctx.Send(message.Text("下载图片失败: ", err)) + return + } + //ctx.Send(message.Text(model)) + respBody, err := createAndSendMultipartRequest("https://api.animetrace.com/v1/search", imageData, map[string]string{ + "is_multi": "0", + "model": model, + "ai_detect": "0", + }) + if err != nil { + ctx.Send(message.Text("识别请求失败: ", err)) + return + } + code := gjson.Get(string(respBody), "code").Int() + if code != 0 { + ctx.Send(message.Text("错误: ", gjson.Get(string(respBody), "zh_message").String())) + return + } + dataArray := gjson.Get(string(respBody), "data").Array() + if len(dataArray) == 0 { + ctx.Send(message.Text("未识别到任何角色")) + return + } + var sk message.Message + sk = append(sk, ctxext.FakeSenderForwardNode(ctx, message.Text("共识别到 ", len(dataArray), " 个角色,可能是以下来源"))) + for _, value := range dataArray { + boxArray := value.Get("box").Array() + imgWidth, imgHeight := imageData.Bounds().Dx(), imageData.Bounds().Dy() // 你可以从 `imageData.Bounds()` 获取 + box := []int{ + int(boxArray[0].Float() * float64(imgWidth)), + int(boxArray[1].Float() * float64(imgHeight)), + int(boxArray[2].Float() * float64(imgWidth)), + int(boxArray[3].Float() * float64(imgHeight)), + } + croppedImg := imaging.Crop(imageData, image.Rect(box[0], box[1], box[2], box[3])) + var buf bytes.Buffer + if err := imaging.Encode(&buf, croppedImg, imaging.JPEG, imaging.JPEGQuality(80)); err != nil { + ctx.Send(message.Text("图片编码失败: ", err)) + continue + } + + base64Str := base64.StdEncoding.EncodeToString(buf.Bytes()) + var sb strings.Builder + value.Get("character").ForEach(func(_, character gjson.Result) bool { + sb.WriteString(fmt.Sprintf("《%s》的角色 %s\n", character.Get("work").String(), character.Get("character").String())) + return true + }) + sk = append(sk, ctxext.FakeSenderForwardNode(ctx, message.Image("base64://"+base64Str), message.Text(sb.String()))) + } + ctx.SendGroupForwardMessage(ctx.Event.GroupID, sk) +} + +// 发送图片识别请求 +func createAndSendMultipartRequest(url string, img image.Image, formFields map[string]string) ([]byte, error) { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + // 直接编码图片 + part, err := writer.CreateFormFile("file", "image.jpg") + if err != nil { + return nil, errors.New("创建文件字段失败: " + err.Error()) + } + if err := jpeg.Encode(part, img, &jpeg.Options{Quality: 80}); err != nil { + return nil, errors.New("图片编码失败: " + err.Error()) + } + + // 写入其他字段 + for key, value := range formFields { + if err := writer.WriteField(key, value); err != nil { + return nil, errors.New("写入表单字段失败 (" + key + "): " + err.Error()) + } + } + + if err := writer.Close(); err != nil { + return nil, errors.New("关闭 multipart writer 失败: " + err.Error()) + } + + return web.PostData(url, writer.FormDataContentType(), body) +}