koishi-plugin-music-to-voice 1.0.0 → 1.0.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/dist/index.d.mts +25 -14
- package/dist/index.d.ts +25 -14
- package/dist/index.js +311 -322
- package/dist/index.mjs +309 -322
- package/package.json +5 -3
- package/src/index.ts +384 -440
package/dist/index.js
CHANGED
|
@@ -32,372 +32,361 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
Config: () => Config,
|
|
34
34
|
apply: () => apply,
|
|
35
|
-
|
|
35
|
+
inject: () => inject,
|
|
36
|
+
name: () => name,
|
|
37
|
+
usage: () => usage
|
|
36
38
|
});
|
|
37
39
|
module.exports = __toCommonJS(index_exports);
|
|
38
40
|
var import_koishi = require("koishi");
|
|
39
|
-
var import_axios = __toESM(require("axios"));
|
|
40
|
-
var import_node_fs = __toESM(require("fs"));
|
|
41
|
-
var import_node_path = __toESM(require("path"));
|
|
42
41
|
var import_node_os = __toESM(require("os"));
|
|
42
|
+
var import_node_fs = __toESM(require("fs"));
|
|
43
43
|
var import_node_crypto = __toESM(require("crypto"));
|
|
44
|
-
var
|
|
44
|
+
var import_node_path = __toESM(require("path"));
|
|
45
|
+
var import_node_url = require("url");
|
|
46
|
+
var name = "music-to-voice";
|
|
45
47
|
var logger = new import_koishi.Logger(name);
|
|
46
|
-
var
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
48
|
+
var inject = {
|
|
49
|
+
required: ["http", "i18n"],
|
|
50
|
+
optional: ["puppeteer", "downloads", "ffmpeg", "silk"]
|
|
51
|
+
};
|
|
52
|
+
var usage = `
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
\u672C\u63D2\u4EF6\u63D0\u4F9B\u201C\u70B9\u6B4C \u2192 \u5217\u8868\u9009\u5E8F\u53F7 \u2192 \u53D1\u9001\u8BED\u97F3/\u97F3\u9891/\u6587\u4EF6\u201D\u7684\u97F3\u4E50\u805A\u5408\u80FD\u529B\u3002
|
|
56
|
+
|
|
57
|
+
\u6570\u636E\u6765\u6E90\uFF1A**GD\u97F3\u4E50\u53F0 API**\uFF08https://music-api.gdstudio.xyz/api.php\uFF09
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## \u5F00\u542F\u63D2\u4EF6\u524D\uFF0C\u8BF7\u786E\u4FDD\u4EE5\u4E0B\u670D\u52A1\u5DF2\u7ECF\u542F\u7528\uFF08\u53EF\u9009\u5B89\u88C5\uFF09
|
|
62
|
+
|
|
63
|
+
\u6240\u9700\u670D\u52A1\uFF1A
|
|
64
|
+
|
|
65
|
+
- puppeteer \u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF0C\u7528\u4E8E\u56FE\u7247\u6B4C\u5355\uFF09
|
|
66
|
+
|
|
67
|
+
\u6B64\u5916\u53EF\u80FD\u8FD8\u9700\u8981\u8FD9\u4E9B\u670D\u52A1\u624D\u80FD\u53D1\u9001\u8BED\u97F3\uFF08\u6700\u7A33\uFF1A\u4E0B\u8F7D + \u8F6C\u7801\uFF09\uFF1A
|
|
68
|
+
|
|
69
|
+
- downloads \u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF09
|
|
70
|
+
- ffmpeg \u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF0C\u53EF\u80FD\u4F9D\u8D56 downloads\uFF09
|
|
71
|
+
- silk \u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF09
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
`;
|
|
75
|
+
var Config = import_koishi.Schema.intersect([
|
|
76
|
+
import_koishi.Schema.object({
|
|
77
|
+
commandName: import_koishi.Schema.string().default("\u542C\u6B4C").description("\u6307\u4EE4\u540D\u79F0"),
|
|
78
|
+
commandAlias: import_koishi.Schema.string().default("music2").description("\u6307\u4EE4\u522B\u540D\uFF08\u907F\u514D\u547D\u4EE4\u51B2\u7A81\uFF09")
|
|
79
|
+
}).description("\u57FA\u7840\u8BBE\u7F6E"),
|
|
80
|
+
import_koishi.Schema.object({
|
|
81
|
+
apiBase: import_koishi.Schema.string().default("https://music-api.gdstudio.xyz/api.php").description("GD\u97F3\u4E50\u53F0 API \u5730\u5740"),
|
|
82
|
+
source: import_koishi.Schema.union([
|
|
83
|
+
import_koishi.Schema.const("netease").description("\u6E901"),
|
|
84
|
+
import_koishi.Schema.const("tencent").description("\u6E902"),
|
|
85
|
+
import_koishi.Schema.const("kugou").description("\u6E903"),
|
|
86
|
+
import_koishi.Schema.const("kuwo").description("\u6E904"),
|
|
87
|
+
import_koishi.Schema.const("migu").description("\u6E905")
|
|
88
|
+
]).default("kuwo").description("\u97F3\u6E90\uFF08\u4E0B\u62C9\u9009\u62E9\uFF09"),
|
|
89
|
+
searchListCount: import_koishi.Schema.natural().min(5).max(50).default(20).description("\u6BCF\u9875\u663E\u793A\u6761\u6570")
|
|
90
|
+
}).description("API \u8BBE\u7F6E"),
|
|
91
|
+
import_koishi.Schema.object({
|
|
92
|
+
imageMode: import_koishi.Schema.boolean().default(false).description("\u56FE\u7247\u6B4C\u5355\u6A21\u5F0F\uFF08\u9700\u8981 puppeteer\uFF0C\u53EF\u9009\u5B89\u88C5\uFF09"),
|
|
93
|
+
textColor: import_koishi.Schema.string().role("color").default("rgba(255,255,255,1)").description("\u56FE\u7247\u6B4C\u5355\u6587\u5B57\u989C\u8272"),
|
|
94
|
+
backgroundColor: import_koishi.Schema.string().role("color").default("rgba(0,0,0,1)").description("\u56FE\u7247\u6B4C\u5355\u80CC\u666F\u989C\u8272")
|
|
95
|
+
}).description("\u6B4C\u5355\u6837\u5F0F"),
|
|
96
|
+
import_koishi.Schema.object({
|
|
97
|
+
nextPageCommand: import_koishi.Schema.string().default("\u4E0B\u4E00\u9875").description("\u4E0B\u4E00\u9875\u6307\u4EE4"),
|
|
98
|
+
prevPageCommand: import_koishi.Schema.string().default("\u4E0A\u4E00\u9875").description("\u4E0A\u4E00\u9875\u6307\u4EE4"),
|
|
99
|
+
exitCommandList: import_koishi.Schema.array(import_koishi.Schema.string()).default(["0", "\u9000\u51FA", "\u4E0D\u542C\u4E86"]).description("\u9000\u51FA\u6307\u4EE4\u5217\u8868"),
|
|
100
|
+
menuExitCommandTip: import_koishi.Schema.boolean().default(false).description("\u662F\u5426\u5728\u6B4C\u5355\u5C3E\u90E8\u63D0\u793A\u9000\u51FA\u6307\u4EE4"),
|
|
101
|
+
waitForTimeout: import_koishi.Schema.natural().min(5).max(180).default(45).description("\u7B49\u5F85\u8F93\u5165\u5E8F\u53F7\u8D85\u65F6\uFF08\u79D2\uFF09"),
|
|
102
|
+
generationTip: import_koishi.Schema.string().default("\u751F\u6210\u8BED\u97F3\u4E2D\u2026").description("\u751F\u6210\u63D0\u793A\u6587\u672C")
|
|
103
|
+
}).description("\u4EA4\u4E92\u8BBE\u7F6E"),
|
|
104
|
+
import_koishi.Schema.object({
|
|
105
|
+
sendAs: import_koishi.Schema.union([
|
|
106
|
+
import_koishi.Schema.const("record").description("\u8BED\u97F3 record"),
|
|
107
|
+
import_koishi.Schema.const("audio").description("\u97F3\u9891 audio"),
|
|
108
|
+
import_koishi.Schema.const("file").description("\u6587\u4EF6 file")
|
|
109
|
+
]).default("record").description("\u53D1\u9001\u7C7B\u578B"),
|
|
110
|
+
forceTranscode: import_koishi.Schema.boolean().default(true).description("\u6700\u7A33\u6A21\u5F0F\uFF1A\u4E0B\u8F7D + ffmpeg + silk \u8F6C\u7801\uFF08\u53EF\u9009\u4F9D\u8D56\uFF0C\u7F3A\u4F9D\u8D56\u81EA\u52A8\u964D\u7EA7\u76F4\u94FE\uFF09")
|
|
111
|
+
}).description("\u53D1\u9001\u8BBE\u7F6E"),
|
|
112
|
+
import_koishi.Schema.object({
|
|
113
|
+
recallMenu: import_koishi.Schema.boolean().default(true).description("\u64A4\u56DE\u6B4C\u5355\u6D88\u606F"),
|
|
114
|
+
recallGeneratingTip: import_koishi.Schema.boolean().default(true).description("\u64A4\u56DE\u751F\u6210\u63D0\u793A\u6D88\u606F"),
|
|
115
|
+
recallSongMessage: import_koishi.Schema.boolean().default(false).description("\u64A4\u56DE\u6B4C\u66F2\u6D88\u606F\uFF08\u8BED\u97F3/\u97F3\u9891/\u6587\u4EF6\uFF09"),
|
|
116
|
+
recallTimeoutTip: import_koishi.Schema.boolean().default(true).description("\u64A4\u56DE\u8D85\u65F6\u63D0\u793A"),
|
|
117
|
+
recallExitTip: import_koishi.Schema.boolean().default(false).description("\u64A4\u56DE\u9000\u51FA\u63D0\u793A"),
|
|
118
|
+
recallInvalidTip: import_koishi.Schema.boolean().default(false).description("\u64A4\u56DE\u5E8F\u53F7\u9519\u8BEF\u63D0\u793A"),
|
|
119
|
+
recallFetchFailTip: import_koishi.Schema.boolean().default(true).description("\u64A4\u56DE\u83B7\u53D6\u5931\u8D25\u63D0\u793A")
|
|
120
|
+
}).description("\u64A4\u56DE\u8BBE\u7F6E"),
|
|
121
|
+
import_koishi.Schema.object({
|
|
122
|
+
enableRateLimit: import_koishi.Schema.boolean().default(false).description("\u662F\u5426\u5F00\u542F\u9891\u7387\u9650\u5236"),
|
|
123
|
+
rateLimitScope: import_koishi.Schema.union([
|
|
124
|
+
import_koishi.Schema.const("user").description("\u6309\u7528\u6237"),
|
|
125
|
+
import_koishi.Schema.const("channel").description("\u6309\u9891\u9053"),
|
|
126
|
+
import_koishi.Schema.const("platform").description("\u6309\u5E73\u53F0")
|
|
127
|
+
]).default("user").description("\u9650\u5236\u8303\u56F4"),
|
|
128
|
+
rateLimitIntervalSec: import_koishi.Schema.natural().min(1).max(3600).default(60).description("\u95F4\u9694\u79D2\u6570")
|
|
129
|
+
}).description("\u9891\u7387\u9650\u5236")
|
|
130
|
+
]);
|
|
131
|
+
function artistsToString(x) {
|
|
132
|
+
if (!x) return "";
|
|
133
|
+
if (Array.isArray(x)) return x.join(" / ");
|
|
134
|
+
return String(x);
|
|
100
135
|
}
|
|
101
|
-
async function
|
|
136
|
+
async function safeDelete(session, messageId) {
|
|
102
137
|
if (!messageId) return;
|
|
138
|
+
const channelId = session.channelId;
|
|
139
|
+
if (!channelId) return;
|
|
103
140
|
try {
|
|
104
|
-
await session.bot.deleteMessage(
|
|
141
|
+
await session.bot.deleteMessage(channelId, messageId);
|
|
105
142
|
} catch {
|
|
106
143
|
}
|
|
107
144
|
}
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return (0, import_koishi.h)("audio", { src });
|
|
113
|
-
}
|
|
114
|
-
function buildMenuText(config, keyword, list, page) {
|
|
115
|
-
const lines = [];
|
|
116
|
-
lines.push(`NetEase Music:`);
|
|
117
|
-
lines.push(`\u5173\u952E\u8BCD\uFF1A${keyword}`);
|
|
118
|
-
lines.push(`\u97F3\u6E90\uFF1A${config.source} \u7B2C ${page} \u9875`);
|
|
119
|
-
lines.push("");
|
|
120
|
-
for (let i = 0; i < list.length; i++) {
|
|
121
|
-
const it = list[i];
|
|
122
|
-
const meta = [it.artist, it.album].filter(Boolean).join(" - ");
|
|
123
|
-
lines.push(`${i + 1}. ${it.name}${meta ? ` -- ${meta}` : ""}`);
|
|
124
|
-
}
|
|
125
|
-
lines.push("");
|
|
126
|
-
lines.push(`\u8BF7\u5728 ${config.waitForTimeout} \u79D2\u5185\u8F93\u5165\u5E8F\u53F7\uFF081-${list.length}\uFF09`);
|
|
127
|
-
lines.push(`\u7FFB\u9875\uFF1A${config.prevPageCommand} / ${config.nextPageCommand}`);
|
|
128
|
-
if (config.menuExitCommandTip) {
|
|
129
|
-
lines.push(`\u9000\u51FA\uFF1A${config.exitCommandList.join(" / ")}`);
|
|
130
|
-
}
|
|
131
|
-
lines.push("");
|
|
132
|
-
lines.push("\u6570\u636E\u6765\u6E90\uFF1AGD\u97F3\u4E50\u53F0 API");
|
|
133
|
-
return lines.join("\n");
|
|
134
|
-
}
|
|
135
|
-
async function apiSearch(config, keyword, page = 1) {
|
|
136
|
-
const params = {
|
|
137
|
-
types: "search",
|
|
138
|
-
source: config.source,
|
|
139
|
-
name: keyword,
|
|
140
|
-
count: config.searchListCount,
|
|
141
|
-
pages: page
|
|
142
|
-
};
|
|
143
|
-
const { data } = await import_axios.default.get(config.apiBase, { params, timeout: 15e3 });
|
|
144
|
-
return normalizeSearchList(data);
|
|
145
|
+
function buildRateKey(session, scope) {
|
|
146
|
+
if (scope === "platform") return session.platform;
|
|
147
|
+
if (scope === "channel") return `${session.platform}:${session.channelId ?? ""}`;
|
|
148
|
+
return `${session.platform}:${session.userId ?? ""}`;
|
|
145
149
|
}
|
|
146
|
-
async function
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
async function downloadToFile(url, filePath) {
|
|
152
|
-
const res = await import_axios.default.get(url, { responseType: "stream", timeout: 3e4 });
|
|
153
|
-
await new Promise((resolve, reject) => {
|
|
154
|
-
const ws = import_node_fs.default.createWriteStream(filePath);
|
|
155
|
-
res.data.pipe(ws);
|
|
156
|
-
ws.on("finish", () => resolve());
|
|
157
|
-
ws.on("error", reject);
|
|
150
|
+
async function gdSearch(ctx, config, keyword, page) {
|
|
151
|
+
const data = await ctx.http.get(config.apiBase, {
|
|
152
|
+
timeout: 15e3,
|
|
153
|
+
params: { types: "search", source: config.source, name: keyword, count: config.searchListCount, pages: page },
|
|
154
|
+
headers: { "user-agent": "koishi-music-to-voice" }
|
|
158
155
|
});
|
|
156
|
+
if (!Array.isArray(data)) return [];
|
|
157
|
+
return data.map((x) => ({
|
|
158
|
+
id: String(x.id ?? x.url_id ?? ""),
|
|
159
|
+
name: String(x.name ?? ""),
|
|
160
|
+
artist: artistsToString(x.artist),
|
|
161
|
+
album: String(x.album ?? ""),
|
|
162
|
+
source: String(x.source ?? config.source)
|
|
163
|
+
})).filter((x) => x.id && x.name);
|
|
159
164
|
}
|
|
160
|
-
async function
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return
|
|
165
|
+
async function gdUrl(ctx, config, item) {
|
|
166
|
+
const data = await ctx.http.get(config.apiBase, {
|
|
167
|
+
timeout: 15e3,
|
|
168
|
+
params: { types: "url", source: item.source || config.source, id: item.id },
|
|
169
|
+
headers: { "user-agent": "koishi-music-to-voice" }
|
|
170
|
+
});
|
|
171
|
+
if (typeof data === "string") return data;
|
|
172
|
+
if (data?.url) return String(data.url);
|
|
173
|
+
if (data?.data?.url) return String(data.data.url);
|
|
174
|
+
return "";
|
|
170
175
|
}
|
|
171
|
-
async function
|
|
172
|
-
const hasFfmpeg = !!ctx.ffmpeg;
|
|
173
|
-
const hasSilk = !!ctx.silk;
|
|
174
|
-
if (!hasFfmpeg || !hasSilk) return null;
|
|
175
|
-
ensureDir(config.tempDir);
|
|
176
|
-
const silkPath = import_node_path.default.join(config.tempDir, `${cacheKey}.silk`);
|
|
177
|
-
if (config.cacheMinutes > 0 && import_node_fs.default.existsSync(silkPath)) {
|
|
178
|
-
return silkPath;
|
|
179
|
-
}
|
|
180
|
-
const rawPath = import_node_path.default.join(config.tempDir, `${cacheKey}.src`);
|
|
181
|
-
await downloadToFile(audioUrl, rawPath);
|
|
182
|
-
const wavPath = import_node_path.default.join(config.tempDir, `${cacheKey}.wav`);
|
|
183
|
-
const ffmpeg = ctx.ffmpeg;
|
|
176
|
+
async function downloadToTemp(ctx, src) {
|
|
184
177
|
try {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
191
|
-
} else if (typeof ffmpeg.exec === "function") {
|
|
192
|
-
await ffmpeg.exec(["-y", "-i", rawPath, "-ac", "1", "-ar", "24000", wavPath]);
|
|
193
|
-
} else {
|
|
194
|
-
throw new Error("ffmpeg service API not recognized");
|
|
195
|
-
}
|
|
178
|
+
const file = await ctx.http.file(src);
|
|
179
|
+
const ext = ".mp3";
|
|
180
|
+
const fp = import_node_path.default.join(import_node_os.default.tmpdir(), `music_${import_node_crypto.default.randomBytes(8).toString("hex")}${ext}`);
|
|
181
|
+
import_node_fs.default.writeFileSync(fp, Buffer.from(file.data));
|
|
182
|
+
return fp;
|
|
196
183
|
} catch (e) {
|
|
197
|
-
|
|
184
|
+
return null;
|
|
198
185
|
}
|
|
199
|
-
|
|
186
|
+
}
|
|
187
|
+
async function tryTranscode(ctx, localPath) {
|
|
188
|
+
const anyCtx = ctx;
|
|
189
|
+
if (!anyCtx.ffmpeg || !anyCtx.silk) return localPath;
|
|
190
|
+
const wav = localPath.replace(/\.\w+$/, "") + ".wav";
|
|
191
|
+
const silk = localPath.replace(/\.\w+$/, "") + ".silk";
|
|
200
192
|
try {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
throw new Error("silk service API not recognized");
|
|
207
|
-
}
|
|
208
|
-
} catch (e) {
|
|
209
|
-
throw new Error(`silk \u7F16\u7801\u5931\u8D25\uFF1A${e?.message || String(e)}`);
|
|
193
|
+
await anyCtx.ffmpeg.convert(localPath, wav);
|
|
194
|
+
await anyCtx.silk.encode(wav, silk);
|
|
195
|
+
return silk;
|
|
196
|
+
} catch {
|
|
197
|
+
return localPath;
|
|
210
198
|
} finally {
|
|
211
199
|
try {
|
|
212
|
-
import_node_fs.default.unlinkSync(
|
|
213
|
-
} catch {
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
import_node_fs.default.unlinkSync(wavPath);
|
|
200
|
+
import_node_fs.default.existsSync(wav) && import_node_fs.default.unlinkSync(wav);
|
|
217
201
|
} catch {
|
|
218
202
|
}
|
|
219
203
|
}
|
|
220
|
-
return silkPath;
|
|
221
204
|
}
|
|
222
|
-
function
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
logger.info(`- downloads\u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF09\uFF1A${hasDownloads ? "\u5DF2\u68C0\u6D4B\u5230" : "\u672A\u68C0\u6D4B\u5230"}`);
|
|
233
|
-
logger.info("Music API \u51FA\u5904\uFF1AGD\u97F3\u4E50\u53F0 API\uFF08https://music-api.gdstudio.xyz/api.php\uFF09");
|
|
205
|
+
async function renderListImage(ctx, html) {
|
|
206
|
+
const anyCtx = ctx;
|
|
207
|
+
if (!anyCtx.puppeteer) return null;
|
|
208
|
+
const page = await anyCtx.puppeteer.page();
|
|
209
|
+
await page.setContent(html);
|
|
210
|
+
const el = await page.$("#song-list");
|
|
211
|
+
if (!el) return null;
|
|
212
|
+
const buf = await el.screenshot({});
|
|
213
|
+
await page.close();
|
|
214
|
+
return buf;
|
|
234
215
|
}
|
|
235
|
-
function
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
return config.exitCommandList.map((s) => s.trim()).includes(t);
|
|
245
|
-
}
|
|
246
|
-
function isExpired(state) {
|
|
247
|
-
return Date.now() > state.expiresAt;
|
|
248
|
-
}
|
|
249
|
-
async function refreshMenu(session, state) {
|
|
250
|
-
const list = await apiSearch(config, state.keyword, state.page);
|
|
251
|
-
state.list = list;
|
|
252
|
-
state.expiresAt = Date.now() + config.waitForTimeout * 1e3;
|
|
253
|
-
if (config.recallSearchMenuMessage) {
|
|
254
|
-
await tryRecall(session, state.menuMessageId);
|
|
255
|
-
state.menuMessageId = void 0;
|
|
256
|
-
}
|
|
257
|
-
if (!config.recallSearchMenuMessage) {
|
|
258
|
-
const text = buildMenuText(config, state.keyword, list, state.page);
|
|
259
|
-
const ids = await session.send(text);
|
|
260
|
-
state.menuMessageId = Array.isArray(ids) ? ids[0] : ids;
|
|
261
|
-
} else {
|
|
262
|
-
await session.send(`\u5DF2\u7FFB\u5230\u7B2C ${state.page} \u9875\uFF0C\u8BF7\u76F4\u63A5\u53D1\u9001\u5E8F\u53F7\uFF081-${list.length}\uFF09`);
|
|
263
|
-
}
|
|
216
|
+
function makeListText(config, list, page) {
|
|
217
|
+
const start = (page - 1) * config.searchListCount;
|
|
218
|
+
const lines = list.map((s, i) => `${start + i + 1}. ${s.name} -- ${s.artist}${s.album ? " -- " + s.album : ""}`);
|
|
219
|
+
let tail = `
|
|
220
|
+
|
|
221
|
+
\u7FFB\u9875\uFF1A${config.prevPageCommand} / ${config.nextPageCommand}`;
|
|
222
|
+
if (config.menuExitCommandTip && config.exitCommandList?.length) {
|
|
223
|
+
tail += `
|
|
224
|
+
\u9000\u51FA\uFF1A${config.exitCommandList.join(" / ")}`;
|
|
264
225
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
226
|
+
return `\u97F3\u4E50\u5217\u8868\uFF1A
|
|
227
|
+
${lines.join("\n")}${tail}
|
|
228
|
+
|
|
229
|
+
\u8BF7\u5728 ${config.waitForTimeout} \u79D2\u5185\u8F93\u5165\u5E8F\u53F7\uFF1A`;
|
|
230
|
+
}
|
|
231
|
+
function apply(ctx, config) {
|
|
232
|
+
const lastUse = /* @__PURE__ */ new Map();
|
|
233
|
+
ctx.command(`${config.commandName} <keyword:text>`, "\u97F3\u4E50\u805A\u5408\u70B9\u6B4C\u5E76\u53D1\u9001\u8BED\u97F3").alias(config.commandAlias).action(async (argv, keyword) => {
|
|
234
|
+
const session = argv.session;
|
|
235
|
+
const options = argv.options ?? {};
|
|
236
|
+
if (!session) return;
|
|
237
|
+
keyword = (keyword ?? "").trim();
|
|
238
|
+
if (!keyword) return `\u7528\u6CD5\uFF1A${config.commandName} \u6B4C\u66F2\u540D`;
|
|
239
|
+
if (config.enableRateLimit) {
|
|
240
|
+
const scope = config.rateLimitScope ?? "user";
|
|
241
|
+
const key = buildRateKey(session, scope);
|
|
242
|
+
const now = Date.now();
|
|
243
|
+
const last = lastUse.get(key) ?? 0;
|
|
244
|
+
const interval = (config.rateLimitIntervalSec ?? 60) * 1e3;
|
|
245
|
+
if (now - last < interval) {
|
|
246
|
+
const remain = Math.ceil((interval - (now - last)) / 1e3);
|
|
247
|
+
return `\u64CD\u4F5C\u592A\u9891\u7E41\uFF0C\u8BF7 ${remain} \u79D2\u540E\u518D\u8BD5\u3002`;
|
|
248
|
+
}
|
|
249
|
+
lastUse.set(key, now);
|
|
283
250
|
}
|
|
284
|
-
|
|
285
|
-
let
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
251
|
+
let page = 1;
|
|
252
|
+
let menuMsgId = null;
|
|
253
|
+
while (true) {
|
|
254
|
+
let list = [];
|
|
255
|
+
try {
|
|
256
|
+
list = await gdSearch(ctx, config, keyword, page);
|
|
257
|
+
} catch (e) {
|
|
258
|
+
logger.warn(`[search] ${String(e?.message || e)}`);
|
|
259
|
+
const ids = await session.send("\u641C\u7D22\u5931\u8D25\uFF08API \u4E0D\u53EF\u7528\u6216\u8D85\u65F6\uFF09\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002");
|
|
260
|
+
const mid = Array.isArray(ids) ? ids[0] : ids;
|
|
261
|
+
if (config.recallFetchFailTip) await safeDelete(session, mid);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!list.length) {
|
|
265
|
+
const ids = await session.send("\u6CA1\u6709\u641C\u7D22\u5230\u7ED3\u679C\uFF0C\u8BF7\u6362\u4E2A\u5173\u952E\u8BCD\u8BD5\u8BD5\u3002");
|
|
266
|
+
const mid = Array.isArray(ids) ? ids[0] : ids;
|
|
267
|
+
if (config.recallFetchFailTip) await safeDelete(session, mid);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (menuMsgId && config.recallMenu) await safeDelete(session, menuMsgId);
|
|
271
|
+
if (config.imageMode) {
|
|
272
|
+
const text2 = makeListText(config, list, page).replace(/\n/g, "<br/>");
|
|
273
|
+
const html = `<!doctype html><html><head><meta charset="utf-8"/><style>
|
|
274
|
+
body{margin:0;background:${config.backgroundColor};color:${config.textColor};font-size:16px;}
|
|
275
|
+
#song-list{padding:18px;white-space:nowrap;}
|
|
276
|
+
</style></head><body><div id="song-list">${text2}</div></body></html>`;
|
|
277
|
+
const buf = await renderListImage(ctx, html);
|
|
278
|
+
if (!buf) {
|
|
279
|
+
const ids2 = await session.send("\u56FE\u7247\u6B4C\u5355\u751F\u6210\u5931\u8D25\uFF1A\u672A\u5B89\u88C5 puppeteer \u670D\u52A1\u6216\u5176\u4E0D\u53EF\u7528\u3002");
|
|
280
|
+
const mid = Array.isArray(ids2) ? ids2[0] : ids2;
|
|
281
|
+
if (config.recallFetchFailTip) await safeDelete(session, mid);
|
|
296
282
|
return;
|
|
297
283
|
}
|
|
298
|
-
|
|
284
|
+
const ids = await session.send([import_koishi.h.image(buf, "image/png")]);
|
|
285
|
+
menuMsgId = Array.isArray(ids) ? ids[0] : ids;
|
|
286
|
+
} else {
|
|
287
|
+
const txt = makeListText(config, list, page);
|
|
288
|
+
const ids = await session.send(txt);
|
|
289
|
+
menuMsgId = Array.isArray(ids) ? ids[0] : ids;
|
|
299
290
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const key = getKey(session);
|
|
350
|
-
if (!key) return next();
|
|
351
|
-
const state = pending.get(key);
|
|
352
|
-
if (!state) return next();
|
|
353
|
-
if (session.userId !== state.userId) return next();
|
|
354
|
-
if (isExpired(state)) {
|
|
355
|
-
pending.delete(key);
|
|
356
|
-
return next();
|
|
357
|
-
}
|
|
358
|
-
const content = (session.content || "").trim();
|
|
359
|
-
if (!content) return next();
|
|
360
|
-
if (isExit(content)) {
|
|
361
|
-
if (config.recallSearchMenuMessage) await tryRecall(session, state.menuMessageId);
|
|
362
|
-
pending.delete(key);
|
|
363
|
-
if (!config.recallUserSelectMessage) await session.send("\u5DF2\u9000\u51FA\u9009\u6B4C\u3002");
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
if (content === config.nextPageCommand) {
|
|
367
|
-
if (config.recallUserSelectMessage) await tryRecall(session, session.messageId);
|
|
368
|
-
state.page += 1;
|
|
291
|
+
const input = await session.prompt(
|
|
292
|
+
(s) => {
|
|
293
|
+
const els = s.elements ?? [];
|
|
294
|
+
return import_koishi.h.select(els, "text").join("");
|
|
295
|
+
},
|
|
296
|
+
{ timeout: config.waitForTimeout * 1e3 }
|
|
297
|
+
);
|
|
298
|
+
if ((0, import_koishi.isNullable)(input)) {
|
|
299
|
+
if (config.recallMenu) await safeDelete(session, menuMsgId);
|
|
300
|
+
const ids = await session.send("\u8F93\u5165\u8D85\u65F6\uFF0C\u5DF2\u53D6\u6D88\u70B9\u6B4C\u3002");
|
|
301
|
+
const mid = Array.isArray(ids) ? ids[0] : ids;
|
|
302
|
+
if (config.recallTimeoutTip) await safeDelete(session, mid);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const text = String(input).trim();
|
|
306
|
+
if (!text) continue;
|
|
307
|
+
if (config.exitCommandList.includes(text)) {
|
|
308
|
+
if (config.recallMenu) await safeDelete(session, menuMsgId);
|
|
309
|
+
const ids = await session.send("\u5DF2\u9000\u51FA\u6B4C\u66F2\u9009\u62E9\u3002");
|
|
310
|
+
const mid = Array.isArray(ids) ? ids[0] : ids;
|
|
311
|
+
if (config.recallExitTip) await safeDelete(session, mid);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (text === config.nextPageCommand) {
|
|
315
|
+
page += 1;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (text === config.prevPageCommand) {
|
|
319
|
+
page = Math.max(1, page - 1);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (!/^\d+$/.test(text)) continue;
|
|
323
|
+
const idx = parseInt(text, 10);
|
|
324
|
+
const start = (page - 1) * config.searchListCount;
|
|
325
|
+
const local = idx - start - 1;
|
|
326
|
+
if (local < 0 || local >= list.length) {
|
|
327
|
+
if (config.recallMenu) await safeDelete(session, menuMsgId);
|
|
328
|
+
const ids = await session.send("\u5E8F\u53F7\u8F93\u5165\u9519\u8BEF\uFF0C\u5DF2\u9000\u51FA\u6B4C\u66F2\u9009\u62E9\u3002");
|
|
329
|
+
const mid = Array.isArray(ids) ? ids[0] : ids;
|
|
330
|
+
if (config.recallInvalidTip) await safeDelete(session, mid);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const chosen = list[local];
|
|
334
|
+
let tipMsgId = null;
|
|
335
|
+
{
|
|
336
|
+
const ids = await session.send(config.generationTip);
|
|
337
|
+
tipMsgId = Array.isArray(ids) ? ids[0] : ids;
|
|
338
|
+
}
|
|
339
|
+
let playUrl = "";
|
|
369
340
|
try {
|
|
370
|
-
await
|
|
371
|
-
} catch {
|
|
372
|
-
|
|
373
|
-
await session.send("\u7FFB\u9875\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002");
|
|
341
|
+
playUrl = await gdUrl(ctx, config, chosen);
|
|
342
|
+
} catch (e) {
|
|
343
|
+
logger.warn(`[url] ${String(e?.message || e)}`);
|
|
374
344
|
}
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
await session
|
|
345
|
+
if (!playUrl) {
|
|
346
|
+
if (config.recallGeneratingTip) await safeDelete(session, tipMsgId);
|
|
347
|
+
if (config.recallMenu) await safeDelete(session, menuMsgId);
|
|
348
|
+
const ids = await session.send("\u83B7\u53D6\u6B4C\u66F2\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002");
|
|
349
|
+
const mid = Array.isArray(ids) ? ids[0] : ids;
|
|
350
|
+
if (config.recallFetchFailTip) await safeDelete(session, mid);
|
|
381
351
|
return;
|
|
382
352
|
}
|
|
383
|
-
state.page -= 1;
|
|
384
353
|
try {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
354
|
+
let finalSrc = playUrl;
|
|
355
|
+
if (config.forceTranscode) {
|
|
356
|
+
const localPath = await downloadToTemp(ctx, playUrl);
|
|
357
|
+
if (localPath) {
|
|
358
|
+
const out = await tryTranscode(ctx, localPath);
|
|
359
|
+
finalSrc = (0, import_node_url.pathToFileURL)(out).href;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
let sendIds;
|
|
363
|
+
if (config.sendAs === "file") {
|
|
364
|
+
sendIds = await session.send([import_koishi.h.file(finalSrc)]);
|
|
365
|
+
} else if (config.sendAs === "audio") {
|
|
366
|
+
sendIds = await session.send([import_koishi.h.audio(finalSrc)]);
|
|
367
|
+
} else {
|
|
368
|
+
sendIds = await session.send([(0, import_koishi.h)("record", { src: finalSrc })]);
|
|
369
|
+
}
|
|
370
|
+
const songMsgId = Array.isArray(sendIds) ? sendIds[0] : sendIds;
|
|
371
|
+
if (config.recallSongMessage) await safeDelete(session, songMsgId);
|
|
372
|
+
} catch (e) {
|
|
373
|
+
logger.warn(`[send] ${String(e?.message || e)}`);
|
|
374
|
+
const ids = await session.send("\u53D1\u9001\u5931\u8D25\uFF08\u53EF\u80FD\u7F3A\u5C11\u8F6C\u7801\u4F9D\u8D56\u6216\u94FE\u63A5\u5931\u6548\uFF09\u3002");
|
|
375
|
+
const mid = Array.isArray(ids) ? ids[0] : ids;
|
|
376
|
+
if (config.recallFetchFailTip) await safeDelete(session, mid);
|
|
377
|
+
} finally {
|
|
378
|
+
if (config.recallGeneratingTip) await safeDelete(session, tipMsgId);
|
|
379
|
+
if (config.recallMenu) await safeDelete(session, menuMsgId);
|
|
389
380
|
}
|
|
390
381
|
return;
|
|
391
382
|
}
|
|
392
|
-
const n = Number(content);
|
|
393
|
-
if (!Number.isInteger(n) || n < 1 || n > state.list.length) return next();
|
|
394
|
-
if (config.recallUserSelectMessage) await tryRecall(session, session.messageId);
|
|
395
|
-
await handlePick(session, state, n - 1);
|
|
396
383
|
});
|
|
397
384
|
}
|
|
398
385
|
// Annotate the CommonJS export names for ESM import in node:
|
|
399
386
|
0 && (module.exports = {
|
|
400
387
|
Config,
|
|
401
388
|
apply,
|
|
402
|
-
|
|
389
|
+
inject,
|
|
390
|
+
name,
|
|
391
|
+
usage
|
|
403
392
|
});
|