@zhin.js/plugin-music 5.0.2 → 6.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 CHANGED
@@ -286,6 +286,7 @@ export type MusicSource = 'qq' | 'netease' | 'mymusic'
286
286
  2. **版权问题**:仅用于搜索和分享链接,不提供下载功能
287
287
  3. **网络问题**:部分 API 可能需要稳定的网络连接
288
288
  4. **平台兼容**:ICQQ 特定功能仅在 ICQQ 适配器下可用
289
+ 5. **AI 工具平台限制**:旧版 `music-search` / `music-share` 工具曾通过 `.platform('icqq')` 限制仅 icqq 平台可用;现行 `defineAgentTool`(`@zhin.js/tool`)不支持 platform 约束,迁移后工具对所有平台可见
289
290
 
290
291
  ## 🐛 故障排除
291
292
 
@@ -0,0 +1,29 @@
1
+ import { defineComponent } from '@zhin.js/component';
2
+ import { musicServices } from '../src/sources/index.js';
3
+ import { sourceConfigMap } from '../src/config.js';
4
+ import type { MusicSource } from '../src/types.js';
5
+
6
+ interface ShareMusicProps {
7
+ readonly platform: MusicSource;
8
+ readonly musicId: string;
9
+ }
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
+ export default defineComponent<ShareMusicProps>({
16
+ async render({ platform, musicId }) {
17
+ const service = musicServices[platform];
18
+ if (!service) return 'unsupported music source';
19
+ const detail = await service.getDetail(musicId);
20
+ const { id: _id, source: _source, ...rest } = detail;
21
+ return {
22
+ type: 'share',
23
+ data: {
24
+ ...rest,
25
+ config: sourceConfigMap[platform],
26
+ },
27
+ };
28
+ },
29
+ });
package/lib/index.d.ts CHANGED
@@ -1,2 +1,5 @@
1
- export {};
1
+ export { formatDuration, formatMusicInfo, sourceConfigMap } from './config.js';
2
+ export { searchMusic, shareMusicDetail } from './music-lib.js';
3
+ export { musicServices } from './sources/index.js';
4
+ export type { MusicDetail, MusicInfo, MusicSource, MusicSourceConfig } from './types.js';
2
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
package/lib/index.js CHANGED
@@ -1,113 +1,4 @@
1
- import { jsx as _jsx } from "zhin.js/jsx-runtime";
2
- import { formatCompact, defineComponent, usePlugin, ZhinTool } from 'zhin.js';
3
- import { musicServices } from "./sources/index.js";
4
- import { sourceConfigMap } from "./config.js";
5
- const plugin = usePlugin();
6
- const { logger, useContext, addComponent } = plugin;
7
- // 异步组件:分享音乐
8
- const ShareMusic = defineComponent(async function ShareMusic({ platform, musicId }) {
9
- const service = musicServices[platform];
10
- if (!service)
11
- return 'unsupported music source';
12
- const { id, source, ...detail } = await service.getDetail(musicId);
13
- return _jsx("share", { ...detail, config: sourceConfigMap[platform] });
14
- }, 'ShareMusic');
15
- addComponent(ShareMusic);
16
- // Suspense 组件 - 用于包装异步组件
17
- const Suspense = defineComponent(async function Suspense(props, _context) {
18
- try {
19
- // 如果 children 是一个 Promise(异步组件),等待它
20
- if (props.children && typeof props.children === 'object' && 'then' in props.children) {
21
- return await props.children;
22
- }
23
- // 否则直接返回
24
- return props.children || '';
25
- }
26
- catch (error) {
27
- logger.error('Suspense error:', error);
28
- return props.fallback || '加载失败';
29
- }
30
- }, 'Suspense');
31
- addComponent(Suspense);
32
- // ============================================================================
33
- // 点歌工具 (使用 ZhinTool)
34
- // ============================================================================
35
- const searchMusicTool = new ZhinTool('music_search')
36
- .desc('搜索音乐并返回结果列表')
37
- .tag('music', 'entertainment')
38
- .param('keyword', { type: 'string', description: '搜索关键词' }, true)
39
- .param('source', {
40
- type: 'string',
41
- description: '音乐源: qq, netease(默认两者都搜索)',
42
- enum: ['qq', 'netease']
43
- })
44
- .param('limit', { type: 'number', description: '返回结果数量(默认 5)' })
45
- .execute(async ({ keyword, source, limit = 5 }) => {
46
- const keywordStr = keyword;
47
- const limitNum = limit;
48
- // 确定搜索源
49
- const searchSources = source
50
- ? [source]
51
- : ['qq', 'netease'];
52
- logger.debug(formatCompact({ op: 'search', keyword: keywordStr, sources: searchSources.join(',') }));
53
- // 并行搜索
54
- const searchPromises = searchSources.map(s => musicServices[s].search(keywordStr, limitNum));
55
- const searchResults = await Promise.all(searchPromises);
56
- const allMusic = searchResults.flat().filter(Boolean);
57
- return {
58
- success: true,
59
- keyword: keywordStr,
60
- results: allMusic.map(m => ({
61
- id: m.id,
62
- title: m.title,
63
- source: m.source,
64
- url: m.url,
65
- })),
66
- total: allMusic.length,
67
- };
68
- })
69
- .platform('icqq'); // 限制为 icqq 平台
70
- // 分享音乐工具(直接分享指定音乐)
71
- const shareMusicTool = new ZhinTool('music_share')
72
- .desc('分享指定的音乐')
73
- .tag('music', 'entertainment')
74
- .param('id', { type: 'string', description: '音乐 ID' }, true)
75
- .param('source', {
76
- type: 'string',
77
- description: '音乐源: qq 或 netease',
78
- enum: ['qq', 'netease']
79
- }, true)
80
- .platform('icqq')
81
- .execute(async ({ id, source }) => {
82
- const service = musicServices[source];
83
- if (!service) {
84
- return { success: false, error: `不支持的音乐源: ${source}` };
85
- }
86
- try {
87
- const detail = await service.getDetail(id);
88
- return {
89
- success: true,
90
- music: {
91
- id: detail.id,
92
- title: detail.title,
93
- source: detail.source,
94
- url: detail.url,
95
- }
96
- };
97
- }
98
- catch (error) {
99
- return { success: false, error: error instanceof Error ? error.message : String(error) };
100
- }
101
- });
102
- // 注册工具
103
- useContext('tool', (toolService) => {
104
- if (!toolService)
105
- return;
106
- const disposers = [
107
- toolService.addTool(searchMusicTool, 'music'),
108
- toolService.addTool(shareMusicTool, 'music'),
109
- ];
110
- logger.debug('音乐工具已注册');
111
- return () => disposers.forEach(d => d());
112
- });
1
+ export { formatDuration, formatMusicInfo, sourceConfigMap } from './config.js';
2
+ export { searchMusic, shareMusicDetail } from './music-lib.js';
3
+ export { musicServices } from './sources/index.js';
113
4
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { MusicSource } from './types.js';
2
+ export declare function searchMusic(keyword: string, source?: MusicSource, limit?: number): Promise<{
3
+ success: true;
4
+ keyword: string;
5
+ results: Array<{
6
+ id: string;
7
+ title: string;
8
+ source: string;
9
+ url: string;
10
+ }>;
11
+ total: number;
12
+ }>;
13
+ export declare function shareMusicDetail(id: string, source: MusicSource): Promise<{
14
+ success: false;
15
+ error: string;
16
+ music?: undefined;
17
+ } | {
18
+ success: true;
19
+ music: {
20
+ id: string;
21
+ title: string;
22
+ source: MusicSource;
23
+ url: string;
24
+ image: string;
25
+ audio: string;
26
+ duration: number | undefined;
27
+ };
28
+ error?: undefined;
29
+ }>;
30
+ //# sourceMappingURL=music-lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"music-lib.d.ts","sourceRoot":"","sources":["../src/music-lib.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,WAAW,EACpB,KAAK,SAAI,GACR,OAAO,CAAC;IACT,OAAO,EAAE,IAAI,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3E,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAiBD;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;;;;;;;;;GAyBrE"}
@@ -0,0 +1,45 @@
1
+ import { musicServices } from './sources/index.js';
2
+ export async function searchMusic(keyword, source, limit = 5) {
3
+ const searchSources = source ? [source] : ['qq', 'netease'];
4
+ const searchResults = await Promise.all(searchSources.map((s) => musicServices[s].search(keyword, limit)));
5
+ const allMusic = searchResults.flat().filter(Boolean);
6
+ return {
7
+ success: true,
8
+ keyword,
9
+ results: allMusic.map((m) => ({
10
+ id: m.id,
11
+ title: m.title,
12
+ source: m.source,
13
+ url: m.url,
14
+ })),
15
+ total: allMusic.length,
16
+ };
17
+ }
18
+ export async function shareMusicDetail(id, source) {
19
+ const service = musicServices[source];
20
+ if (!service) {
21
+ return { success: false, error: `不支持的音乐源: ${source}` };
22
+ }
23
+ try {
24
+ const detail = await service.getDetail(id);
25
+ return {
26
+ success: true,
27
+ music: {
28
+ id: detail.id,
29
+ title: detail.title,
30
+ source: detail.source,
31
+ url: detail.url,
32
+ image: detail.image,
33
+ audio: detail.audio,
34
+ duration: detail.duration,
35
+ },
36
+ };
37
+ }
38
+ catch (error) {
39
+ return {
40
+ success: false,
41
+ error: error instanceof Error ? error.message : String(error),
42
+ };
43
+ }
44
+ }
45
+ //# sourceMappingURL=music-lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"music-lib.js","sourceRoot":"","sources":["../src/music-lib.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe,EACf,MAAoB,EACpB,KAAK,GAAG,CAAC;IAOT,MAAM,aAAa,GAAkB,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC3E,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,GAAG,CACrC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAClE,CAAC;IACF,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,OAAO,CAAgB,CAAC;IACrE,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO;QACP,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5B,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,GAAG,EAAE,CAAC,CAAC,GAAG;SACX,CAAC,CAAC;QACH,KAAK,EAAE,QAAQ,CAAC,MAAM;KACvB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAU,EAAE,MAAmB;IACpE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,KAAc,EAAE,KAAK,EAAE,YAAY,MAAM,EAAE,EAAE,CAAC;IAClE,CAAC;IACD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC3C,OAAO;YACL,OAAO,EAAE,IAAa;YACtB,KAAK,EAAE;gBACL,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;aAC1B;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAc;YACvB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC;IACJ,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,23 +1,25 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-music",
3
- "version": "5.0.2",
4
- "description": "Music search and sharing plugin for Zhin.js",
3
+ "version": "6.0.0",
4
+ "description": "Music search and sharing plugin for Zhin.js (Plugin Runtime)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
7
7
  "types": "./lib/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./lib/index.d.ts",
11
- "development": "./src/index.tsx",
11
+ "development": "./src/index.ts",
12
12
  "import": "./lib/index.js"
13
13
  },
14
14
  "./package.json": "./package.json"
15
15
  },
16
16
  "files": [
17
+ "plugin.ts",
18
+ "schema.json",
19
+ "components",
20
+ "tools",
17
21
  "src",
18
22
  "lib",
19
- "client",
20
- "dist",
21
23
  "agent",
22
24
  "README.md"
23
25
  ],
@@ -38,15 +40,17 @@
38
40
  "url": "https://github.com/lc-cn"
39
41
  },
40
42
  "license": "MIT",
41
- "dependencies": {},
43
+ "dependencies": {
44
+ "@zhin.js/component": "1.0.2",
45
+ "@zhin.js/plugin-runtime": "1.1.0",
46
+ "@zhin.js/tool": "1.0.2"
47
+ },
42
48
  "devDependencies": {
43
49
  "typescript": "^6.0.3",
44
- "zhin.js": "4.1.2",
45
- "@zhin.js/adapter-icqq": "6.0.2"
50
+ "vitest": "^4.1.10"
46
51
  },
47
52
  "peerDependencies": {
48
- "@zhin.js/adapter-icqq": "6.0.2",
49
- "zhin.js": "4.1.2"
53
+ "@zhin.js/adapter-icqq": "7.0.0"
50
54
  },
51
55
  "peerDependenciesMeta": {
52
56
  "@zhin.js/adapter-icqq": {
@@ -62,8 +66,30 @@
62
66
  "access": "public",
63
67
  "registry": "https://registry.npmjs.org"
64
68
  },
69
+ "engines": {
70
+ "node": "^20.19.0 || >=22.12.0"
71
+ },
72
+ "zhin": {
73
+ "protocol": 1,
74
+ "type": "plugin",
75
+ "entry": "./plugin.ts",
76
+ "engine": "^1.0.0",
77
+ "runtime": "trusted",
78
+ "features": [
79
+ {
80
+ "package": "@zhin.js/component",
81
+ "api": "^1.0.0"
82
+ },
83
+ {
84
+ "package": "@zhin.js/tool",
85
+ "api": "^1.0.0"
86
+ }
87
+ ],
88
+ "plugins": []
89
+ },
65
90
  "scripts": {
66
- "build": "tsc --build",
67
- "clean": "rimraf lib"
91
+ "build": "tsc",
92
+ "clean": "rimraf lib",
93
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/utils/music/tests"
68
94
  }
69
95
  }
package/plugin.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { definePlugin } from '@zhin.js/plugin-runtime';
2
+
3
+ export default definePlugin({
4
+ name: 'music',
5
+ metadata: {
6
+ displayName: 'Music',
7
+ },
8
+ });
package/schema.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "type": "object",
4
+ "additionalProperties": false,
5
+ "properties": {}
6
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { formatDuration, formatMusicInfo, sourceConfigMap } from './config.js';
2
+ export { searchMusic, shareMusicDetail } from './music-lib.js';
3
+ export { musicServices } from './sources/index.js';
4
+ export type { MusicDetail, MusicInfo, MusicSource, MusicSourceConfig } from './types.js';
@@ -0,0 +1,57 @@
1
+ import { musicServices } from './sources/index.js';
2
+ import type { MusicInfo, MusicSource } from './types.js';
3
+
4
+ export async function searchMusic(
5
+ keyword: string,
6
+ source?: MusicSource,
7
+ limit = 5,
8
+ ): Promise<{
9
+ success: true;
10
+ keyword: string;
11
+ results: Array<{ id: string; title: string; source: string; url: string }>;
12
+ total: number;
13
+ }> {
14
+ const searchSources: MusicSource[] = source ? [source] : ['qq', 'netease'];
15
+ const searchResults = await Promise.all(
16
+ searchSources.map((s) => musicServices[s].search(keyword, limit)),
17
+ );
18
+ const allMusic = searchResults.flat().filter(Boolean) as MusicInfo[];
19
+ return {
20
+ success: true,
21
+ keyword,
22
+ results: allMusic.map((m) => ({
23
+ id: m.id,
24
+ title: m.title,
25
+ source: m.source,
26
+ url: m.url,
27
+ })),
28
+ total: allMusic.length,
29
+ };
30
+ }
31
+
32
+ export async function shareMusicDetail(id: string, source: MusicSource) {
33
+ const service = musicServices[source];
34
+ if (!service) {
35
+ return { success: false as const, error: `不支持的音乐源: ${source}` };
36
+ }
37
+ try {
38
+ const detail = await service.getDetail(id);
39
+ return {
40
+ success: true as const,
41
+ music: {
42
+ id: detail.id,
43
+ title: detail.title,
44
+ source: detail.source,
45
+ url: detail.url,
46
+ image: detail.image,
47
+ audio: detail.audio,
48
+ duration: detail.duration,
49
+ },
50
+ };
51
+ } catch (error) {
52
+ return {
53
+ success: false as const,
54
+ error: error instanceof Error ? error.message : String(error),
55
+ };
56
+ }
57
+ }
@@ -0,0 +1,23 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { searchMusic } from '../src/music-lib.js';
3
+ import type { MusicSource } from '../src/types.js';
4
+
5
+ export default defineAgentTool<{
6
+ keyword: string;
7
+ source?: MusicSource;
8
+ limit?: number;
9
+ }>({
10
+ description: '搜索音乐并返回结果列表',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ keyword: { type: 'string', description: '搜索关键词' },
15
+ source: { type: 'string', enum: ['qq', 'netease'] },
16
+ limit: { type: 'number', description: '返回结果数量(默认 5)' },
17
+ },
18
+ required: ['keyword'],
19
+ },
20
+ approval: 'never',
21
+ execute: ({ keyword, source, limit }) =>
22
+ searchMusic(String(keyword), source, typeof limit === 'number' ? limit : 5),
23
+ });
@@ -0,0 +1,20 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { shareMusicDetail } from '../src/music-lib.js';
3
+ import type { MusicSource } from '../src/types.js';
4
+
5
+ export default defineAgentTool<{
6
+ id: string;
7
+ source: MusicSource;
8
+ }>({
9
+ description: '分享指定的音乐',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ id: { type: 'string', description: '音乐 ID' },
14
+ source: { type: 'string', enum: ['qq', 'netease'] },
15
+ },
16
+ required: ['id', 'source'],
17
+ },
18
+ approval: 'never',
19
+ execute: ({ id, source }) => shareMusicDetail(String(id), source),
20
+ });
@@ -1,19 +0,0 @@
1
- import { SendContent } from "zhin.js";
2
- /**
3
- * 异步组件包装器
4
- * 类似 Next.js 的异步组件支持
5
- */
6
- export declare function AsyncComponent<P = any>(Component: (props: P) => Promise<SendContent>): (props: P) => SendContent;
7
- /**
8
- * 渲染异步内容
9
- * 检查是否是异步标记,如果是则执行
10
- */
11
- export declare function renderAsync(content: any): Promise<SendContent>;
12
- /**
13
- * Suspense 组件工厂
14
- * 用于包装异步组件并提供 fallback
15
- */
16
- export declare function createSuspense(fallback?: string): (props: {
17
- children: any;
18
- }) => Promise<SendContent>;
19
- //# sourceMappingURL=async-jsx.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"async-jsx.d.ts","sourceRoot":"","sources":["../src/async-jsx.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,GAAG,EACpC,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,GAC5C,CAAC,KAAK,EAAE,CAAC,KAAK,WAAW,CAc3B;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,WAAW,CAAC,CAQpE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,GAAE,MAAiB,IACzB,OAAO;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,KAAG,OAAO,CAAC,WAAW,CAAC,CAQ/E"}
package/lib/async-jsx.js DELETED
@@ -1,48 +0,0 @@
1
- // async-jsx.ts - 异步 JSX 增强工具
2
- /**
3
- * 异步组件包装器
4
- * 类似 Next.js 的异步组件支持
5
- */
6
- export function AsyncComponent(Component) {
7
- // 返回一个同步函数,但内部处理是异步的
8
- return (props) => {
9
- // 创建一个特殊的标记对象
10
- const asyncMarker = {
11
- __async: true,
12
- component: Component,
13
- props,
14
- execute: () => Component(props)
15
- };
16
- // TypeScript 类型断言
17
- return asyncMarker;
18
- };
19
- }
20
- /**
21
- * 渲染异步内容
22
- * 检查是否是异步标记,如果是则执行
23
- */
24
- export async function renderAsync(content) {
25
- if (content && typeof content === 'object' && content.__async) {
26
- return await content.execute();
27
- }
28
- if (content && typeof content.then === 'function') {
29
- return await content;
30
- }
31
- return content;
32
- }
33
- /**
34
- * Suspense 组件工厂
35
- * 用于包装异步组件并提供 fallback
36
- */
37
- export function createSuspense(fallback = '加载中...') {
38
- return async function Suspense(props) {
39
- try {
40
- return await renderAsync(props.children);
41
- }
42
- catch (error) {
43
- console.error('Suspense error:', error);
44
- return fallback;
45
- }
46
- };
47
- }
48
- //# sourceMappingURL=async-jsx.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"async-jsx.js","sourceRoot":"","sources":["../src/async-jsx.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAI7B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,SAA6C;IAE7C,qBAAqB;IACrB,OAAO,CAAC,KAAQ,EAAE,EAAE;QAClB,cAAc;QACd,MAAM,WAAW,GAAG;YAClB,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,SAAS;YACpB,KAAK;YACL,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC;SAChC,CAAC;QAEF,kBAAkB;QAClB,OAAO,WAAiC,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAY;IAC5C,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAC9D,OAAO,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC;IACD,IAAI,OAAO,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAClD,OAAO,MAAM,OAAO,CAAC;IACvB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB,QAAQ;IACxD,OAAO,KAAK,UAAU,QAAQ,CAAC,KAAwB;QACrD,IAAI,CAAC;YACH,OAAO,MAAM,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;YACxC,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
package/src/async-jsx.ts DELETED
@@ -1,54 +0,0 @@
1
- // async-jsx.ts - 异步 JSX 增强工具
2
-
3
- import { SendContent } from "zhin.js";
4
-
5
- /**
6
- * 异步组件包装器
7
- * 类似 Next.js 的异步组件支持
8
- */
9
- export function AsyncComponent<P = any>(
10
- Component: (props: P) => Promise<SendContent>
11
- ): (props: P) => SendContent {
12
- // 返回一个同步函数,但内部处理是异步的
13
- return (props: P) => {
14
- // 创建一个特殊的标记对象
15
- const asyncMarker = {
16
- __async: true,
17
- component: Component,
18
- props,
19
- execute: () => Component(props)
20
- };
21
-
22
- // TypeScript 类型断言
23
- return asyncMarker as any as SendContent;
24
- };
25
- }
26
-
27
- /**
28
- * 渲染异步内容
29
- * 检查是否是异步标记,如果是则执行
30
- */
31
- export async function renderAsync(content: any): Promise<SendContent> {
32
- if (content && typeof content === 'object' && content.__async) {
33
- return await content.execute();
34
- }
35
- if (content && typeof content.then === 'function') {
36
- return await content;
37
- }
38
- return content;
39
- }
40
-
41
- /**
42
- * Suspense 组件工厂
43
- * 用于包装异步组件并提供 fallback
44
- */
45
- export function createSuspense(fallback: string = '加载中...') {
46
- return async function Suspense(props: { children: any }): Promise<SendContent> {
47
- try {
48
- return await renderAsync(props.children);
49
- } catch (error) {
50
- console.error('Suspense error:', error);
51
- return fallback;
52
- }
53
- };
54
- }
package/src/index.tsx DELETED
@@ -1,130 +0,0 @@
1
- import { formatCompact, defineComponent, usePlugin, ZhinTool } from 'zhin.js';
2
- import { musicServices } from "./sources/index.js";
3
- import { sourceConfigMap } from "./config.js";
4
- import type { MusicSource } from "./types.js";
5
-
6
- const plugin = usePlugin();
7
- const { logger, useContext, addComponent } = plugin;
8
-
9
- // 异步组件:分享音乐
10
- const ShareMusic = defineComponent(async function ShareMusic({ platform, musicId }: { platform: MusicSource, musicId: string }) {
11
- const service = musicServices[platform];
12
- if (!service) return 'unsupported music source';
13
- const { id, source, ...detail } = await service.getDetail(musicId);
14
- return <share {...detail} config={sourceConfigMap[platform]} />
15
- }, 'ShareMusic')
16
- addComponent(ShareMusic)
17
-
18
- // Suspense 组件 - 用于包装异步组件
19
- const Suspense = defineComponent(async function Suspense(
20
- props: { fallback?: string; children?: any },
21
- _context
22
- ) {
23
- try {
24
- // 如果 children 是一个 Promise(异步组件),等待它
25
- if (props.children && typeof props.children === 'object' && 'then' in props.children) {
26
- return await props.children;
27
- }
28
- // 否则直接返回
29
- return props.children || '';
30
- } catch (error) {
31
- logger.error('Suspense error:', error);
32
- return props.fallback || '加载失败';
33
- }
34
- }, 'Suspense');
35
-
36
- addComponent(Suspense);
37
-
38
- // ============================================================================
39
- // 点歌工具 (使用 ZhinTool)
40
- // ============================================================================
41
-
42
- const searchMusicTool = new ZhinTool('music_search')
43
- .desc('搜索音乐并返回结果列表')
44
- .tag('music', 'entertainment')
45
- .param('keyword', { type: 'string', description: '搜索关键词' }, true)
46
- .param('source', {
47
- type: 'string',
48
- description: '音乐源: qq, netease(默认两者都搜索)',
49
- enum: ['qq', 'netease']
50
- })
51
- .param('limit', { type: 'number', description: '返回结果数量(默认 5)' })
52
- .execute(async ({ keyword, source, limit = 5 }) => {
53
- const keywordStr = keyword as string;
54
- const limitNum = limit as number;
55
-
56
- // 确定搜索源
57
- const searchSources: MusicSource[] = source
58
- ? [source as MusicSource]
59
- : ['qq', 'netease'];
60
-
61
- logger.debug(formatCompact( { op: 'search', keyword: keywordStr, sources: searchSources.join(',') }));
62
-
63
- // 并行搜索
64
- const searchPromises = searchSources.map(s =>
65
- musicServices[s].search(keywordStr, limitNum)
66
- );
67
-
68
- const searchResults = await Promise.all(searchPromises);
69
- const allMusic = searchResults.flat().filter(Boolean);
70
-
71
- return {
72
- success: true,
73
- keyword: keywordStr,
74
- results: allMusic.map(m => ({
75
- id: m.id,
76
- title: m.title,
77
- source: m.source,
78
- url: m.url,
79
- })),
80
- total: allMusic.length,
81
- };
82
- })
83
- .platform('icqq'); // 限制为 icqq 平台
84
-
85
- // 分享音乐工具(直接分享指定音乐)
86
- const shareMusicTool = new ZhinTool('music_share')
87
- .desc('分享指定的音乐')
88
- .tag('music', 'entertainment')
89
- .param('id', { type: 'string', description: '音乐 ID' }, true)
90
- .param('source', {
91
- type: 'string',
92
- description: '音乐源: qq 或 netease',
93
- enum: ['qq', 'netease']
94
- }, true)
95
- .platform('icqq')
96
- .execute(async ({ id, source }) => {
97
- const service = musicServices[source as MusicSource];
98
- if (!service) {
99
- return { success: false, error: `不支持的音乐源: ${source}` };
100
- }
101
-
102
- try {
103
- const detail = await service.getDetail(id as string);
104
- return {
105
- success: true,
106
- music: {
107
- id: detail.id,
108
- title: detail.title,
109
- source: detail.source,
110
- url: detail.url,
111
- }
112
- };
113
- } catch (error) {
114
- return { success: false, error: error instanceof Error ? error.message : String(error) };
115
- }
116
- });
117
-
118
- // 注册工具
119
- useContext('tool', (toolService) => {
120
- if (!toolService) return;
121
-
122
- const disposers = [
123
- toolService.addTool(searchMusicTool, 'music'),
124
- toolService.addTool(shareMusicTool, 'music'),
125
- ];
126
-
127
- logger.debug('音乐工具已注册');
128
-
129
- return () => disposers.forEach(d => d());
130
- });