koishi-plugin-music-to-voice 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/README.md +12 -0
- package/dist/index.d.mts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +403 -0
- package/dist/index.mjs +366 -0
- package/package.json +66 -0
- package/src/index.ts +516 -0
package/README.md
ADDED
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Schema, Context } from 'koishi';
|
|
2
|
+
|
|
3
|
+
declare const name = "music-voice-pro";
|
|
4
|
+
type MusicSource = 'netease' | 'tencent' | 'kugou' | 'kuwo' | 'migu' | 'baidu';
|
|
5
|
+
interface Config {
|
|
6
|
+
commandName: string;
|
|
7
|
+
commandAlias: string;
|
|
8
|
+
apiBase: string;
|
|
9
|
+
source: MusicSource;
|
|
10
|
+
searchListCount: number;
|
|
11
|
+
waitForTimeout: number;
|
|
12
|
+
nextPageCommand: string;
|
|
13
|
+
prevPageCommand: string;
|
|
14
|
+
exitCommandList: string[];
|
|
15
|
+
menuExitCommandTip: boolean;
|
|
16
|
+
imageMode: boolean;
|
|
17
|
+
sendAs: 'record' | 'audio';
|
|
18
|
+
forceTranscode: boolean;
|
|
19
|
+
tempDir: string;
|
|
20
|
+
cacheMinutes: number;
|
|
21
|
+
generationTip: string;
|
|
22
|
+
recallSearchMenuMessage: boolean;
|
|
23
|
+
recallTipMessage: boolean;
|
|
24
|
+
recallUserSelectMessage: boolean;
|
|
25
|
+
recallVoiceMessage: boolean;
|
|
26
|
+
loggerinfo: boolean;
|
|
27
|
+
}
|
|
28
|
+
declare const Config: Schema<Config>;
|
|
29
|
+
declare function apply(ctx: Context, config: Config): void;
|
|
30
|
+
|
|
31
|
+
export { Config, apply, name };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Schema, Context } from 'koishi';
|
|
2
|
+
|
|
3
|
+
declare const name = "music-voice-pro";
|
|
4
|
+
type MusicSource = 'netease' | 'tencent' | 'kugou' | 'kuwo' | 'migu' | 'baidu';
|
|
5
|
+
interface Config {
|
|
6
|
+
commandName: string;
|
|
7
|
+
commandAlias: string;
|
|
8
|
+
apiBase: string;
|
|
9
|
+
source: MusicSource;
|
|
10
|
+
searchListCount: number;
|
|
11
|
+
waitForTimeout: number;
|
|
12
|
+
nextPageCommand: string;
|
|
13
|
+
prevPageCommand: string;
|
|
14
|
+
exitCommandList: string[];
|
|
15
|
+
menuExitCommandTip: boolean;
|
|
16
|
+
imageMode: boolean;
|
|
17
|
+
sendAs: 'record' | 'audio';
|
|
18
|
+
forceTranscode: boolean;
|
|
19
|
+
tempDir: string;
|
|
20
|
+
cacheMinutes: number;
|
|
21
|
+
generationTip: string;
|
|
22
|
+
recallSearchMenuMessage: boolean;
|
|
23
|
+
recallTipMessage: boolean;
|
|
24
|
+
recallUserSelectMessage: boolean;
|
|
25
|
+
recallVoiceMessage: boolean;
|
|
26
|
+
loggerinfo: boolean;
|
|
27
|
+
}
|
|
28
|
+
declare const Config: Schema<Config>;
|
|
29
|
+
declare function apply(ctx: Context, config: Config): void;
|
|
30
|
+
|
|
31
|
+
export { Config, apply, name };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name2 in all)
|
|
10
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Config: () => Config,
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
name: () => name
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
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
|
+
var import_node_os = __toESM(require("os"));
|
|
43
|
+
var import_node_crypto = __toESM(require("crypto"));
|
|
44
|
+
var name = "music-voice-pro";
|
|
45
|
+
var logger = new import_koishi.Logger(name);
|
|
46
|
+
var Config = import_koishi.Schema.object({
|
|
47
|
+
commandName: import_koishi.Schema.string().default("\u542C\u6B4C").description("\u6307\u4EE4\u540D\u79F0"),
|
|
48
|
+
commandAlias: import_koishi.Schema.string().default("music").description("\u6307\u4EE4\u522B\u540D"),
|
|
49
|
+
apiBase: import_koishi.Schema.string().default("https://music-api.gdstudio.xyz/api.php").description("\u97F3\u4E50 API \u5730\u5740\uFF08GD\u97F3\u4E50\u53F0 API\uFF09"),
|
|
50
|
+
source: import_koishi.Schema.union([
|
|
51
|
+
import_koishi.Schema.const("netease").description("\u7F51\u6613\u4E91"),
|
|
52
|
+
import_koishi.Schema.const("tencent").description("QQ\u97F3\u4E50"),
|
|
53
|
+
import_koishi.Schema.const("kugou").description("\u9177\u72D7"),
|
|
54
|
+
import_koishi.Schema.const("kuwo").description("\u9177\u6211"),
|
|
55
|
+
import_koishi.Schema.const("migu").description("\u54AA\u5495"),
|
|
56
|
+
import_koishi.Schema.const("baidu").description("\u767E\u5EA6")
|
|
57
|
+
]).default("netease").description("\u97F3\u6E90\uFF08\u4E0B\u62C9\u9009\u62E9\uFF09"),
|
|
58
|
+
searchListCount: import_koishi.Schema.number().min(5).max(50).default(20).description("\u641C\u7D22\u5217\u8868\u6570\u91CF"),
|
|
59
|
+
waitForTimeout: import_koishi.Schema.number().min(10).max(180).default(45).description("\u7B49\u5F85\u8F93\u5165\u5E8F\u53F7\u8D85\u65F6\uFF08\u79D2\uFF09"),
|
|
60
|
+
nextPageCommand: import_koishi.Schema.string().default("\u4E0B\u4E00\u9875").description("\u4E0B\u4E00\u9875\u6307\u4EE4"),
|
|
61
|
+
prevPageCommand: import_koishi.Schema.string().default("\u4E0A\u4E00\u9875").description("\u4E0A\u4E00\u9875\u6307\u4EE4"),
|
|
62
|
+
exitCommandList: import_koishi.Schema.array(String).default(["0", "\u4E0D\u542C\u4E86", "\u9000\u51FA"]).description("\u9000\u51FA\u6307\u4EE4\u5217\u8868"),
|
|
63
|
+
menuExitCommandTip: import_koishi.Schema.boolean().default(false).description("\u662F\u5426\u5728\u6B4C\u5355\u672B\u5C3E\u63D0\u793A\u9000\u51FA\u6307\u4EE4"),
|
|
64
|
+
imageMode: import_koishi.Schema.boolean().default(false).description("\u56FE\u7247\u6B4C\u5355\u6A21\u5F0F\uFF08\u53EF\u9009\uFF1A\u9700\u8981 puppeteer \u63D2\u4EF6\uFF0C\u5F53\u524D\u4EC5\u4FDD\u7559\u5F00\u5173\uFF09"),
|
|
65
|
+
sendAs: import_koishi.Schema.union([
|
|
66
|
+
import_koishi.Schema.const("record").description("\u8BED\u97F3 record"),
|
|
67
|
+
import_koishi.Schema.const("audio").description("\u97F3\u9891 audio")
|
|
68
|
+
]).default("record").description("\u53D1\u9001\u7C7B\u578B"),
|
|
69
|
+
forceTranscode: import_koishi.Schema.boolean().default(true).description("\u662F\u5426\u5F3A\u5236\u8F6C\u7801\u4E3A silk\uFF08\u9700\u8981 ffmpeg + silk \u63D2\u4EF6\uFF09"),
|
|
70
|
+
tempDir: import_koishi.Schema.string().default(import_node_path.default.join(import_node_os.default.tmpdir(), "koishi-music-voice")).description("\u4E34\u65F6\u76EE\u5F55"),
|
|
71
|
+
cacheMinutes: import_koishi.Schema.number().min(0).max(1440).default(120).description("\u7F13\u5B58\u65F6\u957F\uFF08\u5206\u949F\uFF0C0=\u4E0D\u7F13\u5B58\uFF09"),
|
|
72
|
+
generationTip: import_koishi.Schema.string().default("\u751F\u6210\u8BED\u97F3\u4E2D...").description("\u7528\u6237\u9009\u6B4C\u540E\u63D0\u793A"),
|
|
73
|
+
recallSearchMenuMessage: import_koishi.Schema.boolean().default(true).description("\u64A4\u56DE\uFF1A\u6B4C\u5355\u6D88\u606F"),
|
|
74
|
+
recallTipMessage: import_koishi.Schema.boolean().default(true).description("\u64A4\u56DE\uFF1A\u751F\u6210\u63D0\u793A\u6D88\u606F"),
|
|
75
|
+
recallUserSelectMessage: import_koishi.Schema.boolean().default(true).description("\u64A4\u56DE\uFF1A\u7528\u6237\u8F93\u5165\u7684\u5E8F\u53F7\u6D88\u606F"),
|
|
76
|
+
recallVoiceMessage: import_koishi.Schema.boolean().default(false).description("\u64A4\u56DE\uFF1A\u8BED\u97F3\u6D88\u606F"),
|
|
77
|
+
loggerinfo: import_koishi.Schema.boolean().default(false).description("\u65E5\u5FD7\u8C03\u8BD5\u6A21\u5F0F")
|
|
78
|
+
}).description("\u70B9\u6B4C\u8BED\u97F3\uFF08\u652F\u6301\u7FFB\u9875 + \u53EF\u9009 silk/ffmpeg\uFF09");
|
|
79
|
+
function safeText(x) {
|
|
80
|
+
return typeof x === "string" ? x : x == null ? "" : String(x);
|
|
81
|
+
}
|
|
82
|
+
function normalizeSearchList(data) {
|
|
83
|
+
const arr = Array.isArray(data) ? data : Array.isArray(data?.result) ? data.result : Array.isArray(data?.data) ? data.data : Array.isArray(data?.songs) ? data.songs : [];
|
|
84
|
+
return arr.map((it) => {
|
|
85
|
+
const id = safeText(it?.id ?? it?.songid ?? it?.rid ?? it?.hash ?? it?.mid);
|
|
86
|
+
const name2 = safeText(it?.name ?? it?.songname ?? it?.title);
|
|
87
|
+
const artist = safeText(it?.artist) || safeText(it?.singer) || safeText(it?.author) || (Array.isArray(it?.artists) ? it.artists.map((a) => safeText(a?.name)).filter(Boolean).join("/") : "");
|
|
88
|
+
const album = safeText(it?.album ?? it?.albummid ?? it?.albumname);
|
|
89
|
+
return { id, name: name2, artist, album };
|
|
90
|
+
}).filter((x) => x.id && x.name);
|
|
91
|
+
}
|
|
92
|
+
function normalizeUrl(data) {
|
|
93
|
+
return safeText(data?.url) || safeText(data?.data?.url) || safeText(data?.result?.url) || safeText(data?.data) || "";
|
|
94
|
+
}
|
|
95
|
+
function md5(s) {
|
|
96
|
+
return import_node_crypto.default.createHash("md5").update(s).digest("hex");
|
|
97
|
+
}
|
|
98
|
+
function ensureDir(p) {
|
|
99
|
+
import_node_fs.default.mkdirSync(p, { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
async function tryRecall(session, messageId) {
|
|
102
|
+
if (!messageId) return;
|
|
103
|
+
try {
|
|
104
|
+
await session.bot.deleteMessage(session.channelId, messageId);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function hRecord(src) {
|
|
109
|
+
return (0, import_koishi.h)("record", { src });
|
|
110
|
+
}
|
|
111
|
+
function hAudio(src) {
|
|
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
|
+
}
|
|
146
|
+
async function apiGetSongUrl(config, id) {
|
|
147
|
+
const params = { types: "url", source: config.source, id };
|
|
148
|
+
const { data } = await import_axios.default.get(config.apiBase, { params, timeout: 15e3 });
|
|
149
|
+
return normalizeUrl(data);
|
|
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);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async function sendVoiceByUrl(session, config, audioUrl) {
|
|
161
|
+
const seg = config.sendAs === "record" ? hRecord(audioUrl) : hAudio(audioUrl);
|
|
162
|
+
const ids = await session.send(seg);
|
|
163
|
+
return Array.isArray(ids) ? ids[0] : ids;
|
|
164
|
+
}
|
|
165
|
+
async function sendVoiceByFile(session, config, absPath) {
|
|
166
|
+
const url = `file://${absPath.replace(/\\/g, "/")}`;
|
|
167
|
+
const seg = config.sendAs === "record" ? hRecord(url) : hAudio(url);
|
|
168
|
+
const ids = await session.send(seg);
|
|
169
|
+
return Array.isArray(ids) ? ids[0] : ids;
|
|
170
|
+
}
|
|
171
|
+
async function buildSilkIfPossible(ctx, config, audioUrl, cacheKey) {
|
|
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;
|
|
184
|
+
try {
|
|
185
|
+
if (typeof ffmpeg.convert === "function") {
|
|
186
|
+
await ffmpeg.convert(rawPath, wavPath, {
|
|
187
|
+
format: "wav",
|
|
188
|
+
audioChannels: 1,
|
|
189
|
+
audioFrequency: 24e3
|
|
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
|
+
}
|
|
196
|
+
} catch (e) {
|
|
197
|
+
throw new Error(`ffmpeg \u8F6C\u7801\u5931\u8D25\uFF1A${e?.message || String(e)}`);
|
|
198
|
+
}
|
|
199
|
+
const silk = ctx.silk;
|
|
200
|
+
try {
|
|
201
|
+
if (typeof silk.encode === "function") {
|
|
202
|
+
await silk.encode(wavPath, silkPath, { rate: 24e3 });
|
|
203
|
+
} else if (typeof silk.encodeWav === "function") {
|
|
204
|
+
await silk.encodeWav(wavPath, silkPath, { rate: 24e3 });
|
|
205
|
+
} else {
|
|
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)}`);
|
|
210
|
+
} finally {
|
|
211
|
+
try {
|
|
212
|
+
import_node_fs.default.unlinkSync(rawPath);
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
import_node_fs.default.unlinkSync(wavPath);
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return silkPath;
|
|
221
|
+
}
|
|
222
|
+
function logDepsHint(ctx) {
|
|
223
|
+
const hasPuppeteer = !!ctx.puppeteer;
|
|
224
|
+
const hasFfmpeg = !!ctx.ffmpeg;
|
|
225
|
+
const hasSilk = !!ctx.silk;
|
|
226
|
+
const hasDownloads = !!ctx.downloads;
|
|
227
|
+
logger.info("\u5F00\u542F\u63D2\u4EF6\u524D\uFF0C\u8BF7\u786E\u4FDD\u4EE5\u4E0B\u670D\u52A1\u5DF2\u7ECF\u542F\u7528\uFF08\u53EF\u9009\u5B89\u88C5\uFF09\uFF1A");
|
|
228
|
+
logger.info(`- puppeteer\u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF09\uFF1A${hasPuppeteer ? "\u5DF2\u68C0\u6D4B\u5230" : "\u672A\u68C0\u6D4B\u5230"}`);
|
|
229
|
+
logger.info("\u6B64\u5916\u53EF\u80FD\u8FD8\u9700\u8981\u8FD9\u4E9B\u670D\u52A1\u624D\u80FD\u53D1\u9001\u8BED\u97F3\uFF1A");
|
|
230
|
+
logger.info(`- ffmpeg\u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF09\uFF08\u6B64\u670D\u52A1\u53EF\u80FD\u989D\u5916\u4F9D\u8D56downloads\u670D\u52A1\uFF09\uFF1A${hasFfmpeg ? "\u5DF2\u68C0\u6D4B\u5230" : "\u672A\u68C0\u6D4B\u5230"}`);
|
|
231
|
+
logger.info(`- silk\u670D\u52A1\uFF08\u53EF\u9009\u5B89\u88C5\uFF09\uFF1A${hasSilk ? "\u5DF2\u68C0\u6D4B\u5230" : "\u672A\u68C0\u6D4B\u5230"}`);
|
|
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");
|
|
234
|
+
}
|
|
235
|
+
function apply(ctx, config) {
|
|
236
|
+
if (config.loggerinfo) logger.level = import_koishi.Logger.DEBUG;
|
|
237
|
+
logDepsHint(ctx);
|
|
238
|
+
const pending = /* @__PURE__ */ new Map();
|
|
239
|
+
function getKey(session) {
|
|
240
|
+
return String(session?.channelId || "");
|
|
241
|
+
}
|
|
242
|
+
function isExit(input) {
|
|
243
|
+
const t = input.trim();
|
|
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
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function handlePick(session, state, pickIndex) {
|
|
266
|
+
const item = state.list[pickIndex];
|
|
267
|
+
if (!item) {
|
|
268
|
+
await session.send(`\u5E8F\u53F7\u65E0\u6548\uFF0C\u8BF7\u8F93\u5165 1-${state.list.length}\uFF0C\u6216\u8F93\u5165 ${config.exitCommandList.join("/")} \u9000\u51FA\u3002`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
let tipId;
|
|
272
|
+
if (!config.recallTipMessage && config.generationTip?.trim()) {
|
|
273
|
+
const ids = await session.send(config.generationTip);
|
|
274
|
+
tipId = Array.isArray(ids) ? ids[0] : ids;
|
|
275
|
+
}
|
|
276
|
+
let songUrl = "";
|
|
277
|
+
try {
|
|
278
|
+
songUrl = await apiGetSongUrl(config, item.id);
|
|
279
|
+
if (!songUrl) throw new Error("empty url");
|
|
280
|
+
} catch {
|
|
281
|
+
await session.send("\u83B7\u53D6\u6B4C\u66F2\u76F4\u94FE\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\uFF0C\u6216\u66F4\u6362\u97F3\u6E90\u3002");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const cacheKey = md5(`${config.source}:${item.id}`);
|
|
285
|
+
let voiceId;
|
|
286
|
+
try {
|
|
287
|
+
const silkPath = await buildSilkIfPossible(ctx, config, songUrl, cacheKey);
|
|
288
|
+
if (silkPath) {
|
|
289
|
+
voiceId = await sendVoiceByFile(session, config, silkPath);
|
|
290
|
+
} else {
|
|
291
|
+
if (config.forceTranscode) {
|
|
292
|
+
await session.send(
|
|
293
|
+
`\u5F53\u524D\u914D\u7F6E\u4E3A\u3010\u5F3A\u5236 silk \u8F6C\u7801\u3011\u4F46\u672A\u68C0\u6D4B\u5230 ffmpeg/silk \u670D\u52A1\u3002
|
|
294
|
+
\u8BF7\u5728 Koishi \u63D2\u4EF6\u5E02\u573A\u5B89\u88C5\u5E76\u542F\u7528\uFF1Affmpeg\u3001silk\uFF08\u53EF\u80FD\u8FD8\u9700\u8981 downloads\uFF09\u3002`
|
|
295
|
+
);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
voiceId = await sendVoiceByUrl(session, config, songUrl);
|
|
299
|
+
}
|
|
300
|
+
} catch (e) {
|
|
301
|
+
await session.send(`\u751F\u6210\u8BED\u97F3\u5931\u8D25\uFF1A${e?.message || String(e)}
|
|
302
|
+
\u8BF7\u68C0\u67E5 ffmpeg/silk \u63D2\u4EF6\u662F\u5426\u542F\u7528\uFF0C\u6216\u5173\u95ED\u201C\u5F3A\u5236\u8F6C\u7801\u201D\u3002`);
|
|
303
|
+
return;
|
|
304
|
+
} finally {
|
|
305
|
+
state.tipMessageId = tipId;
|
|
306
|
+
state.voiceMessageId = voiceId;
|
|
307
|
+
}
|
|
308
|
+
if (config.recallSearchMenuMessage) await tryRecall(session, state.menuMessageId);
|
|
309
|
+
if (config.recallTipMessage) await tryRecall(session, state.tipMessageId);
|
|
310
|
+
if (config.recallVoiceMessage) await tryRecall(session, state.voiceMessageId);
|
|
311
|
+
pending.delete(getKey(session));
|
|
312
|
+
}
|
|
313
|
+
ctx.command(`${config.commandName} <keyword:text>`, "\u70B9\u6B4C\u5E76\u53D1\u9001\u8BED\u97F3\uFF08GD\u97F3\u4E50\u53F0 API\uFF09").alias(config.commandAlias).action(async ({ session }, keyword) => {
|
|
314
|
+
if (!session) return;
|
|
315
|
+
keyword = (keyword || "").trim();
|
|
316
|
+
if (!keyword) return `\u7528\u6CD5\uFF1A${config.commandName} \u6B4C\u66F2\u540D`;
|
|
317
|
+
const userId = session.userId;
|
|
318
|
+
const channelId = session.channelId;
|
|
319
|
+
if (!userId || !channelId) {
|
|
320
|
+
await session.send("\u5F53\u524D\u9002\u914D\u5668\u672A\u63D0\u4F9B userId/channelId\uFF0C\u65E0\u6CD5\u8FDB\u5165\u9009\u6B4C\u6A21\u5F0F\u3002");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
let list = [];
|
|
324
|
+
try {
|
|
325
|
+
list = await apiSearch(config, keyword, 1);
|
|
326
|
+
} catch {
|
|
327
|
+
return "\u641C\u7D22\u5931\u8D25\uFF08API \u4E0D\u53EF\u7528\u6216\u8D85\u65F6\uFF09\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002";
|
|
328
|
+
}
|
|
329
|
+
if (!list.length) return "\u6CA1\u6709\u641C\u5230\u7ED3\u679C\uFF0C\u6362\u4E2A\u5173\u952E\u8BCD\u8BD5\u8BD5\u3002";
|
|
330
|
+
const state = {
|
|
331
|
+
userId,
|
|
332
|
+
channelId,
|
|
333
|
+
guildId: session.guildId,
|
|
334
|
+
keyword,
|
|
335
|
+
page: 1,
|
|
336
|
+
list,
|
|
337
|
+
expiresAt: Date.now() + config.waitForTimeout * 1e3
|
|
338
|
+
};
|
|
339
|
+
if (!config.recallSearchMenuMessage) {
|
|
340
|
+
const text = buildMenuText(config, keyword, list, 1);
|
|
341
|
+
const ids = await session.send(text);
|
|
342
|
+
state.menuMessageId = Array.isArray(ids) ? ids[0] : ids;
|
|
343
|
+
} else {
|
|
344
|
+
await session.send(`\u5DF2\u8FDB\u5165\u9009\u6B4C\u6A21\u5F0F\uFF0C\u8BF7\u76F4\u63A5\u53D1\u9001\u5E8F\u53F7\uFF081-${list.length}\uFF09\uFF0C\u6216\u53D1\u9001\u201C${config.nextPageCommand}/${config.prevPageCommand}\u201D\u7FFB\u9875\u3002`);
|
|
345
|
+
}
|
|
346
|
+
pending.set(channelId, state);
|
|
347
|
+
});
|
|
348
|
+
ctx.middleware(async (session, next) => {
|
|
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;
|
|
369
|
+
try {
|
|
370
|
+
await refreshMenu(session, state);
|
|
371
|
+
} catch {
|
|
372
|
+
state.page -= 1;
|
|
373
|
+
await session.send("\u7FFB\u9875\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002");
|
|
374
|
+
}
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (content === config.prevPageCommand) {
|
|
378
|
+
if (config.recallUserSelectMessage) await tryRecall(session, session.messageId);
|
|
379
|
+
if (state.page <= 1) {
|
|
380
|
+
await session.send("\u5DF2\u7ECF\u662F\u7B2C\u4E00\u9875\u3002");
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
state.page -= 1;
|
|
384
|
+
try {
|
|
385
|
+
await refreshMenu(session, state);
|
|
386
|
+
} catch {
|
|
387
|
+
state.page += 1;
|
|
388
|
+
await session.send("\u7FFB\u9875\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002");
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
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
|
+
});
|
|
397
|
+
}
|
|
398
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
399
|
+
0 && (module.exports = {
|
|
400
|
+
Config,
|
|
401
|
+
apply,
|
|
402
|
+
name
|
|
403
|
+
});
|