@zhin.js/plugin-music 1.0.25 → 1.1.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.
Files changed (106) hide show
  1. package/README.md +1 -0
  2. package/commands/cookie/delete/[source].js +23 -0
  3. package/commands/cookie/delete/[source].ts +27 -0
  4. package/commands/cookie/get/[source].js +29 -0
  5. package/commands/cookie/get/[source].ts +33 -0
  6. package/commands/cookie/set/[source].js +23 -0
  7. package/commands/cookie/set/[source].ts +27 -0
  8. package/commands/cookie/status.js +25 -0
  9. package/commands/cookie/status.ts +25 -0
  10. package/commands/login/[platform].js +64 -0
  11. package/commands/login/[platform].ts +76 -0
  12. package/commands//347/202/271/346/255/214/[keyword].js +62 -0
  13. package/commands//347/202/271/346/255/214/[keyword].ts +74 -0
  14. package/components/share-music.js +24 -0
  15. package/components/share-music.ts +29 -0
  16. package/lib/config.d.ts +3 -3
  17. package/lib/config.d.ts.map +1 -1
  18. package/lib/config.js +36 -7
  19. package/lib/config.js.map +1 -1
  20. package/lib/credential-store.d.ts +34 -0
  21. package/lib/credential-store.d.ts.map +1 -0
  22. package/lib/credential-store.js +111 -0
  23. package/lib/credential-store.js.map +1 -0
  24. package/lib/index.d.ts +7 -1
  25. package/lib/index.d.ts.map +1 -1
  26. package/lib/index.js +6 -112
  27. package/lib/index.js.map +1 -1
  28. package/lib/login/index.d.ts +20 -0
  29. package/lib/login/index.d.ts.map +1 -0
  30. package/lib/login/index.js +100 -0
  31. package/lib/login/index.js.map +1 -0
  32. package/lib/login/netease.d.ts +6 -0
  33. package/lib/login/netease.d.ts.map +1 -0
  34. package/lib/login/netease.js +58 -0
  35. package/lib/login/netease.js.map +1 -0
  36. package/lib/login/qq.d.ts +7 -0
  37. package/lib/login/qq.d.ts.map +1 -0
  38. package/lib/login/qq.js +139 -0
  39. package/lib/login/qq.js.map +1 -0
  40. package/lib/login/types.d.ts +19 -0
  41. package/lib/login/types.d.ts.map +1 -0
  42. package/lib/login/types.js +11 -0
  43. package/lib/login/types.js.map +1 -0
  44. package/lib/music-lib.d.ts +31 -0
  45. package/lib/music-lib.d.ts.map +1 -0
  46. package/lib/music-lib.js +59 -0
  47. package/lib/music-lib.js.map +1 -0
  48. package/lib/session.d.ts +29 -0
  49. package/lib/session.d.ts.map +1 -0
  50. package/lib/session.js +39 -0
  51. package/lib/session.js.map +1 -0
  52. package/lib/sources/index.d.ts +2 -1
  53. package/lib/sources/index.d.ts.map +1 -1
  54. package/lib/sources/index.js +6 -2
  55. package/lib/sources/index.js.map +1 -1
  56. package/lib/sources/kugou.d.ts +6 -0
  57. package/lib/sources/kugou.d.ts.map +1 -0
  58. package/lib/sources/kugou.js +54 -0
  59. package/lib/sources/kugou.js.map +1 -0
  60. package/lib/sources/kuwo.d.ts +7 -0
  61. package/lib/sources/kuwo.d.ts.map +1 -0
  62. package/lib/sources/kuwo.js +69 -0
  63. package/lib/sources/kuwo.js.map +1 -0
  64. package/lib/sources/netease.d.ts +1 -1
  65. package/lib/sources/netease.d.ts.map +1 -1
  66. package/lib/sources/netease.js +22 -6
  67. package/lib/sources/netease.js.map +1 -1
  68. package/lib/sources/qq.d.ts +1 -14
  69. package/lib/sources/qq.d.ts.map +1 -1
  70. package/lib/sources/qq.js +114 -82
  71. package/lib/sources/qq.js.map +1 -1
  72. package/lib/types.d.ts +4 -24
  73. package/lib/types.d.ts.map +1 -1
  74. package/lib/types.js +0 -1
  75. package/lib/types.js.map +1 -1
  76. package/middlewares/music-selection.js +46 -0
  77. package/middlewares/music-selection.ts +56 -0
  78. package/package.json +62 -13
  79. package/plugin.js +31 -0
  80. package/schema.json +6 -0
  81. package/src/config.ts +54 -30
  82. package/src/credential-store.ts +169 -0
  83. package/src/index.ts +45 -0
  84. package/src/login/index.ts +135 -0
  85. package/src/login/netease.ts +69 -0
  86. package/src/login/qq.ts +154 -0
  87. package/src/login/types.ts +27 -0
  88. package/src/music-lib.ts +70 -0
  89. package/src/session.ts +60 -0
  90. package/src/sources/index.ts +12 -8
  91. package/src/sources/kugou.ts +78 -0
  92. package/src/sources/kuwo.ts +94 -0
  93. package/src/sources/netease.ts +29 -11
  94. package/src/sources/qq.ts +158 -126
  95. package/src/types.ts +6 -28
  96. package/tools/music-search.js +21 -0
  97. package/tools/music-search.ts +27 -0
  98. package/tools/music-share.js +20 -0
  99. package/tools/music-share.ts +24 -0
  100. package/lib/async-jsx.d.ts +0 -19
  101. package/lib/async-jsx.d.ts.map +0 -1
  102. package/lib/async-jsx.js +0 -48
  103. package/lib/async-jsx.js.map +0 -1
  104. package/src/async-jsx.ts +0 -54
  105. package/src/index.tsx +0 -130
  106. /package/{skills/music/SKILL.md → agent/skills/music.md} +0 -0
package/src/sources/qq.ts CHANGED
@@ -1,148 +1,180 @@
1
- // plugins/utils/music/src/sources/qq.ts
2
- import type { MusicSearchService, MusicDetail,MusicInfo, MusicQQ } from '../types.js'
1
+ import * as crypto from 'node:crypto';
2
+ import type { MusicSearchService, MusicDetail, MusicInfo } from '../types.js';
3
+ import { getCredential } from '../credential-store.js';
4
+
5
+ const md5 = (text: string) => crypto.createHash('md5').update(text).digest('hex');
6
+
7
+ const MUSICU_API = 'https://u.y.qq.com/cgi-bin/musicu.fcg';
8
+
9
+ function getHeaders(cookie?: string | null) {
10
+ return {
11
+ Referer: 'https://y.qq.com',
12
+ Cookie: cookie ?? '',
13
+ 'Content-Type': 'application/json; charset=UTF-8',
14
+ };
15
+ }
3
16
 
4
- /** QQ 音乐搜索服务 */
5
17
  export class QQMusicService implements MusicSearchService {
6
18
  async search(keyword: string, limit = 10): Promise<MusicInfo[]> {
7
19
  try {
8
- const url = new URL('https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg')
9
- url.searchParams.set('key', keyword)
10
- url.searchParams.set('format', 'json')
11
-
12
- const response = await fetch(url, { method: 'GET' })
13
- const data = await response.json() as { data: { song: { itemlist: MusicQQ[] } } }
14
-
15
- const items = data.data?.song?.itemlist || []
16
- return items.slice(0, limit).map(music => ({
17
- id: music.id,
18
- source: 'qq' as const,
19
- title: music.name,
20
- url: `https://y.qq.com/n/yqq/song/${music.mid}.html`
21
- }))
22
- } catch (error) {
23
- console.error('QQ Music search failed:', error)
24
- return []
25
- }
26
- }
20
+ const cookie = await getCredential('qq', 'cookie');
21
+ const response = await fetch(MUSICU_API, {
22
+ method: 'POST',
23
+ headers: getHeaders(cookie),
24
+ body: JSON.stringify({
25
+ comm: { uin: '0', authst: '', ct: 29 },
26
+ search: {
27
+ method: 'DoSearchForQQMusicMobile',
28
+ module: 'music.search.SearchCgiService',
29
+ param: {
30
+ query: keyword,
31
+ grp: 1,
32
+ num_per_page: limit,
33
+ page_num: 1,
34
+ search_type: 0,
35
+ searchid: String(Date.now()),
36
+ },
37
+ },
38
+ }),
39
+ });
27
40
 
28
- async getCover(id: string): Promise<string | null> {
29
- try {
30
- const url = new URL('https://u.y.qq.com/cgi-bin/musicu.fcg')
31
- url.searchParams.set('format', 'json')
32
- url.searchParams.set('inCharset', 'utf8')
33
- url.searchParams.set('outCharset', 'utf-8')
34
- url.searchParams.set('notice', '0')
35
- url.searchParams.set('platform', 'yqq.json')
36
- url.searchParams.set('needNewCode', '0')
37
- url.searchParams.set('data', JSON.stringify({
38
- comm: { ct: 24, cv: 0 },
39
- songinfo: {
40
- method: 'get_song_detail_yqq',
41
- param: { song_type: 0, song_mid: '', song_id: parseInt(id) },
42
- module: 'music.pf_song_detail_svr'
43
- }
44
- }))
45
-
46
- const response = await fetch(url, { method: 'GET' })
47
- const result = await response.json()
48
-
49
- const albumMid = result?.songinfo?.data?.track_info?.album?.mid
50
- if (!albumMid) return null
51
-
52
- return `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`
41
+ const data = (await response.json()) as { code?: number; search?: { data?: { body?: { song?: { list?: any[] }; item_song?: any[] } } } };
42
+ if (data.code !== undefined && +data.code !== 0) return [];
43
+
44
+ const body = data.search?.data?.body ?? {};
45
+ const list = (body.song?.list ?? body.item_song ?? []) as any[];
46
+
47
+ return list.slice(0, limit).map((e) => {
48
+ const mid = e?.mid ?? '';
49
+ const albumMid = e?.album?.pmid ?? e?.album?.mid ?? '';
50
+ const singerMid = e?.singer?.[0]?.pmid ?? e?.singer?.[0]?.mid ?? '';
51
+ const cover = albumMid
52
+ ? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`
53
+ : singerMid
54
+ ? `https://y.qq.com/music/photo_new/T001R300x300M000${singerMid}.jpg`
55
+ : '';
56
+
57
+ return {
58
+ id: String(e.id),
59
+ source: 'qq' as const,
60
+ title: e.name,
61
+ artist: (e.singer ?? []).map((s: any) => s.name).join('/'),
62
+ album: e.album?.name,
63
+ url: `https://y.qq.com/n/ryqq/songDetail/${mid}`,
64
+ image: cover,
65
+ duration: e.interval,
66
+ _mid: mid,
67
+ _mediaMid: e.file?.media_mid,
68
+ };
69
+ });
53
70
  } catch (error) {
54
- console.error('QQ Music get cover failed:', error)
55
- return null
71
+ console.error('QQ Music search failed:', error);
72
+ return [];
56
73
  }
57
74
  }
58
75
 
59
76
  async getDetail(id: string): Promise<MusicDetail> {
60
- const url = new URL('https://u.y.qq.com/cgi-bin/musicu.fcg')
61
- url.searchParams.set('format', 'json')
62
- url.searchParams.set('data', JSON.stringify({
63
- comm: { ct: 24, cv: 0 },
64
- songinfo: {
65
- method: 'get_song_detail_yqq',
66
- param: { song_type: 0, song_mid: '', song_id: parseInt(id) },
67
- module: 'music.pf_song_detail_svr'
68
- }
69
- }))
70
-
71
- const response = await fetch(url, { method: 'GET' })
72
- const result = await response.json()
73
-
74
- const trackInfo = result?.songinfo?.data?.track_info
75
- if (!trackInfo) throw new Error('Music not found')
76
-
77
- const albumMid = trackInfo.album?.mid
78
- const image = albumMid
79
- ? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`
80
- : undefined
81
-
82
- return {
83
- id,
84
- source: 'qq',
85
- title: trackInfo.name,
86
- url: `https://y.qq.com/n/yqq/song/${trackInfo.mid}.html`,
87
- audio: await this.getAudioUrl(id),
88
- image: image || '',
89
- duration: trackInfo.interval,
90
- }
77
+ const url = new URL(MUSICU_API);
78
+ url.searchParams.set('format', 'json');
79
+ url.searchParams.set('data', JSON.stringify({
80
+ comm: { ct: 24, cv: 0 },
81
+ songinfo: {
82
+ method: 'get_song_detail_yqq',
83
+ param: { song_type: 0, song_mid: '', song_id: parseInt(id) },
84
+ module: 'music.pf_song_detail_svr',
85
+ },
86
+ }));
87
+
88
+ const response = await fetch(url, { method: 'GET' });
89
+ const result = await response.json() as any;
90
+
91
+ const trackInfo = result?.songinfo?.data?.track_info;
92
+ if (!trackInfo) throw new Error('Music not found');
93
+
94
+ const albumMid = trackInfo.album?.mid;
95
+ const image = albumMid
96
+ ? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`
97
+ : '';
98
+
99
+ return {
100
+ id,
101
+ source: 'qq',
102
+ title: trackInfo.name,
103
+ url: `https://y.qq.com/n/ryqq/songDetail/${trackInfo.mid}`,
104
+ audio: await this.getAudioUrl(id, trackInfo.mid, trackInfo.file?.media_mid),
105
+ image,
106
+ duration: trackInfo.interval,
107
+ };
91
108
  }
92
109
 
93
- /**
94
- * 获取音频直链(需要第三方 API)
95
- * @param id 音乐 ID
96
- * @param metingAPI Meting API 地址(可选)
97
- * @returns 音频直链 URL
98
- */
99
- async getAudioUrl(id: string, metingAPI?: string): Promise<string> {
100
- // QQ 音乐需要使用第三方 API
101
- const apiUrl = metingAPI || 'https://api.injahow.cn/meting/'
102
- const url = `${apiUrl}?type=url&id=${id}&source=qq`
103
-
104
- const response = await fetch(url, { method: 'GET' })
105
- const data = await response.json() as { url?: string, data?: { url?: string } }
106
- if(!data.url && !data.data?.url) {
107
- throw new Error('Audio URL not found')
110
+ async getAudioUrl(id: string, mid?: string, mediaMid?: string): Promise<string> {
111
+ const cookie = await getCredential('qq', 'cookie');
112
+ if (cookie) {
113
+ try {
114
+ const uin = /uin=o?(\d+)/.exec(cookie)?.[1] ?? '0';
115
+ const songMid = mid ?? id;
116
+ const response = await fetch(MUSICU_API, {
117
+ method: 'POST',
118
+ headers: getHeaders(cookie),
119
+ body: JSON.stringify({
120
+ comm: {
121
+ ct: 19,
122
+ cv: '1891',
123
+ uin: '0',
124
+ guid: md5(`${uin}music`),
125
+ tmeAppID: 'qqmusic',
126
+ tmeLoginType: 2,
127
+ },
128
+ vkey: {
129
+ module: 'vkey.GetVkeyServer',
130
+ method: 'CgiGetVkey',
131
+ param: {
132
+ guid: md5(String(Date.now())),
133
+ songmid: [songMid],
134
+ songtype: [0],
135
+ uin: '0',
136
+ ctx: 1,
137
+ filename: [`C400${id}${mediaMid ?? songMid}.m4a`],
138
+ },
139
+ },
140
+ }),
141
+ });
142
+
143
+ const result = await response.json() as any;
144
+ if (result.vkey && +result.vkey.code === 0) {
145
+ const purl = result.vkey.data?.midurlinfo?.[0]?.purl;
146
+ if (purl) return `https://ws.stream.qqmusic.qq.com/${purl}`;
147
+ }
148
+ } catch {
149
+ // fall through to public fallback
108
150
  }
109
- return data.url || data.data?.url!
151
+ }
152
+
153
+ // 无 cookie 的公开 fallback:md5 签名直链
154
+ const songMid = mid ?? id;
155
+ const code = md5(`${songMid}q;z(&l~sdf2!nK`).substring(0, 5).toUpperCase();
156
+ return `https://c6.y.qq.com/rsc/fcgi-bin/fcg_pyq_play.fcg?songmid=${songMid}&code=${code}`;
110
157
  }
111
158
 
112
- /**
113
- * 获取歌词
114
- * @param id 音乐 ID
115
- * @returns 歌词文本
116
- */
117
159
  async getLyric(id: string): Promise<string | null> {
118
160
  try {
119
- const url = new URL('https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg')
120
- url.searchParams.set('songmid', id)
121
- url.searchParams.set('g_tk', '5381')
122
- url.searchParams.set('format', 'json')
123
- url.searchParams.set('inCharset', 'utf8')
124
- url.searchParams.set('outCharset', 'utf-8')
125
- url.searchParams.set('nobase64', '1')
126
-
127
- const response = await fetch(url, {
161
+ const url = new URL('https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg');
162
+ url.searchParams.set('songmid', id);
163
+ url.searchParams.set('g_tk', '5381');
164
+ url.searchParams.set('format', 'json');
165
+ url.searchParams.set('inCharset', 'utf8');
166
+ url.searchParams.set('outCharset', 'utf-8');
167
+ url.searchParams.set('nobase64', '1');
168
+
169
+ const response = await fetch(url, {
128
170
  method: 'GET',
129
- headers: {
130
- 'Referer': 'https://y.qq.com'
131
- }
132
- })
133
- const data = await response.json() as { lyric?: string }
134
-
135
- if (!data.lyric) return null
136
-
137
- // QQ 音乐返回的歌词需要 base64 解码
138
- try {
139
- return Buffer.from(data.lyric, 'base64').toString('utf-8')
140
- } catch {
141
- return data.lyric
142
- }
171
+ headers: { Referer: 'https://y.qq.com' },
172
+ });
173
+ const data = (await response.json()) as { lyric?: string };
174
+ return data.lyric ?? null;
143
175
  } catch (error) {
144
- console.error('QQ Music get lyric failed:', error)
145
- return null
176
+ console.error('QQ Music get lyric failed:', error);
177
+ return null;
146
178
  }
147
179
  }
148
180
  }
package/src/types.ts CHANGED
@@ -1,24 +1,20 @@
1
- // plugins/utils/music/src/types.ts
1
+ export type MusicSource = 'qq' | 'netease' | 'kuwo' | 'kugou';
2
2
 
3
- /** 音乐源类型 */
4
- export type MusicSource = "qq" | "netease";
5
-
6
- /** 音乐分享内容 */
7
3
  export interface MusicInfo {
8
4
  id: string;
9
5
  source: MusicSource;
10
- /** 跳转链接 */
11
6
  url: string;
12
- /** 音乐标题 */
13
7
  title: string;
8
+ artist?: string;
9
+ album?: string;
10
+ duration?: number;
14
11
  }
12
+
15
13
  export interface MusicDetail extends MusicInfo {
16
14
  image: string;
17
- audio:string;
18
- duration?: number;
15
+ audio: string;
19
16
  }
20
17
 
21
- /** 音乐源配置 */
22
18
  export interface MusicSourceConfig {
23
19
  appid: number;
24
20
  package: string;
@@ -27,21 +23,6 @@ export interface MusicSourceConfig {
27
23
  version: string;
28
24
  }
29
25
 
30
- /** QQ 音乐 API 响应 */
31
- export interface MusicQQ {
32
- id: string;
33
- mid: string;
34
- name: string;
35
- docid: string;
36
- singer: string;
37
- album?: {
38
- mid: string;
39
- name: string;
40
- };
41
- interval?: number;
42
- }
43
-
44
- /** 网易云音乐 API 响应 */
45
26
  export interface Music163 {
46
27
  id: string;
47
28
  name: string;
@@ -58,10 +39,7 @@ export interface Music163 {
58
39
  };
59
40
  }
60
41
 
61
- /** 音乐搜索服务接口 */
62
42
  export interface MusicSearchService {
63
- /** 搜索音乐 */
64
43
  search(keyword: string, limit?: number): Promise<MusicInfo[]>;
65
- /** 获取音乐详情 */
66
44
  getDetail(id: string): Promise<MusicDetail>;
67
45
  }
@@ -0,0 +1,21 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { searchMusic } from "../lib/music-lib.js";
4
+ export default defineAgentTool({
5
+ description: '搜索音乐并返回结果列表(支持QQ/网易云/酷我/酷狗)',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ keyword: { type: 'string', description: '搜索关键词' },
10
+ source: {
11
+ type: 'string',
12
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
13
+ description: '音乐源(默认 qq)',
14
+ },
15
+ limit: { type: 'number', description: '返回结果数量(默认 5)' },
16
+ },
17
+ required: ['keyword'],
18
+ },
19
+ approval: 'never',
20
+ execute: ({ keyword, source, limit }) => searchMusic(String(keyword), source, typeof limit === 'number' ? limit : 5),
21
+ });
@@ -0,0 +1,27 @@
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: '搜索音乐并返回结果列表(支持QQ/网易云/酷我/酷狗)',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ keyword: { type: 'string', description: '搜索关键词' },
15
+ source: {
16
+ type: 'string',
17
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
18
+ description: '音乐源(默认 qq)',
19
+ },
20
+ limit: { type: 'number', description: '返回结果数量(默认 5)' },
21
+ },
22
+ required: ['keyword'],
23
+ },
24
+ approval: 'never',
25
+ execute: ({ keyword, source, limit }) =>
26
+ searchMusic(String(keyword), source, typeof limit === 'number' ? limit : 5),
27
+ });
@@ -0,0 +1,20 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { shareMusicDetail } from "../lib/music-lib.js";
4
+ export default defineAgentTool({
5
+ description: '分享指定的音乐(支持QQ/网易云/酷我/酷狗)',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ id: { type: 'string', description: '音乐 ID' },
10
+ source: {
11
+ type: 'string',
12
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
13
+ description: '音乐源',
14
+ },
15
+ },
16
+ required: ['id', 'source'],
17
+ },
18
+ approval: 'never',
19
+ execute: ({ id, source }) => shareMusicDetail(String(id), source),
20
+ });
@@ -0,0 +1,24 @@
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: '分享指定的音乐(支持QQ/网易云/酷我/酷狗)',
10
+ inputSchema: {
11
+ type: 'object',
12
+ properties: {
13
+ id: { type: 'string', description: '音乐 ID' },
14
+ source: {
15
+ type: 'string',
16
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
17
+ description: '音乐源',
18
+ },
19
+ },
20
+ required: ['id', 'source'],
21
+ },
22
+ approval: 'never',
23
+ execute: ({ id, source }) => shareMusicDetail(String(id), source),
24
+ });
@@ -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
- }