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)
+}