koishi-plugin-audiomeme 1.2.0 → 1.2.1
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/README.md +19 -16
- package/lib/index.js +71 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
# koishi-plugin-audiomeme
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
播放内置 JSON 数据库中的 meme 音效,并支持 ChatLuna 工具调用。
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
audiomeme
|
|
9
|
+
audiomeme <音效名>
|
|
10
|
+
audiomeme list [页码]
|
|
11
|
+
audiomeme list [关键词]
|
|
12
|
+
audiomeme list [页码] [关键词]
|
|
13
|
+
audiomeme random
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
17
|
-
- `
|
|
18
|
-
- `
|
|
16
|
+
- `audiomeme` 显示第一页可用音效。
|
|
17
|
+
- `audiomeme <音效名>` 下载并播放指定音效。
|
|
18
|
+
- `audiomeme list` 显示分页列表。
|
|
19
|
+
- `audiomeme list cat` 按音效名搜索。
|
|
20
|
+
- `audiomeme random` 随机播放一个音效。
|
|
21
|
+
- 兼容旧命令:`memeaudio`、`memeaudio.list`、`memeaudio.random`。
|
|
19
22
|
|
|
20
23
|
## ChatLuna Tool Calls
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
本插件可以选择接入 `chatluna_character`。
|
|
23
26
|
|
|
24
|
-
- `enableAudioMemeXmlTool
|
|
25
|
-
- `injectAudioMemeXmlToolAsReplyTool
|
|
27
|
+
- `enableAudioMemeXmlTool`:启用 ChatLuna 回复中的 XML 音效工具调用。
|
|
28
|
+
- `injectAudioMemeXmlToolAsReplyTool`:将同一个 XML 音效工具注入实验性“工具调用回复”参数中;可用时会关闭直接 XML 动作执行,避免重复播放。
|
|
26
29
|
|
|
27
|
-
XML
|
|
30
|
+
XML 示例:
|
|
28
31
|
|
|
29
32
|
```xml
|
|
30
33
|
<audiomeme name="bruh" />
|
|
@@ -32,13 +35,13 @@ XML examples:
|
|
|
32
35
|
<audio-meme key="cat-laugh-meme-1" />
|
|
33
36
|
```
|
|
34
37
|
|
|
35
|
-
Reply tool
|
|
38
|
+
Reply tool 字段名:
|
|
36
39
|
|
|
37
40
|
```text
|
|
38
41
|
audiomeme_play
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
Reply tool
|
|
44
|
+
Reply tool 参数示例:
|
|
42
45
|
|
|
43
46
|
```json
|
|
44
47
|
[{ "name": "bruh" }]
|
package/lib/index.js
CHANGED
|
@@ -16,13 +16,13 @@ exports.inject = {
|
|
|
16
16
|
optional: ['chatluna_character'],
|
|
17
17
|
};
|
|
18
18
|
exports.Config = koishi_1.Schema.object({
|
|
19
|
-
cachePath: koishi_1.Schema.string().default('cache/audiomeme').description('
|
|
20
|
-
cleanupInterval: koishi_1.Schema.number().default(10 * 60 * 1000).description('
|
|
21
|
-
cacheMaxAge: koishi_1.Schema.number().default(60 * 60 * 1000).description('
|
|
22
|
-
pageSize: koishi_1.Schema.number().min(5).max(50).default(20).description('
|
|
23
|
-
downloadTimeout: koishi_1.Schema.number().min(1000).default(30 * 1000).description('
|
|
24
|
-
enableAudioMemeXmlTool: koishi_1.Schema.boolean().default(false).description('
|
|
25
|
-
injectAudioMemeXmlToolAsReplyTool: koishi_1.Schema.boolean().default(false).description('
|
|
19
|
+
cachePath: koishi_1.Schema.string().default('cache/audiomeme').description('音频文件缓存目录。'),
|
|
20
|
+
cleanupInterval: koishi_1.Schema.number().default(10 * 60 * 1000).description('缓存清理间隔,单位为毫秒,默认 10 分钟。'),
|
|
21
|
+
cacheMaxAge: koishi_1.Schema.number().default(60 * 60 * 1000).description('缓存文件最长保留时间,单位为毫秒,默认 1 小时。'),
|
|
22
|
+
pageSize: koishi_1.Schema.number().min(5).max(50).default(20).description('列表每页显示的音效数量。'),
|
|
23
|
+
downloadTimeout: koishi_1.Schema.number().min(1000).default(30 * 1000).description('音频下载超时时间,单位为毫秒。'),
|
|
24
|
+
enableAudioMemeXmlTool: koishi_1.Schema.boolean().default(false).description('是否启用 ChatLuna 回复中的 XML 音效工具调用。'),
|
|
25
|
+
injectAudioMemeXmlToolAsReplyTool: koishi_1.Schema.boolean().default(false).description('是否将 XML 音效工具注入实验性“工具调用回复”参数中。'),
|
|
26
26
|
});
|
|
27
27
|
function matchSounds(sounds, keyword) {
|
|
28
28
|
if (!keyword)
|
|
@@ -30,26 +30,46 @@ function matchSounds(sounds, keyword) {
|
|
|
30
30
|
const normalizedKeyword = keyword.toLowerCase();
|
|
31
31
|
return sounds.filter(sound => sound.name.toLowerCase().includes(normalizedKeyword));
|
|
32
32
|
}
|
|
33
|
+
function parseListArgs(input) {
|
|
34
|
+
const normalizedInput = (input || '').trim();
|
|
35
|
+
if (!normalizedInput)
|
|
36
|
+
return { page: 1, keyword: undefined };
|
|
37
|
+
const [first, ...rest] = normalizedInput.split(/\s+/);
|
|
38
|
+
const page = Number(first);
|
|
39
|
+
if (Number.isInteger(page) && page > 0) {
|
|
40
|
+
return {
|
|
41
|
+
page,
|
|
42
|
+
keyword: rest.join(' ') || undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
page: 1,
|
|
47
|
+
keyword: normalizedInput,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function pickRandomSound(sounds) {
|
|
51
|
+
return sounds[Math.floor(Math.random() * sounds.length)];
|
|
52
|
+
}
|
|
33
53
|
function formatSoundList(sounds, page, pageSize, keyword) {
|
|
34
54
|
const totalPages = Math.max(1, Math.ceil(sounds.length / pageSize));
|
|
35
55
|
const currentPage = Math.min(Math.max(page, 1), totalPages);
|
|
36
56
|
const start = (currentPage - 1) * pageSize;
|
|
37
57
|
const pageSounds = sounds.slice(start, start + pageSize);
|
|
38
58
|
const title = keyword
|
|
39
|
-
?
|
|
40
|
-
:
|
|
59
|
+
? `匹配 "${keyword}" 的音效 (${sounds.length})`
|
|
60
|
+
: `可用音效 (${sounds.length})`;
|
|
41
61
|
if (!sounds.length) {
|
|
42
|
-
return
|
|
62
|
+
return `没有找到匹配 "${keyword}" 的音效。`;
|
|
43
63
|
}
|
|
44
64
|
return [
|
|
45
|
-
`${title} -
|
|
65
|
+
`${title} - 第 ${currentPage}/${totalPages} 页`,
|
|
46
66
|
...pageSounds.map((sound, index) => `${String(start + index + 1).padStart(3, ' ')}. ${sound.name}`),
|
|
47
67
|
'',
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
`播放:audiomeme <音效名>`,
|
|
69
|
+
`搜索:audiomeme list <关键词>`,
|
|
50
70
|
currentPage < totalPages
|
|
51
|
-
?
|
|
52
|
-
: '
|
|
71
|
+
? `下一页:audiomeme list ${currentPage + 1}${keyword ? ` ${keyword}` : ''}`
|
|
72
|
+
: '已经是最后一页。',
|
|
53
73
|
].join('\n');
|
|
54
74
|
}
|
|
55
75
|
function apply(ctx, config) {
|
|
@@ -64,8 +84,8 @@ function apply(ctx, config) {
|
|
|
64
84
|
if (!sound) {
|
|
65
85
|
const suggestions = matchSounds(sounds, name).slice(0, 5).map(s => s.name);
|
|
66
86
|
return suggestions.length
|
|
67
|
-
? [
|
|
68
|
-
:
|
|
87
|
+
? [`未找到音效:${name}`, '你可能想找:', ...suggestions.map(s => `- ${s}`)].join('\n')
|
|
88
|
+
: `未找到音效:${name}`;
|
|
69
89
|
}
|
|
70
90
|
const fileName = `${encodeURIComponent(sound.name)}.mp3`;
|
|
71
91
|
const filePath = path_1.default.join(cacheDir, fileName);
|
|
@@ -82,22 +102,43 @@ function apply(ctx, config) {
|
|
|
82
102
|
}
|
|
83
103
|
catch (error) {
|
|
84
104
|
logger.error(error);
|
|
85
|
-
return '
|
|
105
|
+
return '下载或播放音效失败。';
|
|
86
106
|
}
|
|
87
107
|
};
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!
|
|
91
|
-
return
|
|
92
|
-
return playSound(name);
|
|
108
|
+
const playRandomSound = async () => {
|
|
109
|
+
const sound = pickRandomSound(sounds);
|
|
110
|
+
if (!sound)
|
|
111
|
+
return '当前没有可用音效。';
|
|
112
|
+
return playSound(sound.name);
|
|
113
|
+
};
|
|
114
|
+
const listSounds = (input) => {
|
|
115
|
+
const { page, keyword } = parseListArgs(input);
|
|
116
|
+
const matchedSounds = matchSounds(sounds, keyword);
|
|
117
|
+
return formatSoundList(matchedSounds, page, config.pageSize, keyword);
|
|
118
|
+
};
|
|
119
|
+
ctx.command('audiomeme [action:text]', '播放或查看音效 meme')
|
|
120
|
+
.alias('memeaudio')
|
|
121
|
+
.action(async ({ session }, action) => {
|
|
122
|
+
const normalizedAction = (action || '').trim();
|
|
123
|
+
if (!normalizedAction)
|
|
124
|
+
return listSounds();
|
|
125
|
+
const [command, ...rest] = normalizedAction.split(/\s+/);
|
|
126
|
+
const commandArgs = rest.join(' ');
|
|
127
|
+
if (command === 'list')
|
|
128
|
+
return listSounds(commandArgs);
|
|
129
|
+
if (command === 'random')
|
|
130
|
+
return playRandomSound();
|
|
131
|
+
return playSound(normalizedAction);
|
|
132
|
+
});
|
|
133
|
+
ctx.command('audiomeme.list [query:text]', '查看音效 meme 列表')
|
|
134
|
+
.alias('memeaudio.list')
|
|
135
|
+
.action(({ session }, query) => {
|
|
136
|
+
return listSounds(query);
|
|
93
137
|
});
|
|
94
|
-
ctx.command('
|
|
95
|
-
.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const searchKeyword = hasPage ? keyword : [pageOrKeyword, keyword].filter(Boolean).join(' ');
|
|
99
|
-
const matchedSounds = matchSounds(sounds, searchKeyword);
|
|
100
|
-
return formatSoundList(matchedSounds, hasPage ? page : 1, config.pageSize, searchKeyword);
|
|
138
|
+
ctx.command('audiomeme.random', '随机播放音效 meme')
|
|
139
|
+
.alias('memeaudio.random')
|
|
140
|
+
.action(async () => {
|
|
141
|
+
return playRandomSound();
|
|
101
142
|
});
|
|
102
143
|
(0, chatluna_1.installChatlunaAudioMemeTools)({
|
|
103
144
|
ctx,
|