kokkoro-plugin-animetrace 0.0.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/package.json +23 -6
- package/src/index.ts +31 -1
- package/src/service.ts +60 -0
- package/src/util.ts +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Yuki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1 +1,27 @@
|
|
|
1
1
|
# kokkoro-plugin-animetrace
|
|
2
|
+
|
|
3
|
+
使用 [AnimeTrace](https://www.animetrace.com/) 识别图片中的动漫、游戏角色及所属作品。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
bun add kokkoro-plugin-animetrace
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
发送「/搜角色」时附带图片,插件会识别消息中的第一张图片,并通过 QQ Markdown 展示结果。发送带图的「搜角色」也可以触发识别。
|
|
12
|
+
|
|
13
|
+
完整使用说明见 [AnimeTrace 角色识别](https://kokkoro.js.org/plugin/animetrace)。
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
通过 `service` 入口导入 `fetchCharacters()` 获取完整响应,通过 `util` 入口导入 `createMarkdown()` 生成 QQ Markdown:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { fetchCharacters } from 'kokkoro-plugin-animetrace/service';
|
|
21
|
+
import { createMarkdown } from 'kokkoro-plugin-animetrace/util';
|
|
22
|
+
|
|
23
|
+
const { data } = await fetchCharacters('https://kokkoro.js.org/logo.png');
|
|
24
|
+
const markdown = createMarkdown(data);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`service` 入口同时导出 `ANIMETRACE_API`、`AnimeTrace` 和 `CharacterResult`。参数、响应字段和错误行为见 [插件 API 文档](https://kokkoro.js.org/plugin/animetrace#api)。
|
package/package.json
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kokkoro-plugin-animetrace",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AnimeTrace 角色识别,查找图片中的动漫、游戏角色及所属作品。",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"animetrace",
|
|
7
|
+
"bot",
|
|
8
|
+
"kokkoro",
|
|
9
|
+
"qq"
|
|
10
|
+
],
|
|
11
|
+
"bugs": "https://github.com/kokkorojs/kokkoro/issues",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/kokkorojs/kokkoro.git",
|
|
15
|
+
"directory": "plugins/animetrace"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Yuki <admin@yuki.sh>",
|
|
5
19
|
"type": "module",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": "./src/index.ts",
|
|
22
|
+
"./service": "./src/service.ts",
|
|
23
|
+
"./util": "./src/util.ts"
|
|
24
|
+
},
|
|
6
25
|
"files": [
|
|
7
26
|
"src"
|
|
8
27
|
],
|
|
9
|
-
"devDependencies": {
|
|
10
|
-
"@types/bun": "latest"
|
|
11
|
-
},
|
|
12
28
|
"peerDependencies": {
|
|
13
|
-
"
|
|
29
|
+
"@kokkoro/core": "^3.1.4",
|
|
30
|
+
"typescript": "^6.0.3"
|
|
14
31
|
}
|
|
15
32
|
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { useCommand, useLogger } from '@kokkoro/core';
|
|
2
|
+
|
|
3
|
+
import { ANIMETRACE_API, fetchCharacters } from './service';
|
|
4
|
+
import { createMarkdown } from './util';
|
|
5
|
+
|
|
6
|
+
const logger = useLogger();
|
|
7
|
+
|
|
8
|
+
export default () => {
|
|
9
|
+
useCommand('/搜角色', async context => {
|
|
10
|
+
const image = context.attachments?.find(attachment => attachment.content_type?.startsWith('image/'));
|
|
11
|
+
|
|
12
|
+
if (!image?.url) {
|
|
13
|
+
throw new Error('请在指令中附带需要识别的图片');
|
|
14
|
+
}
|
|
15
|
+
logger.debug('发送 AnimeTrace 请求', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
url: ANIMETRACE_API,
|
|
18
|
+
payload: { url: image.url, is_multi: 1, ai_detect: 0 },
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const result = await fetchCharacters(image.url);
|
|
22
|
+
|
|
23
|
+
logger.debug('收到 AnimeTrace 响应', result);
|
|
24
|
+
logger.info('已识别图片中的角色', { count: result.data.length, trace_id: result.trace_id });
|
|
25
|
+
|
|
26
|
+
await context.reply({
|
|
27
|
+
msg_type: 2,
|
|
28
|
+
markdown: { content: createMarkdown(result.data) },
|
|
29
|
+
});
|
|
30
|
+
}).shortcut('搜角色');
|
|
31
|
+
};
|
package/src/service.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** AnimeTrace v1 识别接口的请求地址。 */
|
|
2
|
+
export const ANIMETRACE_API = 'https://api.animetrace.com/v1/search';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AnimeTrace 识别响应。
|
|
6
|
+
*
|
|
7
|
+
* @see https://www.animetrace.com/api-docs/
|
|
8
|
+
*/
|
|
9
|
+
export interface AnimeTrace {
|
|
10
|
+
/** 状态码,`0` 表示成功。 */
|
|
11
|
+
readonly code: number;
|
|
12
|
+
/** 是否判定为 AI 生成图片。 */
|
|
13
|
+
readonly ai: boolean;
|
|
14
|
+
/** 本次识别的唯一 ID。 */
|
|
15
|
+
readonly trace_id: string;
|
|
16
|
+
/** 图片中各个人物的识别结果。 */
|
|
17
|
+
readonly data: readonly CharacterResult[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** AnimeTrace 返回的单个人物识别结果。 */
|
|
21
|
+
export interface CharacterResult {
|
|
22
|
+
/** 人物位置 `[x1, y1, x2, y2]`,坐标相对于图片宽高,范围为 0 到 1。 */
|
|
23
|
+
readonly box: readonly [number, number, number, number];
|
|
24
|
+
/** 检测框的唯一 ID。 */
|
|
25
|
+
readonly box_id: string;
|
|
26
|
+
/** 是否置信度较低,需要人工确认。 */
|
|
27
|
+
readonly not_confident: boolean;
|
|
28
|
+
/** 候选角色,越靠前可能性越大。 */
|
|
29
|
+
readonly character: readonly {
|
|
30
|
+
/** 作品名称。 */
|
|
31
|
+
readonly work: string;
|
|
32
|
+
/** 角色名称。 */
|
|
33
|
+
readonly character: string;
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 使用 AnimeTrace 默认模型识别图片中的角色,并返回完整的接口响应。 */
|
|
38
|
+
export async function fetchCharacters(url: string): Promise<AnimeTrace> {
|
|
39
|
+
const form = new FormData();
|
|
40
|
+
|
|
41
|
+
form.set('url', url);
|
|
42
|
+
form.set('is_multi', '1');
|
|
43
|
+
form.set('ai_detect', '0');
|
|
44
|
+
|
|
45
|
+
const response = await fetch(ANIMETRACE_API, { method: 'POST', body: form });
|
|
46
|
+
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
throw new Error(`接口请求失败,状态码 ${response.status}`);
|
|
49
|
+
}
|
|
50
|
+
const result = <AnimeTrace>await response.json();
|
|
51
|
+
|
|
52
|
+
if (result.code !== 0) {
|
|
53
|
+
throw new Error(`AnimeTrace 识别失败,状态码 ${result.code}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (result.data.length === 0) {
|
|
57
|
+
throw new Error('没有识别到角色');
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CharacterResult } from './service';
|
|
2
|
+
|
|
3
|
+
/** 将 AnimeTrace 识别结果转换为 QQ Markdown。 */
|
|
4
|
+
export function createMarkdown(results: readonly CharacterResult[]): string {
|
|
5
|
+
const lines = ['## AnimeTrace 搜索结果'];
|
|
6
|
+
|
|
7
|
+
for (const [index, { character: candidates, not_confident: isNotConfident }] of results.entries()) {
|
|
8
|
+
if (results.length > 1) {
|
|
9
|
+
lines.push(`**人物 ${index + 1}**`);
|
|
10
|
+
}
|
|
11
|
+
lines.push(candidates.map(({ character, work }) => `- **${character}** \n ${work}`).join('\n'));
|
|
12
|
+
|
|
13
|
+
if (isNotConfident) {
|
|
14
|
+
lines.push('> 识别置信度较低,请结合原图确认。');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return lines.join('\n\n');
|
|
18
|
+
}
|