Skip to content

插件:AnimeTrace 动画/Galgame识别 #1141

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,18 @@ print("run[CQ:image,file="+j["img"]+"]")

- [x] 支付宝到账 1

</details>
<details>
<summary>AnimeTrace 动画/Galgame识别</summary>

`import _ "github.com/FloatTech/ZeroBot-Plugin/plugin/animetrace"`

基于[AnimeTrace](https://ai.animedb.cn/)API 的识图搜索插件

- [x] Gal识图 | Gal识图 [模型名]

- [x] 动漫识图 | 动漫识图 2 | 动漫识图 [模型名]

</details>
<details>
<summary>触发者撤回时也自动撤回</summary>
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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加解密
Expand Down
145 changes: 145 additions & 0 deletions plugin/animetrace/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading