gerdur 0.0.0-probe → 1.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/CHANGELOG.md +10 -0
- package/LICENSE +0 -0
- package/README.md +231 -1
- package/dist/package.json +90 -0
- package/dist/src/gerdur.d.ts +2 -0
- package/dist/src/gerdur.js +345 -0
- package/dist/src/hoenir.d.ts +2 -0
- package/dist/src/hoenir.js +345 -0
- package/dist/src/index.d.ts +62 -0
- package/dist/src/index.js +101 -0
- package/dist/src/kyrie.d.ts +2 -0
- package/dist/src/kyrie.js +345 -0
- package/dist/src/lib/api-download.d.ts +54 -0
- package/dist/src/lib/api-download.js +89 -0
- package/dist/src/lib/arl-setup.d.ts +82 -0
- package/dist/src/lib/arl-setup.js +274 -0
- package/dist/src/lib/auto-updater.d.ts +2 -0
- package/dist/src/lib/auto-updater.js +70 -0
- package/dist/src/lib/config.d.ts +53 -0
- package/dist/src/lib/config.js +164 -0
- package/dist/src/lib/decrypt-pool.d.ts +2 -0
- package/dist/src/lib/decrypt-pool.js +122 -0
- package/dist/src/lib/decrypt-worker.d.ts +1 -0
- package/dist/src/lib/decrypt-worker.js +17 -0
- package/dist/src/lib/decrypt.d.ts +3 -0
- package/dist/src/lib/decrypt.js +114 -0
- package/dist/src/lib/download-track.d.ts +24 -0
- package/dist/src/lib/download-track.js +163 -0
- package/dist/src/lib/email-login.d.ts +16 -0
- package/dist/src/lib/email-login.js +111 -0
- package/dist/src/lib/session.d.ts +84 -0
- package/dist/src/lib/session.js +118 -0
- package/dist/src/lib/signale.d.ts +15 -0
- package/dist/src/lib/signale.js +20 -0
- package/dist/src/lib/update-check.d.ts +2 -0
- package/dist/src/lib/update-check.js +30 -0
- package/dist/src/lib/util.d.ts +17 -0
- package/dist/src/lib/util.js +94 -0
- package/dist/src/q-fi.d.ts +2 -0
- package/dist/src/q-fi.js +345 -0
- package/package.json +88 -5
- package/setup-termux.js +41 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const commander_1 = require("commander");
|
|
11
|
+
const gradient_string_1 = __importDefault(require("gradient-string"));
|
|
12
|
+
const hoenir_core_1 = require("hoenir-core");
|
|
13
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
14
|
+
const log_update_1 = __importDefault(require("log-update"));
|
|
15
|
+
const p_queue_1 = __importDefault(require("p-queue"));
|
|
16
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
17
|
+
const true_case_path_1 = require("true-case-path");
|
|
18
|
+
const signale_1 = __importDefault(require("./lib/signale"));
|
|
19
|
+
const download_track_1 = __importDefault(require("./lib/download-track"));
|
|
20
|
+
const config_1 = __importDefault(require("./lib/config"));
|
|
21
|
+
const update_check_1 = __importDefault(require("./lib/update-check"));
|
|
22
|
+
const auto_updater_1 = __importDefault(require("./lib/auto-updater"));
|
|
23
|
+
const arl_setup_1 = require("./lib/arl-setup");
|
|
24
|
+
const util_1 = require("./lib/util");
|
|
25
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
26
|
+
// App info
|
|
27
|
+
console.log((0, gradient_string_1.default)('red', 'yellow', 'orange')(` ♥ hoenir - ${package_json_1.default.version} ♥ `) +
|
|
28
|
+
'\n' +
|
|
29
|
+
(0, gradient_string_1.default)('orange', 'yellow', 'red')(' ──────────────────────────────────────────────') +
|
|
30
|
+
'\n' +
|
|
31
|
+
(0, gradient_string_1.default)('red', 'yellow', 'orange')(' │ repo https://github.com/soulwax/hoenir │ ') +
|
|
32
|
+
'\n' +
|
|
33
|
+
(0, gradient_string_1.default)('red', 'yellow', 'orange')(' │ github https://github.com/soulwax │ ') +
|
|
34
|
+
'\n' +
|
|
35
|
+
(0, gradient_string_1.default)('red', 'yellow', 'orange')(' ──────────────────────────────────────────────'));
|
|
36
|
+
const cmd = new commander_1.Command()
|
|
37
|
+
.option('-q, --quality <quality>', 'The quality of the files to download: 128/320/flac ')
|
|
38
|
+
.option('-o, --output <template>', 'Output filename template')
|
|
39
|
+
.option('-u, --url <url>', 'Deezer album/artist/playlist/track url')
|
|
40
|
+
.option('-i, --input-file <file>', 'Downloads all urls listed in text file')
|
|
41
|
+
.option('-c, --concurrency <number>', 'Download concurrency for album, artists and playlist')
|
|
42
|
+
.option('-a, --set-arl <string>', 'Set arl cookie')
|
|
43
|
+
.option('-s, --setup', 'Run guided first-time setup (enter your arl cookie)')
|
|
44
|
+
.option('--experimental-login', 'Deprecated: email/password login is now offered by default')
|
|
45
|
+
.option('-w, --overwrite', 'Re-download and overwrite files that already exist')
|
|
46
|
+
.option('-d, --headless', 'Run in headless mode for scripting automation', false)
|
|
47
|
+
.option('-conf, --config-file <file>', 'Custom location to your config file', 'hoenir.config.json')
|
|
48
|
+
.option('-rfp, --resolve-full-path', 'Use absolute path for playlists')
|
|
49
|
+
.option('-cp, --create-playlist', 'Force create a playlist file for non playlists');
|
|
50
|
+
if (process.pkg) {
|
|
51
|
+
cmd.option('-U, --update', 'Update this program to latest version');
|
|
52
|
+
}
|
|
53
|
+
const options = cmd.parse(process.argv).opts();
|
|
54
|
+
// Support a bare `hoenir setup` subcommand as an alias for `--setup`.
|
|
55
|
+
if (cmd.args[0] && cmd.args[0].toLowerCase() === 'setup') {
|
|
56
|
+
options.setup = true;
|
|
57
|
+
cmd.args.shift();
|
|
58
|
+
}
|
|
59
|
+
if (!options.url && cmd.args[0]) {
|
|
60
|
+
options.url = cmd.args[0];
|
|
61
|
+
}
|
|
62
|
+
if (options.headless && !options.quality) {
|
|
63
|
+
console.error(signale_1.default.error('Missing parameters --quality'));
|
|
64
|
+
console.error(signale_1.default.note('Quality must be provided with headless mode'));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
if (options.headless && !options.url && !options.inputFile) {
|
|
68
|
+
console.error(signale_1.default.error('Missing parameters --url'));
|
|
69
|
+
console.error(signale_1.default.note('URL must be provided with headless mode'));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const conf = new config_1.default(options.configFile);
|
|
73
|
+
if (conf.userConfigLocation) {
|
|
74
|
+
console.log(signale_1.default.info('Config loaded --> ' + conf.userConfigLocation));
|
|
75
|
+
}
|
|
76
|
+
const queue = new p_queue_1.default({ concurrency: Number(options.concurrency || conf.get('concurrency')) });
|
|
77
|
+
const urlRegex = /https?:\/\/.*\w+\.\w+\/\w+/;
|
|
78
|
+
const onCancel = () => {
|
|
79
|
+
console.info(signale_1.default.note('Aborted!'));
|
|
80
|
+
process.exit();
|
|
81
|
+
};
|
|
82
|
+
const startDownload = async (saveLayout, url, skipPrompt) => {
|
|
83
|
+
try {
|
|
84
|
+
if (!options.quality) {
|
|
85
|
+
const { musicQuality } = await (0, prompts_1.default)([
|
|
86
|
+
{
|
|
87
|
+
type: 'select',
|
|
88
|
+
name: 'musicQuality',
|
|
89
|
+
message: 'Select music quality:',
|
|
90
|
+
choices: [
|
|
91
|
+
{ title: 'MP3 - 128 kbps', value: '128' },
|
|
92
|
+
{ title: 'MP3 - 320 kbps', value: '320' },
|
|
93
|
+
{ title: 'FLAC - 1411 kbps', value: 'flac' },
|
|
94
|
+
],
|
|
95
|
+
initial: 1,
|
|
96
|
+
},
|
|
97
|
+
], { onCancel });
|
|
98
|
+
options.quality = musicQuality;
|
|
99
|
+
}
|
|
100
|
+
if (!url) {
|
|
101
|
+
const { query } = await (0, prompts_1.default)([
|
|
102
|
+
{
|
|
103
|
+
type: 'text',
|
|
104
|
+
name: 'query',
|
|
105
|
+
message: 'Enter URL or search:',
|
|
106
|
+
validate: (value) => (value ? true : false),
|
|
107
|
+
},
|
|
108
|
+
], { onCancel });
|
|
109
|
+
url = query;
|
|
110
|
+
}
|
|
111
|
+
let searchData = null;
|
|
112
|
+
if (!url.match(urlRegex)) {
|
|
113
|
+
if (options.headless) {
|
|
114
|
+
throw new Error('Please provide a valid URL. Unknown URL: ' + url);
|
|
115
|
+
}
|
|
116
|
+
if (url.startsWith('artist:')) {
|
|
117
|
+
const { ARTIST } = await (0, hoenir_core_1.searchMusic)(url.replace('artist:', ''), ['ARTIST'], 50);
|
|
118
|
+
const choice = await (0, prompts_1.default)([
|
|
119
|
+
{
|
|
120
|
+
type: 'select',
|
|
121
|
+
name: 'items',
|
|
122
|
+
message: `Select one artist. (found ${ARTIST.data.length} artists)`,
|
|
123
|
+
choices: ARTIST.data.map((a) => ({
|
|
124
|
+
title: a.ART_NAME,
|
|
125
|
+
value: a,
|
|
126
|
+
description: `${a.NB_FAN} fans`,
|
|
127
|
+
})),
|
|
128
|
+
},
|
|
129
|
+
], { onCancel });
|
|
130
|
+
console.log(signale_1.default.info('Fetching data. Please hold on.'));
|
|
131
|
+
url = `https://deezer.com/us/artist/${choice.items.ART_ID}`;
|
|
132
|
+
}
|
|
133
|
+
else if (url.startsWith('album:')) {
|
|
134
|
+
const { ALBUM } = await (0, hoenir_core_1.searchMusic)(url.replace('album:', ''), ['ALBUM'], 50);
|
|
135
|
+
const choice = await (0, prompts_1.default)([
|
|
136
|
+
{
|
|
137
|
+
type: 'select',
|
|
138
|
+
name: 'items',
|
|
139
|
+
message: `Select one album. (found ${ALBUM.data.length} albums)`,
|
|
140
|
+
choices: ALBUM.data.map((a) => ({
|
|
141
|
+
title: a.ALB_TITLE,
|
|
142
|
+
value: a,
|
|
143
|
+
description: `by ${a.ART_NAME}, ${a.NUMBER_TRACK} tracks`,
|
|
144
|
+
})),
|
|
145
|
+
},
|
|
146
|
+
], { onCancel });
|
|
147
|
+
url = `https://deezer.com/us/album/${choice.items.ALB_ID}`;
|
|
148
|
+
}
|
|
149
|
+
else if (url.startsWith('playlist:')) {
|
|
150
|
+
const { PLAYLIST } = await (0, hoenir_core_1.searchMusic)(url.replace('playlist:', ''), ['PLAYLIST'], 50);
|
|
151
|
+
const choice = await (0, prompts_1.default)([
|
|
152
|
+
{
|
|
153
|
+
type: 'select',
|
|
154
|
+
name: 'items',
|
|
155
|
+
message: `Select one playlist. (found ${PLAYLIST.data.length} playlists)`,
|
|
156
|
+
choices: PLAYLIST.data.map((p) => ({
|
|
157
|
+
title: p.TITLE,
|
|
158
|
+
value: p,
|
|
159
|
+
description: `by ${p.PARENT_USERNAME}, ${p.NB_SONG} tracks`,
|
|
160
|
+
})),
|
|
161
|
+
},
|
|
162
|
+
], { onCancel });
|
|
163
|
+
url = `https://deezer.com/us/playlist/${choice.items.PLAYLIST_ID}`;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const { TRACK } = await (0, hoenir_core_1.searchMusic)(url, ['TRACK']);
|
|
167
|
+
searchData = {
|
|
168
|
+
info: { type: 'track', id: url },
|
|
169
|
+
linktype: 'track',
|
|
170
|
+
linkinfo: {},
|
|
171
|
+
tracks: TRACK.data.map((t) => {
|
|
172
|
+
if (t.VERSION && !t.SNG_TITLE.includes(t.VERSION)) {
|
|
173
|
+
t.SNG_TITLE += ' ' + t.VERSION;
|
|
174
|
+
}
|
|
175
|
+
return t;
|
|
176
|
+
}),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else if (url.match(/playlist|artist/)) {
|
|
181
|
+
console.log(signale_1.default.info('Fetching data. Please hold on.'));
|
|
182
|
+
}
|
|
183
|
+
const data = searchData ? searchData : await (0, hoenir_core_1.parseInfo)(url);
|
|
184
|
+
if (!options.headless && data.tracks.length > 1) {
|
|
185
|
+
const choices = await (0, prompts_1.default)([
|
|
186
|
+
{
|
|
187
|
+
type: 'multiselect',
|
|
188
|
+
name: 'items',
|
|
189
|
+
message: `Select songs to download. Total of ${data.tracks.length} tracks.`,
|
|
190
|
+
choices: data.tracks.map((t) => ({
|
|
191
|
+
title: t.SNG_TITLE,
|
|
192
|
+
value: t,
|
|
193
|
+
description: `Artist: ${t.ART_NAME}\nAlbum: ${t.ALB_TITLE}\nDuration: ${(0, util_1.formatSecondsReadable)(Number(t.DURATION))}`,
|
|
194
|
+
})),
|
|
195
|
+
},
|
|
196
|
+
], { onCancel });
|
|
197
|
+
data.tracks = choices.items;
|
|
198
|
+
}
|
|
199
|
+
if (data && data.tracks.length > 0) {
|
|
200
|
+
console.log(signale_1.default.info(`Proceeding to download ${data.tracks.length} tracks. Be patient.`));
|
|
201
|
+
if (data.linktype === 'playlist') {
|
|
202
|
+
const filteredTracks = data.tracks.filter((item, index, self) => index === self.findIndex((t) => t.SNG_ID === item.SNG_ID));
|
|
203
|
+
const duplicateTracks = data.tracks.length - filteredTracks.length;
|
|
204
|
+
if (duplicateTracks > 0) {
|
|
205
|
+
data.tracks = filteredTracks
|
|
206
|
+
.sort((a, b) => a.TRACK_POSITION - b.TRACK_POSITION)
|
|
207
|
+
.map((t, i) => {
|
|
208
|
+
t.TRACK_POSITION = i + 1;
|
|
209
|
+
return t;
|
|
210
|
+
});
|
|
211
|
+
console.log(signale_1.default.warn(`Removed ${duplicateTracks} duplicate ${duplicateTracks > 1 ? 'tracks' : 'track'}.`));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const coverSizes = conf.get('coverSize');
|
|
215
|
+
const trackNumber = conf.get('trackNumber', true);
|
|
216
|
+
const fallbackTrack = conf.get('fallbackTrack', true);
|
|
217
|
+
const fallbackQuality = conf.get('fallbackQuality', true);
|
|
218
|
+
const overwrite = options.overwrite ?? conf.get('overwrite', false);
|
|
219
|
+
const resolveFullPath = options.resolveFullPath ?? conf.get('playlist.resolveFullPath');
|
|
220
|
+
const savedFiles = [];
|
|
221
|
+
let m3u8 = [];
|
|
222
|
+
await queue.addAll(data.tracks.map((track, index) => {
|
|
223
|
+
return async () => {
|
|
224
|
+
const savedPath = await (0, download_track_1.default)({
|
|
225
|
+
track,
|
|
226
|
+
quality: options.quality,
|
|
227
|
+
info: data.linkinfo,
|
|
228
|
+
coverSizes,
|
|
229
|
+
path: options.output ? options.output : saveLayout[data.linktype],
|
|
230
|
+
totalTracks: data ? data.tracks.length : 10,
|
|
231
|
+
trackNumber,
|
|
232
|
+
fallbackTrack,
|
|
233
|
+
fallbackQuality,
|
|
234
|
+
overwrite,
|
|
235
|
+
message: `(${index}/${data.tracks.length})`,
|
|
236
|
+
});
|
|
237
|
+
// Add to saved list
|
|
238
|
+
if (savedPath) {
|
|
239
|
+
m3u8.push((0, path_1.resolve)(process.env.SIMULATE ? savedPath : (0, true_case_path_1.trueCasePathSync)(savedPath)));
|
|
240
|
+
savedFiles.push(savedPath);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}));
|
|
244
|
+
// Display downloaded location
|
|
245
|
+
if (savedFiles.length > 0) {
|
|
246
|
+
const savedIn = new Set(savedFiles.map((l) => (0, path_1.dirname)(l)));
|
|
247
|
+
console.log(signale_1.default.info('Saved in ' + [...savedIn].map((d) => chalk_1.default.bgGreen(d)).join(', ')));
|
|
248
|
+
}
|
|
249
|
+
if ((options.createPlaylist || data.linktype === 'playlist') && !process.env.SIMULATE && m3u8.length > 1) {
|
|
250
|
+
const playlistDir = (0, util_1.commonPath)([...new Set(savedFiles.map(path_1.dirname))]);
|
|
251
|
+
const playlistFile = (0, path_1.join)(playlistDir, (0, util_1.sanitizeFilename)(data.linkinfo.TITLE || data.linkinfo.ALB_TITLE));
|
|
252
|
+
if (!resolveFullPath) {
|
|
253
|
+
const resolvedPlaylistDir = (0, path_1.resolve)(playlistDir) + path_1.sep;
|
|
254
|
+
m3u8 = m3u8.map((file) => file.replace(resolvedPlaylistDir, ''));
|
|
255
|
+
}
|
|
256
|
+
const m3u8Content = '#EXTM3U' + os_1.EOL + m3u8.sort().join(os_1.EOL);
|
|
257
|
+
(0, fs_1.writeFileSync)(playlistFile + '.m3u8', m3u8Content, { encoding: 'utf-8' });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
console.log(signale_1.default.info('No items to download!'));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
console.error(signale_1.default.error(err.message));
|
|
266
|
+
}
|
|
267
|
+
// Ask for new download
|
|
268
|
+
if (!options.headless && !skipPrompt) {
|
|
269
|
+
startDownload(saveLayout, '', skipPrompt);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Application init.
|
|
274
|
+
*/
|
|
275
|
+
const initApp = async () => {
|
|
276
|
+
if (options.setup) {
|
|
277
|
+
await (0, arl_setup_1.runSetup)(conf, options.headless, options.experimentalLogin);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (options.setArl) {
|
|
281
|
+
const configPath = conf.set('cookies.arl', options.setArl);
|
|
282
|
+
console.log(signale_1.default.info('cookies.arl set to --> ' + options.setArl));
|
|
283
|
+
console.log(signale_1.default.note(configPath));
|
|
284
|
+
process.exit();
|
|
285
|
+
}
|
|
286
|
+
(0, log_update_1.default)(signale_1.default.pending('Initializing session...'));
|
|
287
|
+
let arl = await (0, arl_setup_1.ensureArl)(conf, options.headless, options.experimentalLogin);
|
|
288
|
+
const login = async (cookie) => {
|
|
289
|
+
(0, log_update_1.default)(signale_1.default.pending('Verifying session...'));
|
|
290
|
+
await (0, hoenir_core_1.initDeezerApi)(cookie);
|
|
291
|
+
return (0, hoenir_core_1.getUser)();
|
|
292
|
+
};
|
|
293
|
+
let user;
|
|
294
|
+
try {
|
|
295
|
+
user = await login(arl);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
log_update_1.default.clear();
|
|
299
|
+
const newArl = await (0, arl_setup_1.recoverFromLoginFailure)(conf, options.headless, err, options.experimentalLogin);
|
|
300
|
+
if (!newArl) {
|
|
301
|
+
throw err;
|
|
302
|
+
}
|
|
303
|
+
arl = newArl;
|
|
304
|
+
user = await login(arl);
|
|
305
|
+
}
|
|
306
|
+
(0, log_update_1.default)(signale_1.default.success('Logged in as ' + user.BLOG_NAME));
|
|
307
|
+
log_update_1.default.done();
|
|
308
|
+
const saveLayout = conf.get('saveLayout');
|
|
309
|
+
if (options.inputFile) {
|
|
310
|
+
const urls = (0, fs_1.readFileSync)(options.inputFile, 'utf-8')
|
|
311
|
+
.split(/\r?\n/)
|
|
312
|
+
.map((line) => line.trim())
|
|
313
|
+
.filter((line) => line.match(urlRegex));
|
|
314
|
+
if (!options.quality && !options.headless) {
|
|
315
|
+
for (const url of urls) {
|
|
316
|
+
console.log(signale_1.default.info('Starting download: ' + url));
|
|
317
|
+
await startDownload(saveLayout, url, true);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
await Promise.all(urls.map((url) => {
|
|
322
|
+
console.log(signale_1.default.info('Starting download: ' + url));
|
|
323
|
+
return startDownload(saveLayout, url, true);
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
startDownload(saveLayout, options.url, false);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
if (options.update) {
|
|
332
|
+
(0, auto_updater_1.default)(package_json_1.default).catch((err) => {
|
|
333
|
+
console.error(signale_1.default.error(err.message));
|
|
334
|
+
process.exit(1);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// Check for update
|
|
339
|
+
(0, update_check_1.default)(package_json_1.default);
|
|
340
|
+
// Init interface
|
|
341
|
+
initApp().catch((err) => {
|
|
342
|
+
console.error(signale_1.default.error(err.message));
|
|
343
|
+
process.exit(1);
|
|
344
|
+
});
|
|
345
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public library entry point for `gerdur`.
|
|
3
|
+
*
|
|
4
|
+
* This module is safe to `import`/`require` from other packages — unlike the
|
|
5
|
+
* CLI entry (`gerdur.ts`), importing it has no side effects (no banner, no
|
|
6
|
+
* argument parsing, no process exit). All functions are silent; progress is
|
|
7
|
+
* surfaced through optional callbacks.
|
|
8
|
+
*
|
|
9
|
+
* @example High-level usage
|
|
10
|
+
* ```ts
|
|
11
|
+
* import {createSession} from 'gerdur';
|
|
12
|
+
*
|
|
13
|
+
* const session = await createSession({email: 'you@example.com', password: 'secret'});
|
|
14
|
+
* await session.downloadUrl('https://deezer.com/album/302127', 'flac', {
|
|
15
|
+
* output: 'Music/{ALB_TITLE}/{SNG_TITLE}',
|
|
16
|
+
* onProgress: ({index, total, track}) => console.log(`${index + 1}/${total} ${track.SNG_TITLE}`),
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example Low-level usage
|
|
21
|
+
* ```ts
|
|
22
|
+
* import {getArl, getTrackBuffer} from 'gerdur';
|
|
23
|
+
* import {initDeezerApi, getTrackInfo} from 'gerdur';
|
|
24
|
+
*
|
|
25
|
+
* const arl = await getArl('you@example.com', 'secret');
|
|
26
|
+
* await initDeezerApi(arl);
|
|
27
|
+
* const track = await getTrackInfo('3135556');
|
|
28
|
+
* const mp3 = await getTrackBuffer(track, '320'); // Buffer, not written to disk
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import { loginWithEmail } from './lib/email-login';
|
|
32
|
+
import type { LoginResult } from './lib/email-login';
|
|
33
|
+
export { loginWithEmail };
|
|
34
|
+
export type { LoginResult };
|
|
35
|
+
/**
|
|
36
|
+
* Thrown by {@link getArl} when an email/password login does not yield an arl.
|
|
37
|
+
* `reason` mirrors the failure reason from {@link LoginResult}.
|
|
38
|
+
*/
|
|
39
|
+
export declare class LoginError extends Error {
|
|
40
|
+
readonly reason: Exclude<LoginResult, {
|
|
41
|
+
ok: true;
|
|
42
|
+
}>['reason'];
|
|
43
|
+
constructor(reason: Exclude<LoginResult, {
|
|
44
|
+
ok: true;
|
|
45
|
+
}>['reason'], message: string);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Fetch a Deezer `arl` from email and password.
|
|
49
|
+
*
|
|
50
|
+
* Convenience wrapper around {@link loginWithEmail} that returns the arl string
|
|
51
|
+
* directly and throws a {@link LoginError} on failure. The password is never
|
|
52
|
+
* persisted or logged by this call.
|
|
53
|
+
*
|
|
54
|
+
* @throws {LoginError} If authentication fails or no arl could be retrieved.
|
|
55
|
+
*/
|
|
56
|
+
export declare const getArl: (email: string, password: string) => Promise<string>;
|
|
57
|
+
export { createSession, Session } from './lib/session';
|
|
58
|
+
export type { SessionOptions, DownloadTracksOptions, SearchType } from './lib/session';
|
|
59
|
+
export { getTrackBuffer, downloadTrackToFile } from './lib/api-download';
|
|
60
|
+
export type { Quality, GetTrackBufferOptions, DownloadTrackOptions, DownloadResult } from './lib/api-download';
|
|
61
|
+
export { default as Config, globalConfigPath, resolveConfigFile } from './lib/config';
|
|
62
|
+
export { initDeezerApi, parseInfo, searchMusic, getUser, getTrackInfo, getAlbumInfo, getAlbumTracks, getPlaylistInfo, getPlaylistTracks, getArtistInfo, getDiscography, getLyrics, getTrackDownloadUrl, GeoBlocked, } from 'gerdur-core';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Public library entry point for `gerdur`.
|
|
4
|
+
*
|
|
5
|
+
* This module is safe to `import`/`require` from other packages — unlike the
|
|
6
|
+
* CLI entry (`gerdur.ts`), importing it has no side effects (no banner, no
|
|
7
|
+
* argument parsing, no process exit). All functions are silent; progress is
|
|
8
|
+
* surfaced through optional callbacks.
|
|
9
|
+
*
|
|
10
|
+
* @example High-level usage
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {createSession} from 'gerdur';
|
|
13
|
+
*
|
|
14
|
+
* const session = await createSession({email: 'you@example.com', password: 'secret'});
|
|
15
|
+
* await session.downloadUrl('https://deezer.com/album/302127', 'flac', {
|
|
16
|
+
* output: 'Music/{ALB_TITLE}/{SNG_TITLE}',
|
|
17
|
+
* onProgress: ({index, total, track}) => console.log(`${index + 1}/${total} ${track.SNG_TITLE}`),
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example Low-level usage
|
|
22
|
+
* ```ts
|
|
23
|
+
* import {getArl, getTrackBuffer} from 'gerdur';
|
|
24
|
+
* import {initDeezerApi, getTrackInfo} from 'gerdur';
|
|
25
|
+
*
|
|
26
|
+
* const arl = await getArl('you@example.com', 'secret');
|
|
27
|
+
* await initDeezerApi(arl);
|
|
28
|
+
* const track = await getTrackInfo('3135556');
|
|
29
|
+
* const mp3 = await getTrackBuffer(track, '320'); // Buffer, not written to disk
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
33
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.GeoBlocked = exports.getTrackDownloadUrl = exports.getLyrics = exports.getDiscography = exports.getArtistInfo = exports.getPlaylistTracks = exports.getPlaylistInfo = exports.getAlbumTracks = exports.getAlbumInfo = exports.getTrackInfo = exports.getUser = exports.searchMusic = exports.parseInfo = exports.initDeezerApi = exports.resolveConfigFile = exports.globalConfigPath = exports.Config = exports.downloadTrackToFile = exports.getTrackBuffer = exports.Session = exports.createSession = exports.getArl = exports.LoginError = exports.loginWithEmail = void 0;
|
|
37
|
+
// ─── Authentication ────────────────────────────────────────────────────────
|
|
38
|
+
const email_login_1 = require("./lib/email-login");
|
|
39
|
+
Object.defineProperty(exports, "loginWithEmail", { enumerable: true, get: function () { return email_login_1.loginWithEmail; } });
|
|
40
|
+
/**
|
|
41
|
+
* Thrown by {@link getArl} when an email/password login does not yield an arl.
|
|
42
|
+
* `reason` mirrors the failure reason from {@link LoginResult}.
|
|
43
|
+
*/
|
|
44
|
+
class LoginError extends Error {
|
|
45
|
+
reason;
|
|
46
|
+
constructor(reason, message) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = 'LoginError';
|
|
49
|
+
this.reason = reason;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.LoginError = LoginError;
|
|
53
|
+
/**
|
|
54
|
+
* Fetch a Deezer `arl` from email and password.
|
|
55
|
+
*
|
|
56
|
+
* Convenience wrapper around {@link loginWithEmail} that returns the arl string
|
|
57
|
+
* directly and throws a {@link LoginError} on failure. The password is never
|
|
58
|
+
* persisted or logged by this call.
|
|
59
|
+
*
|
|
60
|
+
* @throws {LoginError} If authentication fails or no arl could be retrieved.
|
|
61
|
+
*/
|
|
62
|
+
const getArl = async (email, password) => {
|
|
63
|
+
const result = await (0, email_login_1.loginWithEmail)(email, password);
|
|
64
|
+
if (result.ok) {
|
|
65
|
+
return result.arl;
|
|
66
|
+
}
|
|
67
|
+
throw new LoginError(result.reason, result.message);
|
|
68
|
+
};
|
|
69
|
+
exports.getArl = getArl;
|
|
70
|
+
// ─── High-level session ──────────────────────────────────────────────────────
|
|
71
|
+
var session_1 = require("./lib/session");
|
|
72
|
+
Object.defineProperty(exports, "createSession", { enumerable: true, get: function () { return session_1.createSession; } });
|
|
73
|
+
Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_1.Session; } });
|
|
74
|
+
// ─── Download primitives ─────────────────────────────────────────────────────
|
|
75
|
+
var api_download_1 = require("./lib/api-download");
|
|
76
|
+
Object.defineProperty(exports, "getTrackBuffer", { enumerable: true, get: function () { return api_download_1.getTrackBuffer; } });
|
|
77
|
+
Object.defineProperty(exports, "downloadTrackToFile", { enumerable: true, get: function () { return api_download_1.downloadTrackToFile; } });
|
|
78
|
+
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
79
|
+
// The same config the CLI uses (env var, global path resolution, arl storage).
|
|
80
|
+
var config_1 = require("./lib/config");
|
|
81
|
+
Object.defineProperty(exports, "Config", { enumerable: true, get: function () { return __importDefault(config_1).default; } });
|
|
82
|
+
Object.defineProperty(exports, "globalConfigPath", { enumerable: true, get: function () { return config_1.globalConfigPath; } });
|
|
83
|
+
Object.defineProperty(exports, "resolveConfigFile", { enumerable: true, get: function () { return config_1.resolveConfigFile; } });
|
|
84
|
+
// ─── Re-exported core query functions ────────────────────────────────────────
|
|
85
|
+
// Convenience re-exports so consumers don't need `gerdur-core` directly.
|
|
86
|
+
// After `initDeezerApi(arl)` (or `createSession`) these are ready to use.
|
|
87
|
+
var gerdur_core_1 = require("gerdur-core");
|
|
88
|
+
Object.defineProperty(exports, "initDeezerApi", { enumerable: true, get: function () { return gerdur_core_1.initDeezerApi; } });
|
|
89
|
+
Object.defineProperty(exports, "parseInfo", { enumerable: true, get: function () { return gerdur_core_1.parseInfo; } });
|
|
90
|
+
Object.defineProperty(exports, "searchMusic", { enumerable: true, get: function () { return gerdur_core_1.searchMusic; } });
|
|
91
|
+
Object.defineProperty(exports, "getUser", { enumerable: true, get: function () { return gerdur_core_1.getUser; } });
|
|
92
|
+
Object.defineProperty(exports, "getTrackInfo", { enumerable: true, get: function () { return gerdur_core_1.getTrackInfo; } });
|
|
93
|
+
Object.defineProperty(exports, "getAlbumInfo", { enumerable: true, get: function () { return gerdur_core_1.getAlbumInfo; } });
|
|
94
|
+
Object.defineProperty(exports, "getAlbumTracks", { enumerable: true, get: function () { return gerdur_core_1.getAlbumTracks; } });
|
|
95
|
+
Object.defineProperty(exports, "getPlaylistInfo", { enumerable: true, get: function () { return gerdur_core_1.getPlaylistInfo; } });
|
|
96
|
+
Object.defineProperty(exports, "getPlaylistTracks", { enumerable: true, get: function () { return gerdur_core_1.getPlaylistTracks; } });
|
|
97
|
+
Object.defineProperty(exports, "getArtistInfo", { enumerable: true, get: function () { return gerdur_core_1.getArtistInfo; } });
|
|
98
|
+
Object.defineProperty(exports, "getDiscography", { enumerable: true, get: function () { return gerdur_core_1.getDiscography; } });
|
|
99
|
+
Object.defineProperty(exports, "getLyrics", { enumerable: true, get: function () { return gerdur_core_1.getLyrics; } });
|
|
100
|
+
Object.defineProperty(exports, "getTrackDownloadUrl", { enumerable: true, get: function () { return gerdur_core_1.getTrackDownloadUrl; } });
|
|
101
|
+
Object.defineProperty(exports, "GeoBlocked", { enumerable: true, get: function () { return gerdur_core_1.GeoBlocked; } });
|