@zhin.js/plugin-music 7.0.8 → 7.0.10
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/commands/cookie/delete/[source].js +23 -0
- package/commands/cookie/delete/[source].ts +27 -0
- package/commands/cookie/get/[source].js +29 -0
- package/commands/cookie/get/[source].ts +33 -0
- package/commands/cookie/set/[source].js +23 -0
- package/commands/cookie/set/[source].ts +27 -0
- package/commands/cookie/status.js +25 -0
- package/commands/cookie/status.ts +25 -0
- package/commands/login/[platform].js +64 -0
- package/commands/login/[platform].ts +76 -0
- package/commands//347/202/271/346/255/214/[keyword].js +62 -0
- package/commands//347/202/271/346/255/214/[keyword].ts +74 -0
- package/components/share-music.js +6 -6
- package/components/share-music.ts +6 -6
- package/lib/config.d.ts +3 -3
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +36 -7
- package/lib/config.js.map +1 -1
- package/lib/credential-store.d.ts +34 -0
- package/lib/credential-store.d.ts.map +1 -0
- package/lib/credential-store.js +111 -0
- package/lib/credential-store.js.map +1 -0
- package/lib/index.d.ts +5 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -2
- package/lib/index.js.map +1 -1
- package/lib/login/index.d.ts +20 -0
- package/lib/login/index.d.ts.map +1 -0
- package/lib/login/index.js +100 -0
- package/lib/login/index.js.map +1 -0
- package/lib/login/netease.d.ts +6 -0
- package/lib/login/netease.d.ts.map +1 -0
- package/lib/login/netease.js +58 -0
- package/lib/login/netease.js.map +1 -0
- package/lib/login/qq.d.ts +7 -0
- package/lib/login/qq.d.ts.map +1 -0
- package/lib/login/qq.js +139 -0
- package/lib/login/qq.js.map +1 -0
- package/lib/login/types.d.ts +19 -0
- package/lib/login/types.d.ts.map +1 -0
- package/lib/login/types.js +11 -0
- package/lib/login/types.js.map +1 -0
- package/lib/music-lib.d.ts +13 -12
- package/lib/music-lib.d.ts.map +1 -1
- package/lib/music-lib.js +36 -22
- package/lib/music-lib.js.map +1 -1
- package/lib/session.d.ts +29 -0
- package/lib/session.d.ts.map +1 -0
- package/lib/session.js +39 -0
- package/lib/session.js.map +1 -0
- package/lib/sources/index.d.ts +2 -1
- package/lib/sources/index.d.ts.map +1 -1
- package/lib/sources/index.js +6 -2
- package/lib/sources/index.js.map +1 -1
- package/lib/sources/kugou.d.ts +6 -0
- package/lib/sources/kugou.d.ts.map +1 -0
- package/lib/sources/kugou.js +54 -0
- package/lib/sources/kugou.js.map +1 -0
- package/lib/sources/kuwo.d.ts +7 -0
- package/lib/sources/kuwo.d.ts.map +1 -0
- package/lib/sources/kuwo.js +69 -0
- package/lib/sources/kuwo.js.map +1 -0
- package/lib/sources/netease.d.ts +1 -1
- package/lib/sources/netease.d.ts.map +1 -1
- package/lib/sources/netease.js +19 -5
- package/lib/sources/netease.js.map +1 -1
- package/lib/sources/qq.d.ts +1 -14
- package/lib/sources/qq.d.ts.map +1 -1
- package/lib/sources/qq.js +114 -81
- package/lib/sources/qq.js.map +1 -1
- package/lib/types.d.ts +4 -24
- package/lib/types.d.ts.map +1 -1
- package/lib/types.js +0 -1
- package/lib/types.js.map +1 -1
- package/middlewares/music-selection.js +46 -0
- package/middlewares/music-selection.ts +56 -0
- package/package.json +10 -5
- package/plugin.js +24 -1
- package/src/config.ts +54 -30
- package/src/credential-store.ts +169 -0
- package/src/index.ts +43 -2
- package/src/login/index.ts +135 -0
- package/src/login/netease.ts +69 -0
- package/src/login/qq.ts +154 -0
- package/src/login/types.ts +27 -0
- package/src/music-lib.ts +39 -26
- package/src/session.ts +60 -0
- package/src/sources/index.ts +12 -8
- package/src/sources/kugou.ts +78 -0
- package/src/sources/kuwo.ts +94 -0
- package/src/sources/netease.ts +29 -12
- package/src/sources/qq.ts +158 -125
- package/src/types.ts +6 -28
- package/tools/music-search.js +6 -2
- package/tools/music-search.ts +6 -2
- package/tools/music-share.js +6 -2
- package/tools/music-share.ts +6 -2
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { deleteCredential } from "../../../lib/credential-store.js";
|
|
4
|
+
import { SOURCE_DISPLAY_NAME } from "../../../lib/config.js";
|
|
5
|
+
const VALID_SOURCES = new Set(['qq', 'netease', 'kuwo', 'kugou']);
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: '删除音乐平台凭证',
|
|
8
|
+
alias: ['删除'],
|
|
9
|
+
params: { source: { type: 'string', description: '音乐源(qq/netease/kuwo/kugou)' } },
|
|
10
|
+
permit: ['role(master)'],
|
|
11
|
+
async execute({ params, args }) {
|
|
12
|
+
const source = String(params.source ?? '').trim();
|
|
13
|
+
const key = String(args[0] ?? '').trim();
|
|
14
|
+
if (!VALID_SOURCES.has(source)) {
|
|
15
|
+
return `不支持的音乐源:${source}`;
|
|
16
|
+
}
|
|
17
|
+
if (!key) {
|
|
18
|
+
return `格式:cookie delete ${source} <key>`;
|
|
19
|
+
}
|
|
20
|
+
await deleteCredential(source, key);
|
|
21
|
+
return `[${SOURCE_DISPLAY_NAME[source]}] 凭证 ${key} 已删除`;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineCommand } from '@zhin.js/command';
|
|
2
|
+
import { deleteCredential } from '../../../src/credential-store.js';
|
|
3
|
+
import { SOURCE_DISPLAY_NAME } from '../../../src/config.js';
|
|
4
|
+
import type { MusicSource } from '../../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const VALID_SOURCES = new Set<string>(['qq', 'netease', 'kuwo', 'kugou']);
|
|
7
|
+
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
description: '删除音乐平台凭证',
|
|
10
|
+
alias: ['删除'],
|
|
11
|
+
params: { source: { type: 'string', description: '音乐源(qq/netease/kuwo/kugou)' } },
|
|
12
|
+
permit: ['role(master)'],
|
|
13
|
+
async execute({ params, args }) {
|
|
14
|
+
const source = String(params.source ?? '').trim();
|
|
15
|
+
const key = String(args[0] ?? '').trim();
|
|
16
|
+
|
|
17
|
+
if (!VALID_SOURCES.has(source)) {
|
|
18
|
+
return `不支持的音乐源:${source}`;
|
|
19
|
+
}
|
|
20
|
+
if (!key) {
|
|
21
|
+
return `格式:cookie delete ${source} <key>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await deleteCredential(source as MusicSource, key);
|
|
25
|
+
return `[${SOURCE_DISPLAY_NAME[source as MusicSource]}] 凭证 ${key} 已删除`;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { getCredential } from "../../../lib/credential-store.js";
|
|
4
|
+
import { SOURCE_DISPLAY_NAME } from "../../../lib/config.js";
|
|
5
|
+
const VALID_SOURCES = new Set(['qq', 'netease', 'kuwo', 'kugou']);
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: '查看音乐平台凭证(脱敏)',
|
|
8
|
+
alias: ['查看'],
|
|
9
|
+
params: { source: { type: 'string', description: '音乐源(qq/netease/kuwo/kugou)' } },
|
|
10
|
+
permit: ['role(master)'],
|
|
11
|
+
async execute({ params, args }) {
|
|
12
|
+
const source = String(params.source ?? '').trim();
|
|
13
|
+
const key = String(args[0] ?? '').trim();
|
|
14
|
+
if (!VALID_SOURCES.has(source)) {
|
|
15
|
+
return `不支持的音乐源:${source}`;
|
|
16
|
+
}
|
|
17
|
+
if (!key) {
|
|
18
|
+
return `格式:cookie get ${source} <key>`;
|
|
19
|
+
}
|
|
20
|
+
const sourceName = SOURCE_DISPLAY_NAME[source];
|
|
21
|
+
const value = await getCredential(source, key);
|
|
22
|
+
if (!value)
|
|
23
|
+
return `[${sourceName}] 凭证 ${key} 未设置`;
|
|
24
|
+
const masked = value.length > 10
|
|
25
|
+
? `${value.slice(0, 4)}****${value.slice(-4)}`
|
|
26
|
+
: '****';
|
|
27
|
+
return `[${sourceName}] ${key} = ${masked}`;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineCommand } from '@zhin.js/command';
|
|
2
|
+
import { getCredential } from '../../../src/credential-store.js';
|
|
3
|
+
import { SOURCE_DISPLAY_NAME } from '../../../src/config.js';
|
|
4
|
+
import type { MusicSource } from '../../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const VALID_SOURCES = new Set<string>(['qq', 'netease', 'kuwo', 'kugou']);
|
|
7
|
+
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
description: '查看音乐平台凭证(脱敏)',
|
|
10
|
+
alias: ['查看'],
|
|
11
|
+
params: { source: { type: 'string', description: '音乐源(qq/netease/kuwo/kugou)' } },
|
|
12
|
+
permit: ['role(master)'],
|
|
13
|
+
async execute({ params, args }) {
|
|
14
|
+
const source = String(params.source ?? '').trim();
|
|
15
|
+
const key = String(args[0] ?? '').trim();
|
|
16
|
+
|
|
17
|
+
if (!VALID_SOURCES.has(source)) {
|
|
18
|
+
return `不支持的音乐源:${source}`;
|
|
19
|
+
}
|
|
20
|
+
if (!key) {
|
|
21
|
+
return `格式:cookie get ${source} <key>`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sourceName = SOURCE_DISPLAY_NAME[source as MusicSource];
|
|
25
|
+
const value = await getCredential(source as MusicSource, key);
|
|
26
|
+
if (!value) return `[${sourceName}] 凭证 ${key} 未设置`;
|
|
27
|
+
|
|
28
|
+
const masked = value.length > 10
|
|
29
|
+
? `${value.slice(0, 4)}****${value.slice(-4)}`
|
|
30
|
+
: '****';
|
|
31
|
+
return `[${sourceName}] ${key} = ${masked}`;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { setCredential } from "../../../lib/credential-store.js";
|
|
4
|
+
import { SOURCE_DISPLAY_NAME } from "../../../lib/config.js";
|
|
5
|
+
const VALID_SOURCES = new Set(['qq', 'netease', 'kuwo', 'kugou']);
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: '保存音乐平台凭证',
|
|
8
|
+
alias: ['设置'],
|
|
9
|
+
params: { source: { type: 'string', description: '音乐源(qq/netease/kuwo/kugou)' } },
|
|
10
|
+
permit: ['role(master)'],
|
|
11
|
+
async execute({ params, args }) {
|
|
12
|
+
const source = String(params.source ?? '').trim();
|
|
13
|
+
const [key, ...valueParts] = args.map(String);
|
|
14
|
+
if (!VALID_SOURCES.has(source)) {
|
|
15
|
+
return `不支持的音乐源:${source}\n支持:${[...VALID_SOURCES].join(', ')}`;
|
|
16
|
+
}
|
|
17
|
+
if (!key || valueParts.length === 0) {
|
|
18
|
+
return `格式:cookie set ${source} <key> <value>\n示例:cookie set netease cookie MUSIC_U=xxx`;
|
|
19
|
+
}
|
|
20
|
+
await setCredential(source, key, valueParts.join(' '));
|
|
21
|
+
return `[${SOURCE_DISPLAY_NAME[source]}] 凭证 ${key} 已保存`;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineCommand } from '@zhin.js/command';
|
|
2
|
+
import { setCredential } from '../../../src/credential-store.js';
|
|
3
|
+
import { SOURCE_DISPLAY_NAME } from '../../../src/config.js';
|
|
4
|
+
import type { MusicSource } from '../../../src/types.js';
|
|
5
|
+
|
|
6
|
+
const VALID_SOURCES = new Set<string>(['qq', 'netease', 'kuwo', 'kugou']);
|
|
7
|
+
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
description: '保存音乐平台凭证',
|
|
10
|
+
alias: ['设置'],
|
|
11
|
+
params: { source: { type: 'string', description: '音乐源(qq/netease/kuwo/kugou)' } },
|
|
12
|
+
permit: ['role(master)'],
|
|
13
|
+
async execute({ params, args }) {
|
|
14
|
+
const source = String(params.source ?? '').trim();
|
|
15
|
+
const [key, ...valueParts] = args.map(String);
|
|
16
|
+
|
|
17
|
+
if (!VALID_SOURCES.has(source)) {
|
|
18
|
+
return `不支持的音乐源:${source}\n支持:${[...VALID_SOURCES].join(', ')}`;
|
|
19
|
+
}
|
|
20
|
+
if (!key || valueParts.length === 0) {
|
|
21
|
+
return `格式:cookie set ${source} <key> <value>\n示例:cookie set netease cookie MUSIC_U=xxx`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
await setCredential(source as MusicSource, key, valueParts.join(' '));
|
|
25
|
+
return `[${SOURCE_DISPLAY_NAME[source as MusicSource]}] 凭证 ${key} 已保存`;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { listCredentials } from "../../lib/credential-store.js";
|
|
4
|
+
import { SOURCE_DISPLAY_NAME } from "../../lib/config.js";
|
|
5
|
+
export default defineCommand({
|
|
6
|
+
description: '查看各平台凭证配置状态',
|
|
7
|
+
alias: ['状态'],
|
|
8
|
+
permit: ['role(master)'],
|
|
9
|
+
async execute() {
|
|
10
|
+
const allCreds = await listCredentials();
|
|
11
|
+
const lines = ['--- 音乐凭证状态 ---'];
|
|
12
|
+
for (const source of ['qq', 'netease', 'kuwo', 'kugou']) {
|
|
13
|
+
const name = SOURCE_DISPLAY_NAME[source];
|
|
14
|
+
const creds = allCreds.filter((c) => c.source === source);
|
|
15
|
+
if (creds.length === 0) {
|
|
16
|
+
lines.push(`${name}: 未配置`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const keys = creds.map((c) => c.key).join(', ');
|
|
20
|
+
lines.push(`${name}: ${keys} (${creds.length} 项)`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return lines.join('\n');
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineCommand } from '@zhin.js/command';
|
|
2
|
+
import { listCredentials } from '../../src/credential-store.js';
|
|
3
|
+
import { SOURCE_DISPLAY_NAME } from '../../src/config.js';
|
|
4
|
+
import type { MusicSource } from '../../src/types.js';
|
|
5
|
+
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: '查看各平台凭证配置状态',
|
|
8
|
+
alias: ['状态'],
|
|
9
|
+
permit: ['role(master)'],
|
|
10
|
+
async execute() {
|
|
11
|
+
const allCreds = await listCredentials();
|
|
12
|
+
const lines: string[] = ['--- 音乐凭证状态 ---'];
|
|
13
|
+
for (const source of ['qq', 'netease', 'kuwo', 'kugou'] as MusicSource[]) {
|
|
14
|
+
const name = SOURCE_DISPLAY_NAME[source];
|
|
15
|
+
const creds = allCreds.filter((c) => c.source === source);
|
|
16
|
+
if (creds.length === 0) {
|
|
17
|
+
lines.push(`${name}: 未配置`);
|
|
18
|
+
} else {
|
|
19
|
+
const keys = creds.map((c) => c.key).join(', ');
|
|
20
|
+
lines.push(`${name}: ${keys} (${creds.length} 项)`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return lines.join('\n');
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { startLogin, pollLogin, cancelLogin, getActiveLogin, loginSessionKey, } from "../../lib/login/index.js";
|
|
4
|
+
import { resolveMessageIds } from "../../lib/session.js";
|
|
5
|
+
import { SOURCE_DISPLAY_NAME } from "../../lib/config.js";
|
|
6
|
+
const QR_LOGIN_SOURCES = {
|
|
7
|
+
qq: 'qq',
|
|
8
|
+
netease: 'netease',
|
|
9
|
+
};
|
|
10
|
+
export default defineCommand({
|
|
11
|
+
description: '扫码登录音乐平台获取凭证(master 专用)',
|
|
12
|
+
params: {
|
|
13
|
+
platform: { type: 'string', description: '音乐平台(qq / netease / 取消)' },
|
|
14
|
+
},
|
|
15
|
+
permit: ['role(master)'],
|
|
16
|
+
async execute({ params, input }) {
|
|
17
|
+
const platform = String(params.platform ?? '').trim();
|
|
18
|
+
const ids = resolveMessageIds(input);
|
|
19
|
+
if (!ids)
|
|
20
|
+
return '无法识别会话信息,请在群聊或私聊中使用';
|
|
21
|
+
const key = loginSessionKey(ids.endpointId, ids.conversationId, ids.senderId);
|
|
22
|
+
if (platform === '取消' || platform === 'cancel') {
|
|
23
|
+
return cancelLogin(key) ? '已取消登录' : '没有进行中的登录会话';
|
|
24
|
+
}
|
|
25
|
+
const source = QR_LOGIN_SOURCES[platform];
|
|
26
|
+
if (!source) {
|
|
27
|
+
return `不支持的平台:${platform || '(空)'}\n支持:qq, netease`;
|
|
28
|
+
}
|
|
29
|
+
const existing = getActiveLogin(key);
|
|
30
|
+
if (existing) {
|
|
31
|
+
return `正在进行 ${SOURCE_DISPLAY_NAME[existing.source]} 登录,请先完成或发送"音乐登录 取消"`;
|
|
32
|
+
}
|
|
33
|
+
const sourceName = SOURCE_DISPLAY_NAME[source];
|
|
34
|
+
let qrResult;
|
|
35
|
+
try {
|
|
36
|
+
qrResult = await startLogin(source, key);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return `[${sourceName}] 获取二维码失败:${err instanceof Error ? err.message : String(err)}`;
|
|
40
|
+
}
|
|
41
|
+
const replyFn = input?.$reply;
|
|
42
|
+
if (!replyFn)
|
|
43
|
+
return '无法发送消息';
|
|
44
|
+
await replyFn([
|
|
45
|
+
`[${sourceName}] 请使用 ${sourceName} App 扫描二维码登录:\n`,
|
|
46
|
+
qrResult.imageSegment,
|
|
47
|
+
`\n二维码有效期 2 分钟,发送"音乐登录 取消"可中止`,
|
|
48
|
+
]);
|
|
49
|
+
const finalResult = await pollLogin(key, async (result) => {
|
|
50
|
+
if (result.status === 'scanned') {
|
|
51
|
+
await replyFn(`[${sourceName}] ${result.message}`);
|
|
52
|
+
}
|
|
53
|
+
else if (result.status === 'confirmed') {
|
|
54
|
+
await replyFn(`[${sourceName}] 登录成功!cookie 已自动保存,现在可以搜索和播放高品质音乐了`);
|
|
55
|
+
}
|
|
56
|
+
else if (result.status === 'expired' || result.status === 'error') {
|
|
57
|
+
await replyFn(`[${sourceName}] ${result.message}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
if (finalResult.status === 'confirmed')
|
|
61
|
+
return undefined;
|
|
62
|
+
return `[${sourceName}] ${finalResult.message}`;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { defineCommand } from '@zhin.js/command';
|
|
2
|
+
import type { Message } from '@zhin.js/core/runtime';
|
|
3
|
+
import {
|
|
4
|
+
startLogin,
|
|
5
|
+
pollLogin,
|
|
6
|
+
cancelLogin,
|
|
7
|
+
getActiveLogin,
|
|
8
|
+
loginSessionKey,
|
|
9
|
+
type QrLoginSource,
|
|
10
|
+
} from '../../src/login/index.js';
|
|
11
|
+
import { resolveMessageIds } from '../../src/session.js';
|
|
12
|
+
import { SOURCE_DISPLAY_NAME } from '../../src/config.js';
|
|
13
|
+
|
|
14
|
+
const QR_LOGIN_SOURCES: Record<string, QrLoginSource> = {
|
|
15
|
+
qq: 'qq',
|
|
16
|
+
netease: 'netease',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default defineCommand<unknown, string, Message>({
|
|
20
|
+
description: '扫码登录音乐平台获取凭证(master 专用)',
|
|
21
|
+
params: {
|
|
22
|
+
platform: { type: 'string', description: '音乐平台(qq / netease / 取消)' },
|
|
23
|
+
},
|
|
24
|
+
permit: ['role(master)'],
|
|
25
|
+
async execute({ params, input }) {
|
|
26
|
+
const platform = String(params.platform ?? '').trim();
|
|
27
|
+
|
|
28
|
+
const ids = resolveMessageIds(input!);
|
|
29
|
+
if (!ids) return '无法识别会话信息,请在群聊或私聊中使用';
|
|
30
|
+
const key = loginSessionKey(ids.endpointId, ids.conversationId, ids.senderId);
|
|
31
|
+
|
|
32
|
+
if (platform === '取消' || platform === 'cancel') {
|
|
33
|
+
return cancelLogin(key) ? '已取消登录' : '没有进行中的登录会话';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const source = QR_LOGIN_SOURCES[platform];
|
|
37
|
+
if (!source) {
|
|
38
|
+
return `不支持的平台:${platform || '(空)'}\n支持:qq, netease`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const existing = getActiveLogin(key);
|
|
42
|
+
if (existing) {
|
|
43
|
+
return `正在进行 ${SOURCE_DISPLAY_NAME[existing.source]} 登录,请先完成或发送"音乐登录 取消"`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const sourceName = SOURCE_DISPLAY_NAME[source];
|
|
47
|
+
let qrResult;
|
|
48
|
+
try {
|
|
49
|
+
qrResult = await startLogin(source, key);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
return `[${sourceName}] 获取二维码失败:${err instanceof Error ? err.message : String(err)}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const replyFn = input?.$reply;
|
|
55
|
+
if (!replyFn) return '无法发送消息';
|
|
56
|
+
|
|
57
|
+
await replyFn([
|
|
58
|
+
`[${sourceName}] 请使用 ${sourceName} App 扫描二维码登录:\n`,
|
|
59
|
+
qrResult.imageSegment,
|
|
60
|
+
`\n二维码有效期 2 分钟,发送"音乐登录 取消"可中止`,
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const finalResult = await pollLogin(key, async (result) => {
|
|
64
|
+
if (result.status === 'scanned') {
|
|
65
|
+
await replyFn(`[${sourceName}] ${result.message}`);
|
|
66
|
+
} else if (result.status === 'confirmed') {
|
|
67
|
+
await replyFn(`[${sourceName}] 登录成功!cookie 已自动保存,现在可以搜索和播放高品质音乐了`);
|
|
68
|
+
} else if (result.status === 'expired' || result.status === 'error') {
|
|
69
|
+
await replyFn(`[${sourceName}] ${result.message}`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (finalResult.status === 'confirmed') return undefined as unknown as string;
|
|
74
|
+
return `[${sourceName}] ${finalResult.message}`;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineCommand } from '@zhin.js/command';
|
|
3
|
+
import { searchMusic, formatSearchResults } from "../../lib/music-lib.js";
|
|
4
|
+
import { resolveSourceAlias, SOURCE_DISPLAY_NAME } from "../../lib/config.js";
|
|
5
|
+
import { sessionKey, resolveMessageIds, setPending } from "../../lib/session.js";
|
|
6
|
+
export default defineCommand({
|
|
7
|
+
description: '搜索音乐并点歌(支持QQ/网易云/酷我/酷狗)',
|
|
8
|
+
params: { keyword: { type: 'string' } },
|
|
9
|
+
shortcut: {
|
|
10
|
+
'QQ点歌': { keyword: '' },
|
|
11
|
+
'qq点歌': { keyword: '' },
|
|
12
|
+
'网易云点歌': { keyword: '' },
|
|
13
|
+
'酷我点歌': { keyword: '' },
|
|
14
|
+
'酷狗点歌': { keyword: '' },
|
|
15
|
+
},
|
|
16
|
+
async execute({ params, config, input }) {
|
|
17
|
+
const rawKeyword = String(params.keyword ?? '').trim();
|
|
18
|
+
if (!rawKeyword) {
|
|
19
|
+
const sources = Object.values(SOURCE_DISPLAY_NAME).join('/');
|
|
20
|
+
return `请输入歌曲名称,如:点歌 稻香\n支持音乐源:${sources}`;
|
|
21
|
+
}
|
|
22
|
+
let source = config?.defaultSource ?? 'qq';
|
|
23
|
+
let keyword = rawKeyword;
|
|
24
|
+
const triggerText = input?.content?.trim() ?? '';
|
|
25
|
+
for (const [prefix, s] of Object.entries({
|
|
26
|
+
'QQ点歌': 'qq',
|
|
27
|
+
'qq点歌': 'qq',
|
|
28
|
+
'网易云点歌': 'netease',
|
|
29
|
+
'酷我点歌': 'kuwo',
|
|
30
|
+
'酷狗点歌': 'kugou',
|
|
31
|
+
})) {
|
|
32
|
+
if (triggerText.startsWith(prefix)) {
|
|
33
|
+
source = s;
|
|
34
|
+
keyword = triggerText.slice(prefix.length).trim() || rawKeyword;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const sourceFromKeyword = resolveSourceAlias(keyword.split(/\s+/)[0] ?? '');
|
|
39
|
+
if (sourceFromKeyword) {
|
|
40
|
+
source = sourceFromKeyword;
|
|
41
|
+
keyword = keyword.slice(keyword.indexOf(' ') + 1).trim();
|
|
42
|
+
if (!keyword)
|
|
43
|
+
return '请输入歌曲名称';
|
|
44
|
+
}
|
|
45
|
+
const pageSize = config?.pageSize ?? 5;
|
|
46
|
+
const result = await searchMusic(keyword, source, pageSize);
|
|
47
|
+
if (result.total === 0) {
|
|
48
|
+
return `[${SOURCE_DISPLAY_NAME[source]}] 未找到"${keyword}"相关歌曲`;
|
|
49
|
+
}
|
|
50
|
+
const ids = input ? resolveMessageIds(input) : null;
|
|
51
|
+
if (ids) {
|
|
52
|
+
const key = sessionKey(ids.endpointId, ids.conversationId, ids.senderId);
|
|
53
|
+
setPending(key, {
|
|
54
|
+
results: result.results,
|
|
55
|
+
source: result.source,
|
|
56
|
+
keyword,
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return formatSearchResults(result.results, result.source);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { defineCommand } from '@zhin.js/command';
|
|
2
|
+
import type { Message } from '@zhin.js/core/runtime';
|
|
3
|
+
import { searchMusic, formatSearchResults } from '../../src/music-lib.js';
|
|
4
|
+
import { resolveSourceAlias, SOURCE_DISPLAY_NAME } from '../../src/config.js';
|
|
5
|
+
import { sessionKey, resolveMessageIds, setPending } from '../../src/session.js';
|
|
6
|
+
import type { MusicSource } from '../../src/types.js';
|
|
7
|
+
|
|
8
|
+
interface MusicConfig {
|
|
9
|
+
defaultSource?: MusicSource;
|
|
10
|
+
pageSize?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default defineCommand<MusicConfig, string, Message>({
|
|
14
|
+
description: '搜索音乐并点歌(支持QQ/网易云/酷我/酷狗)',
|
|
15
|
+
params: { keyword: { type: 'string' } },
|
|
16
|
+
shortcut: {
|
|
17
|
+
'QQ点歌': { keyword: '' },
|
|
18
|
+
'qq点歌': { keyword: '' },
|
|
19
|
+
'网易云点歌': { keyword: '' },
|
|
20
|
+
'酷我点歌': { keyword: '' },
|
|
21
|
+
'酷狗点歌': { keyword: '' },
|
|
22
|
+
},
|
|
23
|
+
async execute({ params, config, input }) {
|
|
24
|
+
const rawKeyword = String(params.keyword ?? '').trim();
|
|
25
|
+
if (!rawKeyword) {
|
|
26
|
+
const sources = Object.values(SOURCE_DISPLAY_NAME).join('/');
|
|
27
|
+
return `请输入歌曲名称,如:点歌 稻香\n支持音乐源:${sources}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let source: MusicSource = (config as MusicConfig | undefined)?.defaultSource ?? 'qq';
|
|
31
|
+
let keyword = rawKeyword;
|
|
32
|
+
|
|
33
|
+
const triggerText = input?.content?.trim() ?? '';
|
|
34
|
+
for (const [prefix, s] of Object.entries({
|
|
35
|
+
'QQ点歌': 'qq',
|
|
36
|
+
'qq点歌': 'qq',
|
|
37
|
+
'网易云点歌': 'netease',
|
|
38
|
+
'酷我点歌': 'kuwo',
|
|
39
|
+
'酷狗点歌': 'kugou',
|
|
40
|
+
} as Record<string, MusicSource>)) {
|
|
41
|
+
if (triggerText.startsWith(prefix)) {
|
|
42
|
+
source = s;
|
|
43
|
+
keyword = triggerText.slice(prefix.length).trim() || rawKeyword;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sourceFromKeyword = resolveSourceAlias(keyword.split(/\s+/)[0] ?? '');
|
|
49
|
+
if (sourceFromKeyword) {
|
|
50
|
+
source = sourceFromKeyword;
|
|
51
|
+
keyword = keyword.slice(keyword.indexOf(' ') + 1).trim();
|
|
52
|
+
if (!keyword) return '请输入歌曲名称';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pageSize = (config as MusicConfig | undefined)?.pageSize ?? 5;
|
|
56
|
+
const result = await searchMusic(keyword, source, pageSize);
|
|
57
|
+
if (result.total === 0) {
|
|
58
|
+
return `[${SOURCE_DISPLAY_NAME[source]}] 未找到"${keyword}"相关歌曲`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const ids = input ? resolveMessageIds(input) : null;
|
|
62
|
+
if (ids) {
|
|
63
|
+
const key = sessionKey(ids.endpointId, ids.conversationId, ids.senderId);
|
|
64
|
+
setPending(key, {
|
|
65
|
+
results: result.results,
|
|
66
|
+
source: result.source,
|
|
67
|
+
keyword,
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return formatSearchResults(result.results, result.source);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
import { defineComponent } from '@zhin.js/component';
|
|
3
3
|
import { musicServices } from "../lib/sources/index.js";
|
|
4
4
|
import { sourceConfigMap } from "../lib/config.js";
|
|
5
|
-
/**
|
|
6
|
-
* Async music share card. Adapters that understand `share` segments render the
|
|
7
|
-
* rich card; others may fall back to text.
|
|
8
|
-
*/
|
|
9
5
|
export default defineComponent({
|
|
10
6
|
async render({ platform, musicId }) {
|
|
11
7
|
const service = musicServices[platform];
|
|
12
8
|
if (!service)
|
|
13
9
|
return 'unsupported music source';
|
|
14
10
|
const detail = await service.getDetail(musicId);
|
|
15
|
-
const { id: _id, source: _source, ...rest } = detail;
|
|
16
11
|
return {
|
|
17
12
|
type: 'share',
|
|
18
13
|
data: {
|
|
19
|
-
|
|
14
|
+
title: detail.title,
|
|
15
|
+
url: detail.url,
|
|
16
|
+
image: detail.image,
|
|
17
|
+
audio: detail.audio,
|
|
18
|
+
duration: detail.duration,
|
|
19
|
+
artist: detail.artist,
|
|
20
20
|
config: sourceConfigMap[platform],
|
|
21
21
|
},
|
|
22
22
|
};
|
|
@@ -8,20 +8,20 @@ interface ShareMusicProps {
|
|
|
8
8
|
readonly musicId: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* Async music share card. Adapters that understand `share` segments render the
|
|
13
|
-
* rich card; others may fall back to text.
|
|
14
|
-
*/
|
|
15
11
|
export default defineComponent<ShareMusicProps>({
|
|
16
12
|
async render({ platform, musicId }) {
|
|
17
13
|
const service = musicServices[platform];
|
|
18
14
|
if (!service) return 'unsupported music source';
|
|
19
15
|
const detail = await service.getDetail(musicId);
|
|
20
|
-
const { id: _id, source: _source, ...rest } = detail;
|
|
21
16
|
return {
|
|
22
17
|
type: 'share',
|
|
23
18
|
data: {
|
|
24
|
-
|
|
19
|
+
title: detail.title,
|
|
20
|
+
url: detail.url,
|
|
21
|
+
image: detail.image,
|
|
22
|
+
audio: detail.audio,
|
|
23
|
+
duration: detail.duration,
|
|
24
|
+
artist: detail.artist,
|
|
25
25
|
config: sourceConfigMap[platform],
|
|
26
26
|
},
|
|
27
27
|
};
|
package/lib/config.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { MusicSourceConfig, MusicSource } from './types.js';
|
|
2
|
-
/** 音乐源配置映射 */
|
|
3
2
|
export declare const sourceConfigMap: Record<MusicSource, MusicSourceConfig>;
|
|
4
|
-
|
|
3
|
+
export declare const SOURCE_ALIASES: Record<string, MusicSource>;
|
|
4
|
+
export declare const SOURCE_DISPLAY_NAME: Record<MusicSource, string>;
|
|
5
|
+
export declare function resolveSourceAlias(input: string): MusicSource | undefined;
|
|
5
6
|
export declare function formatDuration(seconds?: number): string;
|
|
6
|
-
/** 格式化音乐信息 */
|
|
7
7
|
export declare function formatMusicInfo(music: {
|
|
8
8
|
title: string;
|
|
9
9
|
artist?: string;
|
package/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEjE,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,WAAW,EAAE,iBAAiB,CA6BlE,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAUtD,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAK3D,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEzE;AAED,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAQT"}
|
package/lib/config.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** 音乐源配置映射 */
|
|
2
1
|
export const sourceConfigMap = {
|
|
3
2
|
qq: {
|
|
4
3
|
appid: 100497308,
|
|
@@ -14,8 +13,41 @@ export const sourceConfigMap = {
|
|
|
14
13
|
sign: 'da6b069da1e2982db3e386233f68d76d',
|
|
15
14
|
version: '9.1.92',
|
|
16
15
|
},
|
|
16
|
+
kuwo: {
|
|
17
|
+
appid: 100243533,
|
|
18
|
+
package: 'cn.kuwo.player',
|
|
19
|
+
icon: 'https://p.qpic.cn/qqconnect/0/app_100243533_1636498875/100?max-age=2592000&t=0',
|
|
20
|
+
sign: 'bf9ff4ffb4c558a34ee3fd52c223ebf5',
|
|
21
|
+
version: '10.1.3.5',
|
|
22
|
+
},
|
|
23
|
+
kugou: {
|
|
24
|
+
appid: 205141,
|
|
25
|
+
package: 'com.kugou.android',
|
|
26
|
+
icon: 'https://open.gtimg.cn/open/app_icon/00/20/51/41/205141_100_m.png',
|
|
27
|
+
sign: '5a84ee451a90ca88a1ea0ad2bef9e69c',
|
|
28
|
+
version: '12.2.8',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
export const SOURCE_ALIASES = {
|
|
32
|
+
'qq': 'qq',
|
|
33
|
+
'QQ': 'qq',
|
|
34
|
+
'QQ音乐': 'qq',
|
|
35
|
+
'qq音乐': 'qq',
|
|
36
|
+
'网易': 'netease',
|
|
37
|
+
'网易云': 'netease',
|
|
38
|
+
'163': 'netease',
|
|
39
|
+
'酷我': 'kuwo',
|
|
40
|
+
'酷狗': 'kugou',
|
|
17
41
|
};
|
|
18
|
-
|
|
42
|
+
export const SOURCE_DISPLAY_NAME = {
|
|
43
|
+
qq: 'QQ音乐',
|
|
44
|
+
netease: '网易云',
|
|
45
|
+
kuwo: '酷我',
|
|
46
|
+
kugou: '酷狗',
|
|
47
|
+
};
|
|
48
|
+
export function resolveSourceAlias(input) {
|
|
49
|
+
return SOURCE_ALIASES[input.trim()];
|
|
50
|
+
}
|
|
19
51
|
export function formatDuration(seconds) {
|
|
20
52
|
if (!seconds)
|
|
21
53
|
return '未知';
|
|
@@ -23,15 +55,12 @@ export function formatDuration(seconds) {
|
|
|
23
55
|
const secs = seconds % 60;
|
|
24
56
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
25
57
|
}
|
|
26
|
-
/** 格式化音乐信息 */
|
|
27
58
|
export function formatMusicInfo(music) {
|
|
28
59
|
const parts = [music.title];
|
|
29
|
-
if (music.artist)
|
|
60
|
+
if (music.artist)
|
|
30
61
|
parts.push(music.artist);
|
|
31
|
-
|
|
32
|
-
if (music.album) {
|
|
62
|
+
if (music.album)
|
|
33
63
|
parts.push(music.album);
|
|
34
|
-
}
|
|
35
64
|
const info = parts.join(' - ');
|
|
36
65
|
const duration = formatDuration(music.duration);
|
|
37
66
|
const source = music.source.toUpperCase();
|
package/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAA2C;IACrE,EAAE,EAAE;QACF,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,qBAAqB;QAC9B,IAAI,EAAE,gFAAgF;QACtF,IAAI,EAAE,kCAAkC;QACxC,OAAO,EAAE,WAAW;KACrB;IACD,OAAO,EAAE;QACP,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,wBAAwB;QACjC,IAAI,EAAE,kEAAkE;QACxE,IAAI,EAAE,kCAAkC;QACxC,OAAO,EAAE,QAAQ;KAClB;IACD,IAAI,EAAE;QACJ,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,gBAAgB;QACzB,IAAI,EAAE,gFAAgF;QACtF,IAAI,EAAE,kCAAkC;QACxC,OAAO,EAAE,UAAU;KACpB;IACD,KAAK,EAAE;QACL,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,mBAAmB;QAC5B,IAAI,EAAE,kEAAkE;QACxE,IAAI,EAAE,kCAAkC;QACxC,OAAO,EAAE,QAAQ;KAClB;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAgC;IACzD,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,MAAM,EAAE,IAAI;IACZ,MAAM,EAAE,IAAI;IACZ,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,OAAO;CACd,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAgC;IAC9D,EAAE,EAAE,MAAM;IACV,OAAO,EAAE,KAAK;IACd,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,OAAO,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAgB;IAC7C,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,OAAO,GAAG,EAAE,CAAC;IAC1B,OAAO,GAAG,OAAO,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAM/B;IACC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3C,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;IAC1C,OAAO,GAAG,IAAI,KAAK,QAAQ,MAAM,MAAM,GAAG,CAAC;AAC7C,CAAC"}
|