@zhin.js/plugin-music 1.0.26 → 1.1.2

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 (111) hide show
  1. package/README.md +28 -27
  2. package/commands/cookie/delete/[source]/index.js +23 -0
  3. package/commands/cookie/delete/[source]/index.ts +27 -0
  4. package/commands/cookie/get/[source]/index.js +29 -0
  5. package/commands/cookie/get/[source]/index.ts +33 -0
  6. package/commands/cookie/set/[source]/index.js +23 -0
  7. package/commands/cookie/set/[source]/index.ts +27 -0
  8. package/commands/cookie/status/index.js +25 -0
  9. package/commands/cookie/status/index.ts +25 -0
  10. package/commands/login/[platform]/index.js +66 -0
  11. package/commands/login/[platform]/index.ts +74 -0
  12. package/commands//347/202/271/346/255/214/[keyword]/index.js +63 -0
  13. package/commands//347/202/271/346/255/214/[keyword]/index.ts +75 -0
  14. package/components/share-music/index.js +24 -0
  15. package/components/share-music/index.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 +37 -0
  21. package/lib/credential-store.d.ts.map +1 -0
  22. package/lib/credential-store.js +85 -0
  23. package/lib/credential-store.js.map +1 -0
  24. package/lib/index.d.ts +8 -1
  25. package/lib/index.d.ts.map +1 -1
  26. package/lib/index.js +7 -112
  27. package/lib/index.js.map +1 -1
  28. package/lib/login/index.d.ts +27 -0
  29. package/lib/login/index.d.ts.map +1 -0
  30. package/lib/login/index.js +124 -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 +32 -0
  45. package/lib/music-lib.d.ts.map +1 -0
  46. package/lib/music-lib.js +58 -0
  47. package/lib/music-lib.js.map +1 -0
  48. package/lib/runtime.d.ts +12 -0
  49. package/lib/runtime.d.ts.map +1 -0
  50. package/lib/runtime.js +3 -0
  51. package/lib/runtime.js.map +1 -0
  52. package/lib/session.d.ts +35 -0
  53. package/lib/session.d.ts.map +1 -0
  54. package/lib/session.js +51 -0
  55. package/lib/session.js.map +1 -0
  56. package/lib/sources/index.d.ts +4 -2
  57. package/lib/sources/index.d.ts.map +1 -1
  58. package/lib/sources/index.js +12 -6
  59. package/lib/sources/index.js.map +1 -1
  60. package/lib/sources/kugou.d.ts +6 -0
  61. package/lib/sources/kugou.d.ts.map +1 -0
  62. package/lib/sources/kugou.js +69 -0
  63. package/lib/sources/kugou.js.map +1 -0
  64. package/lib/sources/kuwo.d.ts +7 -0
  65. package/lib/sources/kuwo.d.ts.map +1 -0
  66. package/lib/sources/kuwo.js +69 -0
  67. package/lib/sources/kuwo.js.map +1 -0
  68. package/lib/sources/netease.d.ts +4 -1
  69. package/lib/sources/netease.d.ts.map +1 -1
  70. package/lib/sources/netease.js +25 -6
  71. package/lib/sources/netease.js.map +1 -1
  72. package/lib/sources/qq.d.ts +4 -14
  73. package/lib/sources/qq.d.ts.map +1 -1
  74. package/lib/sources/qq.js +117 -82
  75. package/lib/sources/qq.js.map +1 -1
  76. package/lib/types.d.ts +4 -24
  77. package/lib/types.d.ts.map +1 -1
  78. package/lib/types.js +0 -1
  79. package/lib/types.js.map +1 -1
  80. package/middlewares/music-selection/index.js +48 -0
  81. package/middlewares/music-selection/index.ts +62 -0
  82. package/package.json +66 -12
  83. package/plugin.js +45 -0
  84. package/schema.json +6 -0
  85. package/skills/music/SKILL.md +2 -2
  86. package/skills/music/tools/music-search/index.js +22 -0
  87. package/skills/music/tools/music-search/index.ts +33 -0
  88. package/skills/music/tools/music-share/index.js +21 -0
  89. package/skills/music/tools/music-share/index.ts +26 -0
  90. package/src/config.ts +54 -30
  91. package/src/credential-store.ts +125 -0
  92. package/src/index.ts +37 -0
  93. package/src/login/index.ts +154 -0
  94. package/src/login/netease.ts +69 -0
  95. package/src/login/qq.ts +154 -0
  96. package/src/login/types.ts +27 -0
  97. package/src/music-lib.ts +71 -0
  98. package/src/runtime.ts +17 -0
  99. package/src/session.ts +77 -0
  100. package/src/sources/index.ts +17 -10
  101. package/src/sources/kugou.ts +92 -0
  102. package/src/sources/kuwo.ts +94 -0
  103. package/src/sources/netease.ts +35 -11
  104. package/src/sources/qq.ts +164 -126
  105. package/src/types.ts +6 -28
  106. package/lib/async-jsx.d.ts +0 -19
  107. package/lib/async-jsx.d.ts.map +0 -1
  108. package/lib/async-jsx.js +0 -48
  109. package/lib/async-jsx.js.map +0 -1
  110. package/src/async-jsx.ts +0 -54
  111. package/src/index.tsx +0 -130
package/package.json CHANGED
@@ -1,23 +1,27 @@
1
1
  {
2
2
  "name": "@zhin.js/plugin-music",
3
- "version": "1.0.26",
4
- "description": "Music search and sharing plugin for Zhin.js",
3
+ "version": "1.1.2",
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.js",
18
+ "schema.json",
19
+ "commands",
20
+ "middlewares",
21
+ "components",
17
22
  "src",
18
23
  "lib",
19
- "client",
20
- "dist",
24
+ "tools",
21
25
  "skills",
22
26
  "README.md"
23
27
  ],
@@ -38,19 +42,35 @@
38
42
  "url": "https://github.com/lc-cn"
39
43
  },
40
44
  "license": "MIT",
41
- "dependencies": {},
45
+ "dependencies": {
46
+ "@zhin.js/core": "1.1.37",
47
+ "@zhin.js/tool": "1.1.1",
48
+ "@zhin.js/skill": "1.1.1"
49
+ },
42
50
  "devDependencies": {
43
51
  "typescript": "^6.0.3",
44
- "zhin.js": "1.0.81",
45
- "@zhin.js/adapter-icqq": "2.0.26"
52
+ "vitest": "^4.1.10",
53
+ "zhin.js": "1.1.2"
46
54
  },
47
55
  "peerDependencies": {
48
- "@zhin.js/adapter-icqq": "2.0.26",
49
- "zhin.js": "1.0.81"
56
+ "@zhin.js/adapter-icqq": "^1.1.2",
57
+ "@zhin.js/command": "^1.1.1",
58
+ "@zhin.js/component": "^1.1.1",
59
+ "@zhin.js/middleware": "^1.1.1",
60
+ "zhin.js": "^1.1.2"
50
61
  },
51
62
  "peerDependenciesMeta": {
52
63
  "@zhin.js/adapter-icqq": {
53
64
  "optional": true
65
+ },
66
+ "@zhin.js/command": {
67
+ "optional": true
68
+ },
69
+ "@zhin.js/component": {
70
+ "optional": true
71
+ },
72
+ "@zhin.js/middleware": {
73
+ "optional": true
54
74
  }
55
75
  },
56
76
  "repository": {
@@ -62,8 +82,42 @@
62
82
  "access": "public",
63
83
  "registry": "https://registry.npmjs.org"
64
84
  },
85
+ "engines": {
86
+ "node": "^20.19.0 || >=22.12.0"
87
+ },
88
+ "zhin": {
89
+ "protocol": 1,
90
+ "type": "plugin",
91
+ "entry": "./plugin.js",
92
+ "engine": "^1.0.0",
93
+ "runtime": "trusted",
94
+ "features": [
95
+ {
96
+ "package": "@zhin.js/command",
97
+ "api": "^1.0.0"
98
+ },
99
+ {
100
+ "package": "@zhin.js/middleware",
101
+ "api": "^1.0.0"
102
+ },
103
+ {
104
+ "package": "@zhin.js/component",
105
+ "api": "^1.0.0"
106
+ },
107
+ {
108
+ "package": "@zhin.js/tool",
109
+ "api": "^1.0.0"
110
+ },
111
+ {
112
+ "package": "@zhin.js/skill",
113
+ "api": "^1.0.0"
114
+ }
115
+ ],
116
+ "plugins": []
117
+ },
65
118
  "scripts": {
66
- "build": "tsc --build",
67
- "clean": "rimraf lib"
119
+ "build": "tsc",
120
+ "clean": "rimraf lib",
121
+ "test": "NODE_OPTIONS=--experimental-strip-types vitest run --root ../../.. plugins/utils/music/tests"
68
122
  }
69
123
  }
package/plugin.js ADDED
@@ -0,0 +1,45 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { definePlugin, databaseHostToken } from 'zhin.js';
3
+ import { MusicSearchSessions } from "./lib/session.js";
4
+ import { QrLoginRuntime } from "./lib/login/index.js";
5
+ import { CredentialStore, createInMemoryCredentialDb, MUSIC_CREDENTIALS_TABLE, } from "./lib/credential-store.js";
6
+ import { createMusicServices } from "./lib/sources/index.js";
7
+ import { musicRuntimeToken } from "./lib/runtime.js";
8
+ function defineCredentialTable(db) {
9
+ db.define(MUSIC_CREDENTIALS_TABLE, {
10
+ source: { type: 'text', nullable: false },
11
+ key: { type: 'text', nullable: false },
12
+ value: { type: 'text', nullable: false },
13
+ updated_at: { type: 'text', default: '' },
14
+ });
15
+ }
16
+ export default definePlugin({
17
+ name: 'music',
18
+ metadata: {
19
+ displayName: 'Music',
20
+ },
21
+ setup(context) {
22
+ const db = context.resources.has(databaseHostToken) ? (() => {
23
+ const host = context.resources.use(databaseHostToken);
24
+ defineCredentialTable(host);
25
+ return host;
26
+ })() : createInMemoryCredentialDb();
27
+ const credentials = new CredentialStore(db);
28
+ const runtime = Object.freeze({
29
+ credentials,
30
+ services: Object.freeze(createMusicServices(credentials)),
31
+ sessions: new MusicSearchSessions(),
32
+ logins: new QrLoginRuntime(credentials),
33
+ });
34
+ context.resources.provide(musicRuntimeToken, runtime);
35
+ const timer = setInterval(() => {
36
+ runtime.sessions.pruneExpired();
37
+ runtime.logins.pruneExpired();
38
+ }, 60_000);
39
+ return () => {
40
+ clearInterval(timer);
41
+ runtime.sessions.dispose();
42
+ runtime.logins.dispose();
43
+ };
44
+ },
45
+ });
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
+ }
@@ -19,8 +19,8 @@ tags:
19
19
  - entertainment
20
20
  - media
21
21
  tools:
22
- - music_search
23
- - music_share
22
+ - music-search
23
+ - music-share
24
24
  ---
25
25
 
26
26
  # 音乐搜索分享技能
@@ -0,0 +1,22 @@
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
+ import { musicRuntimeToken } from "../../../../lib/runtime.js";
5
+ export default defineAgentTool({
6
+ description: '搜索音乐并返回结果列表(支持QQ/网易云/酷我/酷狗)',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ keyword: { type: 'string', description: '搜索关键词' },
11
+ source: {
12
+ type: 'string',
13
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
14
+ description: '音乐源(默认 qq)',
15
+ },
16
+ limit: { type: 'number', description: '返回结果数量(默认 5)' },
17
+ },
18
+ required: ['keyword'],
19
+ },
20
+ requiresApproval: 'never',
21
+ execute: ({ keyword, source, limit }, context) => searchMusic(context.use(musicRuntimeToken), String(keyword), source, typeof limit === 'number' ? limit : 5),
22
+ });
@@ -0,0 +1,33 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { searchMusic } from '../../../../src/music-lib.js';
3
+ import type { MusicSource } from '../../../../src/types.js';
4
+ import { musicRuntimeToken } from '../../../../src/runtime.js';
5
+
6
+ export default defineAgentTool<{
7
+ keyword: string;
8
+ source?: MusicSource;
9
+ limit?: number;
10
+ }>({
11
+ description: '搜索音乐并返回结果列表(支持QQ/网易云/酷我/酷狗)',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ keyword: { type: 'string', description: '搜索关键词' },
16
+ source: {
17
+ type: 'string',
18
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
19
+ description: '音乐源(默认 qq)',
20
+ },
21
+ limit: { type: 'number', description: '返回结果数量(默认 5)' },
22
+ },
23
+ required: ['keyword'],
24
+ },
25
+ requiresApproval: 'never',
26
+ execute: ({ keyword, source, limit }, context) =>
27
+ searchMusic(
28
+ context.use(musicRuntimeToken),
29
+ String(keyword),
30
+ source,
31
+ typeof limit === 'number' ? limit : 5,
32
+ ),
33
+ });
@@ -0,0 +1,21 @@
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
+ import { musicRuntimeToken } from "../../../../lib/runtime.js";
5
+ export default defineAgentTool({
6
+ description: '分享指定的音乐(支持QQ/网易云/酷我/酷狗)',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ id: { type: 'string', description: '音乐 ID' },
11
+ source: {
12
+ type: 'string',
13
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
14
+ description: '音乐源',
15
+ },
16
+ },
17
+ required: ['id', 'source'],
18
+ },
19
+ requiresApproval: 'never',
20
+ execute: ({ id, source }, context) => shareMusicDetail(context.use(musicRuntimeToken), String(id), source),
21
+ });
@@ -0,0 +1,26 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { shareMusicDetail } from '../../../../src/music-lib.js';
3
+ import type { MusicSource } from '../../../../src/types.js';
4
+ import { musicRuntimeToken } from '../../../../src/runtime.js';
5
+
6
+ export default defineAgentTool<{
7
+ id: string;
8
+ source: MusicSource;
9
+ }>({
10
+ description: '分享指定的音乐(支持QQ/网易云/酷我/酷狗)',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ id: { type: 'string', description: '音乐 ID' },
15
+ source: {
16
+ type: 'string',
17
+ enum: ['qq', 'netease', 'kuwo', 'kugou'],
18
+ description: '音乐源',
19
+ },
20
+ },
21
+ required: ['id', 'source'],
22
+ },
23
+ requiresApproval: 'never',
24
+ execute: ({ id, source }, context) =>
25
+ shareMusicDetail(context.use(musicRuntimeToken), String(id), source),
26
+ });
package/src/config.ts CHANGED
@@ -1,7 +1,5 @@
1
- // plugins/utils/music/src/config.ts
2
- import type { MusicSourceConfig,MusicSource } from './types.js'
1
+ import type { MusicSourceConfig, MusicSource } from './types.js';
3
2
 
4
- /** 音乐源配置映射 */
5
3
  export const sourceConfigMap: Record<MusicSource, MusicSourceConfig> = {
6
4
  qq: {
7
5
  appid: 100497308,
@@ -17,38 +15,64 @@ export const sourceConfigMap: Record<MusicSource, MusicSourceConfig> = {
17
15
  sign: 'da6b069da1e2982db3e386233f68d76d',
18
16
  version: '9.1.92',
19
17
  },
18
+ kuwo: {
19
+ appid: 100243533,
20
+ package: 'cn.kuwo.player',
21
+ icon: 'https://p.qpic.cn/qqconnect/0/app_100243533_1636498875/100?max-age=2592000&t=0',
22
+ sign: 'bf9ff4ffb4c558a34ee3fd52c223ebf5',
23
+ version: '10.1.3.5',
24
+ },
25
+ kugou: {
26
+ appid: 205141,
27
+ package: 'com.kugou.android',
28
+ icon: 'https://open.gtimg.cn/open/app_icon/00/20/51/41/205141_100_m.png',
29
+ sign: '5a84ee451a90ca88a1ea0ad2bef9e69c',
30
+ version: '12.2.8',
31
+ },
32
+ };
33
+
34
+ export const SOURCE_ALIASES: Record<string, MusicSource> = {
35
+ 'qq': 'qq',
36
+ 'QQ': 'qq',
37
+ 'QQ音乐': 'qq',
38
+ 'qq音乐': 'qq',
39
+ '网易': 'netease',
40
+ '网易云': 'netease',
41
+ '163': 'netease',
42
+ '酷我': 'kuwo',
43
+ '酷狗': 'kugou',
44
+ };
45
+
46
+ export const SOURCE_DISPLAY_NAME: Record<MusicSource, string> = {
47
+ qq: 'QQ音乐',
48
+ netease: '网易云',
49
+ kuwo: '酷我',
50
+ kugou: '酷狗',
51
+ };
52
+
53
+ export function resolveSourceAlias(input: string): MusicSource | undefined {
54
+ return SOURCE_ALIASES[input.trim()];
20
55
  }
21
56
 
22
- /** 格式化时长 */
23
57
  export function formatDuration(seconds?: number): string {
24
- if (!seconds) return '未知'
25
-
26
- const minutes = Math.floor(seconds / 60)
27
- const secs = seconds % 60
28
- return `${minutes}:${secs.toString().padStart(2, '0')}`
58
+ if (!seconds) return '未知';
59
+ const minutes = Math.floor(seconds / 60);
60
+ const secs = seconds % 60;
61
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
29
62
  }
30
63
 
31
- /** 格式化音乐信息 */
32
64
  export function formatMusicInfo(music: {
33
- title: string
34
- artist?: string
35
- album?: string
36
- duration?: number
37
- source: string
65
+ title: string;
66
+ artist?: string;
67
+ album?: string;
68
+ duration?: number;
69
+ source: string;
38
70
  }): string {
39
- const parts = [music.title]
40
-
41
- if (music.artist) {
42
- parts.push(music.artist)
43
- }
44
-
45
- if (music.album) {
46
- parts.push(music.album)
47
- }
48
-
49
- const info = parts.join(' - ')
50
- const duration = formatDuration(music.duration)
51
- const source = music.source.toUpperCase()
52
-
53
- return `${info} [${duration}] [${source}]`
71
+ const parts = [music.title];
72
+ if (music.artist) parts.push(music.artist);
73
+ if (music.album) parts.push(music.album);
74
+ const info = parts.join(' - ');
75
+ const duration = formatDuration(music.duration);
76
+ const source = music.source.toUpperCase();
77
+ return `${info} [${duration}] [${source}]`;
54
78
  }
@@ -0,0 +1,125 @@
1
+ import type { MusicSource } from './types.js';
2
+
3
+ export const MUSIC_CREDENTIALS_TABLE = 'music_credentials';
4
+
5
+ export interface CredentialRow {
6
+ source: MusicSource;
7
+ key: string;
8
+ value: string;
9
+ updated_at: string;
10
+ }
11
+
12
+ export interface CredentialModel {
13
+ select(...fields: string[]): {
14
+ where(query: Record<string, unknown>): PromiseLike<Record<string, unknown>[]>;
15
+ then<TResult1 = Record<string, unknown>[], TResult2 = never>(
16
+ onfulfilled?:
17
+ | ((value: Record<string, unknown>[]) => TResult1 | PromiseLike<TResult1>)
18
+ | null,
19
+ onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
20
+ ): Promise<TResult1 | TResult2>;
21
+ };
22
+ insert(row: Record<string, unknown>): Promise<unknown>;
23
+ delete(): { where(query: Record<string, unknown>): Promise<unknown> };
24
+ update(patch: Record<string, unknown>): {
25
+ where(query: Record<string, unknown>): Promise<unknown>;
26
+ };
27
+ }
28
+
29
+ export interface CredentialDb {
30
+ models: { get(name: string): CredentialModel | undefined };
31
+ }
32
+
33
+ function createMemoryModel(): CredentialModel {
34
+ const rows: Record<string, unknown>[] = [];
35
+
36
+ function matches(
37
+ row: Record<string, unknown>,
38
+ query: Record<string, unknown>,
39
+ ): boolean {
40
+ return Object.entries(query).every(([k, v]) => row[k] === v);
41
+ }
42
+
43
+ return {
44
+ select: () => {
45
+ const all = rows.map((r) => ({ ...r }));
46
+ return {
47
+ where: async (query) =>
48
+ rows.filter((r) => matches(r, query)).map((r) => ({ ...r })),
49
+ then: (onfulfilled, onrejected) =>
50
+ Promise.resolve(all).then(onfulfilled, onrejected),
51
+ };
52
+ },
53
+ insert: async (row) => {
54
+ rows.push({ ...row });
55
+ },
56
+ delete: () => ({
57
+ where: async (query) => {
58
+ for (let i = rows.length - 1; i >= 0; i--) {
59
+ if (matches(rows[i]!, query)) rows.splice(i, 1);
60
+ }
61
+ },
62
+ }),
63
+ update: (patch) => ({
64
+ where: async (query) => {
65
+ for (const row of rows) {
66
+ if (matches(row, query)) Object.assign(row, patch);
67
+ }
68
+ },
69
+ }),
70
+ };
71
+ }
72
+
73
+ export function createInMemoryCredentialDb(): CredentialDb {
74
+ const model = createMemoryModel();
75
+ return {
76
+ models: {
77
+ get: (name) => name === MUSIC_CREDENTIALS_TABLE ? model : undefined,
78
+ },
79
+ };
80
+ }
81
+
82
+ export class CredentialStore {
83
+ readonly #db: CredentialDb;
84
+
85
+ constructor(db: CredentialDb) {
86
+ this.#db = db;
87
+ }
88
+
89
+ private get model(): CredentialModel | null {
90
+ return this.#db.models.get(MUSIC_CREDENTIALS_TABLE) ?? null;
91
+ }
92
+
93
+ async get(source: MusicSource, key: string): Promise<string | null> {
94
+ const rows = await this.model?.select().where({ source, key });
95
+ if (!rows?.length) return null;
96
+ return String(rows[0]!.value ?? '');
97
+ }
98
+
99
+ async set(source: MusicSource, key: string, value: string): Promise<void> {
100
+ const model = this.model;
101
+ if (!model) return;
102
+ const existing = await model.select().where({ source, key });
103
+ if (existing.length > 0) {
104
+ await model.update({ value, updated_at: new Date().toISOString() }).where({ source, key });
105
+ return;
106
+ }
107
+ await model.insert({ source, key, value, updated_at: new Date().toISOString() });
108
+ }
109
+
110
+ async delete(source: MusicSource, key: string): Promise<void> {
111
+ await this.model?.delete().where({ source, key });
112
+ }
113
+
114
+ async list(source?: MusicSource): Promise<CredentialRow[]> {
115
+ const model = this.model;
116
+ if (!model) return [];
117
+ const rows = source ? await model.select().where({ source }) : await model.select();
118
+ return rows.map((row) => ({
119
+ source: String(row.source) as MusicSource,
120
+ key: String(row.key),
121
+ value: String(row.value),
122
+ updated_at: String(row.updated_at ?? ''),
123
+ }));
124
+ }
125
+ }
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ export {
2
+ formatDuration,
3
+ formatMusicInfo,
4
+ sourceConfigMap,
5
+ SOURCE_ALIASES,
6
+ SOURCE_DISPLAY_NAME,
7
+ resolveSourceAlias,
8
+ } from './config.js';
9
+ export {
10
+ searchMusic,
11
+ shareMusicDetail,
12
+ formatSearchResults,
13
+ buildMusicShareSegment,
14
+ } from './music-lib.js';
15
+ export { createMusicServices } from './sources/index.js';
16
+ export {
17
+ sessionKey,
18
+ resolveMessageIds,
19
+ MusicSearchSessions,
20
+ type PendingSearch,
21
+ } from './session.js';
22
+ export {
23
+ CredentialStore,
24
+ createInMemoryCredentialDb,
25
+ MUSIC_CREDENTIALS_TABLE,
26
+ type CredentialDb,
27
+ type CredentialRow,
28
+ } from './credential-store.js';
29
+ export { musicRuntimeToken, type MusicRuntime } from './runtime.js';
30
+ export {
31
+ QrLoginRuntime,
32
+ loginSessionKey,
33
+ type QrLoginSource,
34
+ type QrLoginSession,
35
+ type QrPollResult,
36
+ } from './login/index.js';
37
+ export type { MusicDetail, MusicInfo, MusicSource, MusicSourceConfig } from './types.js';