neteasecli 2.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -0
  3. package/dist/api/client.d.ts +19 -0
  4. package/dist/api/client.js +157 -0
  5. package/dist/api/crypto.d.ts +13 -0
  6. package/dist/api/crypto.js +59 -0
  7. package/dist/api/login.d.ts +8 -0
  8. package/dist/api/login.js +38 -0
  9. package/dist/api/playlist.d.ts +3 -0
  10. package/dist/api/playlist.js +65 -0
  11. package/dist/api/search.d.ts +2 -0
  12. package/dist/api/search.js +82 -0
  13. package/dist/api/track.d.ts +9 -0
  14. package/dist/api/track.js +96 -0
  15. package/dist/api/user.d.ts +5 -0
  16. package/dist/api/user.js +50 -0
  17. package/dist/auth/manager.d.ts +24 -0
  18. package/dist/auth/manager.js +108 -0
  19. package/dist/auth/storage.d.ts +7 -0
  20. package/dist/auth/storage.js +73 -0
  21. package/dist/cli/auth.d.ts +2 -0
  22. package/dist/cli/auth.js +62 -0
  23. package/dist/cli/index.d.ts +2 -0
  24. package/dist/cli/index.js +57 -0
  25. package/dist/cli/library.d.ts +2 -0
  26. package/dist/cli/library.js +92 -0
  27. package/dist/cli/player.d.ts +2 -0
  28. package/dist/cli/player.js +155 -0
  29. package/dist/cli/playlist.d.ts +2 -0
  30. package/dist/cli/playlist.js +60 -0
  31. package/dist/cli/search.d.ts +2 -0
  32. package/dist/cli/search.js +29 -0
  33. package/dist/cli/track.d.ts +2 -0
  34. package/dist/cli/track.js +119 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +18 -0
  37. package/dist/output/color.d.ts +7 -0
  38. package/dist/output/color.js +17 -0
  39. package/dist/output/json.d.ts +7 -0
  40. package/dist/output/json.js +201 -0
  41. package/dist/output/logger.d.ts +6 -0
  42. package/dist/output/logger.js +25 -0
  43. package/dist/player/mpv.d.ts +26 -0
  44. package/dist/player/mpv.js +160 -0
  45. package/dist/types/index.d.ts +68 -0
  46. package/dist/types/index.js +6 -0
  47. package/package.json +63 -0
@@ -0,0 +1,155 @@
1
+ import { Command } from 'commander';
2
+ import { mpvPlayer } from '../player/mpv.js';
3
+ import { output, outputError } from '../output/json.js';
4
+ import { ExitCode } from '../types/index.js';
5
+ function formatTime(seconds) {
6
+ const mins = Math.floor(seconds / 60);
7
+ const secs = Math.floor(seconds % 60);
8
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
9
+ }
10
+ async function requireRunning() {
11
+ if (!(await mpvPlayer.isRunning())) {
12
+ outputError('PLAYER_ERROR', 'Nothing is playing');
13
+ process.exit(ExitCode.GENERAL_ERROR);
14
+ }
15
+ }
16
+ export function createPlayerCommand() {
17
+ const player = new Command('player').description('Playback control');
18
+ player
19
+ .command('status')
20
+ .description('Current playback status')
21
+ .action(async () => {
22
+ try {
23
+ const status = await mpvPlayer.getStatus();
24
+ if (!status.playing) {
25
+ output({ playing: false, message: 'Nothing is playing' });
26
+ return;
27
+ }
28
+ const repeat = status.loop !== 'no' && status.loop !== 'false';
29
+ output({
30
+ playing: true,
31
+ paused: status.paused,
32
+ title: status.title,
33
+ position: status.position,
34
+ duration: status.duration,
35
+ positionFormatted: formatTime(status.position),
36
+ durationFormatted: formatTime(status.duration),
37
+ volume: Math.round(status.volume),
38
+ repeat,
39
+ message: `${status.paused ? '⏸' : '▶'} ${status.title || 'Unknown'} ${formatTime(status.position)}/${formatTime(status.duration)} vol:${Math.round(status.volume)}%${repeat ? ' 🔁' : ''}`,
40
+ });
41
+ }
42
+ catch (error) {
43
+ outputError('PLAYER_ERROR', error instanceof Error ? error.message : 'Failed');
44
+ process.exit(ExitCode.GENERAL_ERROR);
45
+ }
46
+ });
47
+ player
48
+ .command('pause')
49
+ .description('Toggle pause/resume')
50
+ .action(async () => {
51
+ try {
52
+ await requireRunning();
53
+ await mpvPlayer.pause();
54
+ const status = await mpvPlayer.getStatus();
55
+ output({
56
+ paused: status.paused,
57
+ message: status.paused ? 'Paused' : 'Resumed',
58
+ });
59
+ }
60
+ catch (error) {
61
+ outputError('PLAYER_ERROR', error instanceof Error ? error.message : 'Failed');
62
+ process.exit(ExitCode.GENERAL_ERROR);
63
+ }
64
+ });
65
+ player
66
+ .command('stop')
67
+ .description('Stop playback')
68
+ .action(async () => {
69
+ try {
70
+ await mpvPlayer.stop();
71
+ output({ message: 'Stopped' });
72
+ }
73
+ catch (error) {
74
+ outputError('PLAYER_ERROR', error instanceof Error ? error.message : 'Failed');
75
+ process.exit(ExitCode.GENERAL_ERROR);
76
+ }
77
+ });
78
+ player
79
+ .command('seek <seconds>')
80
+ .description('Seek by relative seconds (e.g. 10, -10) or absolute with --absolute')
81
+ .option('--absolute', 'Seek to absolute position')
82
+ .action(async (seconds, opts) => {
83
+ try {
84
+ await requireRunning();
85
+ const secs = Number(seconds);
86
+ if (isNaN(secs)) {
87
+ outputError('PLAYER_ERROR', 'Invalid seconds value');
88
+ process.exit(ExitCode.GENERAL_ERROR);
89
+ return;
90
+ }
91
+ await mpvPlayer.seek(secs, opts.absolute ? 'absolute' : 'relative');
92
+ const status = await mpvPlayer.getStatus();
93
+ output({
94
+ position: status.position,
95
+ duration: status.duration,
96
+ positionFormatted: formatTime(status.position),
97
+ durationFormatted: formatTime(status.duration),
98
+ message: `Seeked to ${formatTime(status.position)}/${formatTime(status.duration)}`,
99
+ });
100
+ }
101
+ catch (error) {
102
+ outputError('PLAYER_ERROR', error instanceof Error ? error.message : 'Failed');
103
+ process.exit(ExitCode.GENERAL_ERROR);
104
+ }
105
+ });
106
+ player
107
+ .command('volume [level]')
108
+ .description('Get or set volume (0-150)')
109
+ .action(async (level) => {
110
+ try {
111
+ await requireRunning();
112
+ if (level !== undefined) {
113
+ const vol = Number(level);
114
+ if (isNaN(vol)) {
115
+ outputError('PLAYER_ERROR', 'Invalid volume value');
116
+ process.exit(ExitCode.GENERAL_ERROR);
117
+ return;
118
+ }
119
+ await mpvPlayer.setVolume(vol);
120
+ output({ volume: vol, message: `Volume: ${vol}%` });
121
+ }
122
+ else {
123
+ const vol = await mpvPlayer.getVolume();
124
+ output({ volume: vol, message: `Volume: ${Math.round(vol)}%` });
125
+ }
126
+ }
127
+ catch (error) {
128
+ outputError('PLAYER_ERROR', error instanceof Error ? error.message : 'Failed');
129
+ process.exit(ExitCode.GENERAL_ERROR);
130
+ }
131
+ });
132
+ player
133
+ .command('repeat [mode]')
134
+ .description('Toggle or set repeat mode (off/on)')
135
+ .action(async (mode) => {
136
+ try {
137
+ await requireRunning();
138
+ if (mode !== undefined) {
139
+ await mpvPlayer.setLoop(mode === 'on' ? 'inf' : 'no');
140
+ output({ repeat: mode === 'on', message: `Repeat: ${mode}` });
141
+ }
142
+ else {
143
+ const current = await mpvPlayer.getLoop();
144
+ const isOn = current !== 'no' && current !== 'false';
145
+ await mpvPlayer.setLoop(isOn ? 'no' : 'inf');
146
+ output({ repeat: !isOn, message: `Repeat: ${!isOn ? 'on' : 'off'}` });
147
+ }
148
+ }
149
+ catch (error) {
150
+ outputError('PLAYER_ERROR', error instanceof Error ? error.message : 'Failed');
151
+ process.exit(ExitCode.GENERAL_ERROR);
152
+ }
153
+ });
154
+ return player;
155
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function createPlaylistCommand(): Command;
@@ -0,0 +1,60 @@
1
+ import { Command } from 'commander';
2
+ import { getUserPlaylists, getPlaylistDetail } from '../api/playlist.js';
3
+ import { output, outputError } from '../output/json.js';
4
+ import { ExitCode } from '../types/index.js';
5
+ export function createPlaylistCommand() {
6
+ const playlist = new Command('playlist').description('Playlists');
7
+ playlist
8
+ .command('list')
9
+ .description('List my playlists')
10
+ .action(async () => {
11
+ try {
12
+ const playlists = await getUserPlaylists();
13
+ output({
14
+ playlists: playlists.map((p) => ({
15
+ id: p.id,
16
+ name: p.name,
17
+ trackCount: p.trackCount,
18
+ creator: p.creator?.name,
19
+ })),
20
+ total: playlists.length,
21
+ });
22
+ }
23
+ catch (error) {
24
+ outputError('PLAYLIST_ERROR', error instanceof Error ? error.message : 'Failed');
25
+ process.exit(ExitCode.NETWORK_ERROR);
26
+ }
27
+ });
28
+ playlist
29
+ .command('detail')
30
+ .description('Playlist details')
31
+ .argument('<id>', 'Playlist ID')
32
+ .option('-l, --limit <number>', 'Track count limit', '50')
33
+ .action(async (id, options) => {
34
+ try {
35
+ const detail = await getPlaylistDetail(id);
36
+ const limit = parseInt(options.limit);
37
+ output({
38
+ id: detail.id,
39
+ name: detail.name,
40
+ description: detail.description,
41
+ coverUrl: detail.coverUrl,
42
+ trackCount: detail.trackCount,
43
+ creator: detail.creator,
44
+ tracks: detail.tracks?.slice(0, limit).map((t) => ({
45
+ id: t.id,
46
+ name: t.name,
47
+ artist: t.artists.map((a) => a.name).join(', '),
48
+ album: t.album.name,
49
+ duration: t.duration,
50
+ uri: t.uri,
51
+ })),
52
+ });
53
+ }
54
+ catch (error) {
55
+ outputError('PLAYLIST_ERROR', error instanceof Error ? error.message : 'Failed');
56
+ process.exit(ExitCode.NETWORK_ERROR);
57
+ }
58
+ });
59
+ return playlist;
60
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function createSearchCommand(): Command;
@@ -0,0 +1,29 @@
1
+ import { Command } from 'commander';
2
+ import { search } from '../api/search.js';
3
+ import { output, outputError } from '../output/json.js';
4
+ import { ExitCode } from '../types/index.js';
5
+ export function createSearchCommand() {
6
+ const searchCmd = new Command('search').description('Search music');
7
+ const createSubCommand = (type, description) => {
8
+ return new Command(type)
9
+ .description(description)
10
+ .argument('<keyword>', 'Search keyword')
11
+ .option('-l, --limit <number>', 'Result count', '20')
12
+ .option('-o, --offset <number>', 'Offset', '0')
13
+ .action(async (keyword, options) => {
14
+ try {
15
+ const result = await search(keyword, type, parseInt(options.limit), parseInt(options.offset));
16
+ output(result);
17
+ }
18
+ catch (error) {
19
+ outputError('SEARCH_ERROR', error instanceof Error ? error.message : 'Search failed');
20
+ process.exit(ExitCode.NETWORK_ERROR);
21
+ }
22
+ });
23
+ };
24
+ searchCmd.addCommand(createSubCommand('track', 'Search tracks'));
25
+ searchCmd.addCommand(createSubCommand('album', 'Search albums'));
26
+ searchCmd.addCommand(createSubCommand('playlist', 'Search playlists'));
27
+ searchCmd.addCommand(createSubCommand('artist', 'Search artists'));
28
+ return searchCmd;
29
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function createTrackCommand(): Command;
@@ -0,0 +1,119 @@
1
+ import { Command } from 'commander';
2
+ import { getTrackDetail, getTrackUrl, getLyric, downloadTrack } from '../api/track.js';
3
+ import { mpvPlayer } from '../player/mpv.js';
4
+ import { output, outputError } from '../output/json.js';
5
+ import { ExitCode } from '../types/index.js';
6
+ export function createTrackCommand() {
7
+ const track = new Command('track').description('Track info');
8
+ track
9
+ .command('detail')
10
+ .description('Track details')
11
+ .argument('<id>', 'Track ID')
12
+ .action(async (id) => {
13
+ try {
14
+ const detail = await getTrackDetail(id);
15
+ output({
16
+ id: detail.id,
17
+ name: detail.name,
18
+ artists: detail.artists,
19
+ album: detail.album,
20
+ duration: detail.duration,
21
+ durationFormatted: formatDuration(detail.duration),
22
+ uri: detail.uri,
23
+ });
24
+ }
25
+ catch (error) {
26
+ outputError('TRACK_ERROR', error instanceof Error ? error.message : 'Failed');
27
+ process.exit(ExitCode.NETWORK_ERROR);
28
+ }
29
+ });
30
+ track
31
+ .command('url')
32
+ .description('Get streaming URL')
33
+ .argument('<id>', 'Track ID')
34
+ .option('-q, --quality <level>', 'Quality: standard/higher/exhigh/lossless/hires', 'exhigh')
35
+ .action(async (id, options) => {
36
+ try {
37
+ const url = await getTrackUrl(id, options.quality);
38
+ output({ id, url, quality: options.quality });
39
+ }
40
+ catch (error) {
41
+ outputError('TRACK_ERROR', error instanceof Error ? error.message : 'Failed');
42
+ process.exit(ExitCode.NETWORK_ERROR);
43
+ }
44
+ });
45
+ track
46
+ .command('lyric')
47
+ .description('Get lyrics')
48
+ .argument('<id>', 'Track ID')
49
+ .action(async (id) => {
50
+ try {
51
+ const lyric = await getLyric(id);
52
+ output({
53
+ id,
54
+ lrc: lyric.lrc,
55
+ tlyric: lyric.tlyric,
56
+ hasLyric: !!lyric.lrc,
57
+ hasTranslation: !!lyric.tlyric,
58
+ });
59
+ }
60
+ catch (error) {
61
+ outputError('TRACK_ERROR', error instanceof Error ? error.message : 'Failed');
62
+ process.exit(ExitCode.NETWORK_ERROR);
63
+ }
64
+ });
65
+ track
66
+ .command('download')
67
+ .description('Download track')
68
+ .argument('<id>', 'Track ID')
69
+ .option('-q, --quality <level>', 'Quality: standard/higher/exhigh/lossless/hires', 'exhigh')
70
+ .option('-o, --output <path>', 'Output file path')
71
+ .action(async (id, options) => {
72
+ try {
73
+ const result = await downloadTrack(id, options.quality, options.output);
74
+ output({
75
+ id,
76
+ path: result.path,
77
+ size: result.size,
78
+ quality: options.quality,
79
+ });
80
+ }
81
+ catch (error) {
82
+ outputError('TRACK_ERROR', error instanceof Error ? error.message : 'Download failed');
83
+ process.exit(ExitCode.NETWORK_ERROR);
84
+ }
85
+ });
86
+ track
87
+ .command('play')
88
+ .description('Play track via mpv')
89
+ .argument('<id>', 'Track ID')
90
+ .option('-q, --quality <level>', 'Quality: standard/higher/exhigh/lossless/hires', 'exhigh')
91
+ .action(async (id, options) => {
92
+ try {
93
+ const [url, detail] = await Promise.all([
94
+ getTrackUrl(id, options.quality),
95
+ getTrackDetail(id),
96
+ ]);
97
+ const title = `${detail.name} - ${detail.artists.map((a) => a.name).join('/')}`;
98
+ await mpvPlayer.play(url, title);
99
+ output({
100
+ id,
101
+ name: detail.name,
102
+ artists: detail.artists,
103
+ quality: options.quality,
104
+ message: `Now playing: ${title}`,
105
+ });
106
+ }
107
+ catch (error) {
108
+ outputError('PLAYER_ERROR', error instanceof Error ? error.message : 'Playback failed');
109
+ process.exit(ExitCode.GENERAL_ERROR);
110
+ }
111
+ });
112
+ return track;
113
+ }
114
+ function formatDuration(ms) {
115
+ const seconds = Math.floor(ms / 1000);
116
+ const minutes = Math.floor(seconds / 60);
117
+ const secs = seconds % 60;
118
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
119
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { createProgram } from './cli/index.js';
3
+ const program = createProgram();
4
+ try {
5
+ await program.parseAsync();
6
+ }
7
+ catch (error) {
8
+ if (error instanceof Error) {
9
+ console.error(JSON.stringify({
10
+ success: false,
11
+ error: {
12
+ code: 'CLI_ERROR',
13
+ message: error.message,
14
+ },
15
+ }, null, 2));
16
+ }
17
+ process.exit(1);
18
+ }
@@ -0,0 +1,7 @@
1
+ export declare function setNoColor(v: boolean): void;
2
+ export declare const bold: (t: string) => string;
3
+ export declare const dim: (t: string) => string;
4
+ export declare const green: (t: string) => string;
5
+ export declare const red: (t: string) => string;
6
+ export declare const yellow: (t: string) => string;
7
+ export declare const cyan: (t: string) => string;
@@ -0,0 +1,17 @@
1
+ const envDisabled = !!process.env.NO_COLOR || process.env.TERM === 'dumb';
2
+ let forceOff = false;
3
+ export function setNoColor(v) {
4
+ forceOff = v;
5
+ }
6
+ function enabled() {
7
+ return !envDisabled && !forceOff && !!process.stdout.isTTY;
8
+ }
9
+ function wrap(code, text) {
10
+ return enabled() ? `\x1b[${code}m${text}\x1b[0m` : text;
11
+ }
12
+ export const bold = (t) => wrap(1, t);
13
+ export const dim = (t) => wrap(2, t);
14
+ export const green = (t) => wrap(32, t);
15
+ export const red = (t) => wrap(31, t);
16
+ export const yellow = (t) => wrap(33, t);
17
+ export const cyan = (t) => wrap(36, t);
@@ -0,0 +1,7 @@
1
+ type OutputMode = 'json' | 'plain' | 'human';
2
+ export declare function setOutputMode(m: OutputMode): void;
3
+ export declare function setPrettyPrint(value: boolean): void;
4
+ export declare function setQuietMode(value: boolean): void;
5
+ export declare function output<T>(data: T): void;
6
+ export declare function outputError(code: string, message: string): void;
7
+ export {};
@@ -0,0 +1,201 @@
1
+ import { bold, dim, green, red, cyan, yellow } from './color.js';
2
+ let mode = process.stdout.isTTY ? 'human' : 'json';
3
+ let prettyPrint = false;
4
+ let quietMode = false;
5
+ export function setOutputMode(m) {
6
+ mode = m;
7
+ }
8
+ export function setPrettyPrint(value) {
9
+ prettyPrint = value;
10
+ }
11
+ export function setQuietMode(value) {
12
+ quietMode = value;
13
+ }
14
+ export function output(data) {
15
+ if (quietMode)
16
+ return;
17
+ switch (mode) {
18
+ case 'json':
19
+ outputJson(data);
20
+ break;
21
+ case 'plain':
22
+ outputPlain(data);
23
+ break;
24
+ case 'human':
25
+ outputHuman(data);
26
+ break;
27
+ }
28
+ }
29
+ export function outputError(code, message) {
30
+ if (quietMode)
31
+ return;
32
+ switch (mode) {
33
+ case 'json': {
34
+ const response = {
35
+ success: false,
36
+ data: null,
37
+ error: { code, message },
38
+ };
39
+ console.log(prettyPrint ? JSON.stringify(response, null, 2) : JSON.stringify(response));
40
+ break;
41
+ }
42
+ case 'plain':
43
+ console.log(`error\t${code}\t${message}`);
44
+ break;
45
+ case 'human':
46
+ console.log(`${red('✗')} ${bold(code)}: ${message}`);
47
+ break;
48
+ }
49
+ }
50
+ // --- JSON mode ---
51
+ function outputJson(data) {
52
+ const response = { success: true, data, error: null };
53
+ console.log(prettyPrint ? JSON.stringify(response, null, 2) : JSON.stringify(response));
54
+ }
55
+ // --- Plain mode ---
56
+ function outputPlain(data) {
57
+ const d = data;
58
+ if (d.tracks && Array.isArray(d.tracks)) {
59
+ for (const t of d.tracks) {
60
+ console.log([t.id, t.name, t.artist || formatArtists(t.artists), t.album, t.uri].join('\t'));
61
+ }
62
+ return;
63
+ }
64
+ if (d.playlists && Array.isArray(d.playlists)) {
65
+ for (const p of d.playlists) {
66
+ console.log([p.id, p.name, p.trackCount, p.creator].join('\t'));
67
+ }
68
+ return;
69
+ }
70
+ if (d.lrc !== undefined) {
71
+ if (d.lrc)
72
+ console.log(String(d.lrc));
73
+ return;
74
+ }
75
+ if (d.url !== undefined) {
76
+ console.log(String(d.url));
77
+ return;
78
+ }
79
+ if (d.message !== undefined) {
80
+ console.log(String(d.message));
81
+ return;
82
+ }
83
+ // Fallback: key=value
84
+ for (const [k, v] of Object.entries(d)) {
85
+ if (v !== undefined && v !== null && typeof v !== 'object') {
86
+ console.log(`${k}\t${v}`);
87
+ }
88
+ }
89
+ }
90
+ // --- Human mode ---
91
+ function outputHuman(data) {
92
+ const d = data;
93
+ // Track list (search results, library, playlist detail)
94
+ if (d.tracks && Array.isArray(d.tracks)) {
95
+ const tracks = d.tracks;
96
+ for (let i = 0; i < tracks.length; i++) {
97
+ const t = tracks[i];
98
+ const artist = t.artist || formatArtists(t.artists);
99
+ const dur = t.duration ? ` ${dim(formatDuration(Number(t.duration)))}` : '';
100
+ console.log(` ${dim(String(i + 1).padStart(2, ' '))} ${bold(String(t.name))} ${dim('-')} ${cyan(String(artist))}${dur}`);
101
+ }
102
+ const total = d.total !== undefined ? Number(d.total) : tracks.length;
103
+ const showing = d.showing !== undefined ? Number(d.showing) : tracks.length;
104
+ if (total > showing) {
105
+ console.log(dim(`\n ${showing} of ${total} tracks`));
106
+ }
107
+ return;
108
+ }
109
+ // Playlist list
110
+ if (d.playlists && Array.isArray(d.playlists)) {
111
+ const pls = d.playlists;
112
+ for (let i = 0; i < pls.length; i++) {
113
+ const p = pls[i];
114
+ console.log(` ${dim(String(i + 1).padStart(2, ' '))} ${bold(String(p.name))} ${dim(`(${p.trackCount} tracks)`)}`);
115
+ }
116
+ return;
117
+ }
118
+ // Lyrics
119
+ if (d.lrc !== undefined) {
120
+ if (d.lrc) {
121
+ console.log(String(d.lrc));
122
+ }
123
+ else {
124
+ console.log(dim('No lyrics available'));
125
+ }
126
+ return;
127
+ }
128
+ // Single track detail
129
+ if (d.artists && Array.isArray(d.artists) && d.album && d.duration) {
130
+ console.log(` ${bold(String(d.name))}`);
131
+ console.log(` ${dim('Artist:')} ${cyan(formatArtists(d.artists))}`);
132
+ const album = d.album;
133
+ console.log(` ${dim('Album:')} ${String(album.name)}`);
134
+ console.log(` ${dim('Duration:')} ${d.durationFormatted || formatDuration(Number(d.duration))}`);
135
+ if (d.uri)
136
+ console.log(` ${dim('URI:')} ${String(d.uri)}`);
137
+ return;
138
+ }
139
+ // URL result
140
+ if (d.url !== undefined && d.quality !== undefined) {
141
+ console.log(` ${dim('URL:')} ${String(d.url)}`);
142
+ console.log(` ${dim('Quality:')} ${String(d.quality)}`);
143
+ return;
144
+ }
145
+ // Auth check result
146
+ if (d.credentials !== undefined && typeof d.credentials === 'object') {
147
+ const creds = d.credentials;
148
+ const warnings = d.warnings || [];
149
+ const profile = d.profile ? String(d.profile) : 'default';
150
+ console.log(`${bold('Credential check')} ${dim(`(profile: ${profile})`)}`);
151
+ console.log(dim('─'.repeat(40)));
152
+ for (const [name, found] of Object.entries(creds)) {
153
+ const icon = found ? green('✓') : red('✗');
154
+ const status = found ? 'found' : 'not found';
155
+ console.log(`${icon} ${bold(name)}: ${found ? green(status) : red(status)}`);
156
+ }
157
+ if (d.valid && d.nickname) {
158
+ console.log(`${green('✓')} ${bold('session')}: ${green('valid')} ${dim(`(${d.nickname})`)}`);
159
+ }
160
+ else if (creds.MUSIC_U) {
161
+ console.log(`${red('✗')} ${bold('session')}: ${red('expired or invalid')}`);
162
+ }
163
+ if (warnings.length > 0) {
164
+ console.log(`\n${yellow('⚠')} ${bold('Warnings:')}`);
165
+ for (const w of warnings) {
166
+ console.log(` ${dim('-')} ${w}`);
167
+ }
168
+ }
169
+ if (!d.valid) {
170
+ console.log(`\n${red('✗')} Missing credentials. Options:`);
171
+ console.log(` 1. Login to ${cyan('music.163.com')} in Chrome, Edge, Firefox, or Safari`);
172
+ console.log(` 2. Run ${cyan('neteasecli auth login')}`);
173
+ console.log(` 3. Use ${cyan('--profile <name>')} for a specific Chrome/Edge profile`);
174
+ }
175
+ return;
176
+ }
177
+ // Message-based output (auth, player, etc.)
178
+ if (d.message !== undefined) {
179
+ const icon = green('✓');
180
+ console.log(`${icon} ${String(d.message)}`);
181
+ return;
182
+ }
183
+ // Fallback
184
+ for (const [k, v] of Object.entries(d)) {
185
+ if (v !== undefined && v !== null && typeof v !== 'object') {
186
+ console.log(` ${dim(k + ':')} ${v}`);
187
+ }
188
+ }
189
+ }
190
+ // --- Helpers ---
191
+ function formatArtists(artists) {
192
+ if (!Array.isArray(artists))
193
+ return String(artists || '');
194
+ return artists.map((a) => a.name || a).join(', ');
195
+ }
196
+ function formatDuration(ms) {
197
+ const seconds = Math.floor(ms / 1000);
198
+ const minutes = Math.floor(seconds / 60);
199
+ const secs = seconds % 60;
200
+ return `${minutes}:${secs.toString().padStart(2, '0')}`;
201
+ }
@@ -0,0 +1,6 @@
1
+ export declare function setVerbose(v: boolean): void;
2
+ export declare function setDebug(v: boolean): void;
3
+ export declare function isVerbose(): boolean;
4
+ export declare function isDebug(): boolean;
5
+ export declare function verbose(msg: string): void;
6
+ export declare function debug(msg: string): void;
@@ -0,0 +1,25 @@
1
+ import { dim, yellow } from './color.js';
2
+ let verboseEnabled = false;
3
+ let debugEnabled = false;
4
+ export function setVerbose(v) {
5
+ verboseEnabled = v;
6
+ }
7
+ export function setDebug(v) {
8
+ debugEnabled = v;
9
+ if (v)
10
+ verboseEnabled = true;
11
+ }
12
+ export function isVerbose() {
13
+ return verboseEnabled;
14
+ }
15
+ export function isDebug() {
16
+ return debugEnabled;
17
+ }
18
+ export function verbose(msg) {
19
+ if (verboseEnabled)
20
+ process.stderr.write(dim(`[verbose] ${msg}`) + '\n');
21
+ }
22
+ export function debug(msg) {
23
+ if (debugEnabled)
24
+ process.stderr.write(yellow(`[debug] ${msg}`) + '\n');
25
+ }