gerdur 0.0.0-probe → 1.0.1

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.
@@ -0,0 +1,82 @@
1
+ import type Config from './config';
2
+ /**
3
+ * True when we are attached to an interactive terminal and not running in a
4
+ * scripted / headless context. Only then is it safe to block on a prompt.
5
+ */
6
+ export declare const isInteractive: (headless: boolean) => boolean;
7
+ /**
8
+ * Print short, copy-pasteable instructions on where to obtain a Deezer arl cookie.
9
+ */
10
+ export declare const printArlInstructions: () => void;
11
+ /**
12
+ * Interactively ask the user to paste an arl cookie and persist it to config.
13
+ *
14
+ * Returns the entered arl, or `null` if the user aborted / gave nothing.
15
+ * The caller is responsible for only invoking this in an interactive context.
16
+ */
17
+ export declare const promptForArl: (conf: Config) => Promise<string | null>;
18
+ /**
19
+ * Resolve email/password credentials from (in order): explicit config, then the
20
+ * `GERDUR_EMAIL` / `GERDUR_PASSWORD` environment variables. Used for non-interactive
21
+ * login attempts. Returns `null` if either half is missing.
22
+ */
23
+ export declare const resolveCredentials: (conf: Config) => {
24
+ email: string;
25
+ password: string;
26
+ } | null;
27
+ /**
28
+ * Attempt an email/password login and, on success, persist the fetched arl.
29
+ * The password itself is never written to disk by this function.
30
+ *
31
+ * Returns the arl on success, or `null` on any failure (the caller should fall
32
+ * back to pasting an arl).
33
+ */
34
+ export declare const tryEmailLogin: (conf: Config, email: string, password: string, { persistCredentials }?: {
35
+ persistCredentials?: boolean | undefined;
36
+ }) => Promise<string | null>;
37
+ /**
38
+ * Interactively prompt for email + password (password masked), attempt login,
39
+ * and persist the arl on success. Offers to save the credentials to config
40
+ * (with a plaintext warning) if the login worked.
41
+ *
42
+ * Returns the arl on success, or `null` on failure/cancel so the caller can
43
+ * fall back to pasting an arl.
44
+ */
45
+ export declare const promptForEmailLogin: (conf: Config) => Promise<string | null>;
46
+ /**
47
+ * Interactive picker for how to configure an arl.
48
+ *
49
+ * Offers email/password login (which fetches the arl automatically via Deezer's
50
+ * mobile OAuth flow) and pasting an arl. Email login falls back to arl paste if
51
+ * it fails. The `_experimentalLogin` parameter is retained for backward
52
+ * compatibility and no longer gates the email option.
53
+ *
54
+ * Returns the arl on success or `null` if nothing was configured.
55
+ */
56
+ export declare const chooseAndConfigureArl: (conf: Config, _experimentalLogin?: boolean) => Promise<string | null>;
57
+ /**
58
+ * Ensure a usable arl is configured before attempting to log in.
59
+ *
60
+ * When no user-supplied arl exists (empty or still the bundled default) and we
61
+ * are in an interactive terminal, print instructions and prompt for one.
62
+ * In non-interactive / headless contexts this is a no-op so existing scripted
63
+ * behavior is preserved.
64
+ *
65
+ * Returns the arl to use for the session.
66
+ */
67
+ export declare const ensureArl: (conf: Config, headless: boolean, experimentalLogin?: boolean) => Promise<string>;
68
+ /**
69
+ * On-demand guided setup, triggered by `gerdur setup` / `--setup`.
70
+ *
71
+ * Always prints instructions and prompts for an arl (even when one is already
72
+ * configured), so users can (re)configure without hunting for the flag.
73
+ * Requires an interactive terminal.
74
+ */
75
+ export declare const runSetup: (conf: Config, headless: boolean, experimentalLogin?: boolean) => Promise<void>;
76
+ /**
77
+ * Handle a failed login by offering to enter a fresh arl and retrying.
78
+ *
79
+ * Returns the new arl if the user provided one, otherwise `null` (and the
80
+ * original error should be surfaced by the caller).
81
+ */
82
+ export declare const recoverFromLoginFailure: (conf: Config, headless: boolean, err: Error, experimentalLogin?: boolean) => Promise<string | null>;
@@ -0,0 +1,274 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.recoverFromLoginFailure = exports.runSetup = exports.ensureArl = exports.chooseAndConfigureArl = exports.promptForEmailLogin = exports.tryEmailLogin = exports.resolveCredentials = exports.promptForArl = exports.printArlInstructions = exports.isInteractive = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const prompts_1 = __importDefault(require("prompts"));
9
+ const signale_1 = __importDefault(require("./signale"));
10
+ const email_login_1 = require("./email-login");
11
+ /**
12
+ * True when we are attached to an interactive terminal and not running in a
13
+ * scripted / headless context. Only then is it safe to block on a prompt.
14
+ */
15
+ const isInteractive = (headless) => !headless && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
16
+ exports.isInteractive = isInteractive;
17
+ /**
18
+ * Print short, copy-pasteable instructions on where to obtain a Deezer arl cookie.
19
+ */
20
+ const printArlInstructions = () => {
21
+ console.log(signale_1.default.info('An ' + chalk_1.default.yellow('arl') + ' cookie is required to log in to Deezer.'));
22
+ console.log(signale_1.default.note('How to get it:'));
23
+ console.log(signale_1.default.note('1. Open ' + chalk_1.default.cyan('https://www.deezer.com') + ' in your browser and log in.'));
24
+ console.log(signale_1.default.note('2. Open DevTools (F12) → ' + chalk_1.default.cyan('Application') + ' → Cookies → deezer.com.'));
25
+ console.log(signale_1.default.note('3. Copy the value of the ' + chalk_1.default.yellow('arl') + ' cookie (a long hex string).'));
26
+ console.log(signale_1.default.note('You can also set it any time with: ' + chalk_1.default.cyan('gerdur --set-arl <arl>')));
27
+ console.log(signale_1.default.note('Or without a config file: ' + chalk_1.default.cyan('GERDUR_ARL=<arl> gerdur ...')));
28
+ };
29
+ exports.printArlInstructions = printArlInstructions;
30
+ /**
31
+ * Interactively ask the user to paste an arl cookie and persist it to config.
32
+ *
33
+ * Returns the entered arl, or `null` if the user aborted / gave nothing.
34
+ * The caller is responsible for only invoking this in an interactive context.
35
+ */
36
+ const promptForArl = async (conf) => {
37
+ const { arl } = await (0, prompts_1.default)([
38
+ {
39
+ type: 'password',
40
+ name: 'arl',
41
+ message: 'Paste your arl cookie:',
42
+ validate: (value) => {
43
+ const v = (value || '').trim();
44
+ if (!v) {
45
+ return 'arl cannot be empty';
46
+ }
47
+ if (!/^[a-f0-9]{16,}$/i.test(v)) {
48
+ return 'That does not look like an arl (expected a long hex string)';
49
+ }
50
+ return true;
51
+ },
52
+ },
53
+ ], {
54
+ onCancel: () => {
55
+ console.log(signale_1.default.note('Aborted.'));
56
+ process.exit();
57
+ },
58
+ });
59
+ const value = (arl || '').trim();
60
+ if (!value) {
61
+ return null;
62
+ }
63
+ const configPath = conf.set('cookies.arl', value);
64
+ console.log(signale_1.default.success('arl saved.'));
65
+ console.log(signale_1.default.note(configPath));
66
+ return value;
67
+ };
68
+ exports.promptForArl = promptForArl;
69
+ /**
70
+ * Resolve email/password credentials from (in order): explicit config, then the
71
+ * `GERDUR_EMAIL` / `GERDUR_PASSWORD` environment variables. Used for non-interactive
72
+ * login attempts. Returns `null` if either half is missing.
73
+ */
74
+ const resolveCredentials = (conf) => {
75
+ const email = (conf.get('cookies.email') || process.env.GERDUR_EMAIL || '').trim();
76
+ const password = (conf.get('cookies.password') || process.env.GERDUR_PASSWORD || '').trim();
77
+ if (email && password) {
78
+ return { email, password };
79
+ }
80
+ return null;
81
+ };
82
+ exports.resolveCredentials = resolveCredentials;
83
+ /**
84
+ * Attempt an email/password login and, on success, persist the fetched arl.
85
+ * The password itself is never written to disk by this function.
86
+ *
87
+ * Returns the arl on success, or `null` on any failure (the caller should fall
88
+ * back to pasting an arl).
89
+ */
90
+ const tryEmailLogin = async (conf, email, password, { persistCredentials = false } = {}) => {
91
+ console.log(signale_1.default.pending('Logging in as ' + email + '...'));
92
+ const result = await (0, email_login_1.loginWithEmail)(email, password);
93
+ if (!result.ok) {
94
+ console.log(signale_1.default.warn(result.message));
95
+ return null;
96
+ }
97
+ if (persistCredentials) {
98
+ conf.set('cookies.email', email);
99
+ conf.set('cookies.password', password);
100
+ console.log(signale_1.default.warn('Email and password saved in plaintext in your config file. Anyone with the file can read them.'));
101
+ }
102
+ const configPath = conf.set('cookies.arl', result.arl);
103
+ console.log(signale_1.default.success('Logged in and saved arl.'));
104
+ console.log(signale_1.default.note(configPath));
105
+ return result.arl;
106
+ };
107
+ exports.tryEmailLogin = tryEmailLogin;
108
+ /**
109
+ * Interactively prompt for email + password (password masked), attempt login,
110
+ * and persist the arl on success. Offers to save the credentials to config
111
+ * (with a plaintext warning) if the login worked.
112
+ *
113
+ * Returns the arl on success, or `null` on failure/cancel so the caller can
114
+ * fall back to pasting an arl.
115
+ */
116
+ const promptForEmailLogin = async (conf) => {
117
+ const { email, password } = await (0, prompts_1.default)([
118
+ {
119
+ type: 'text',
120
+ name: 'email',
121
+ message: 'Deezer email:',
122
+ validate: (v) => (v && v.includes('@') ? true : 'Enter a valid email'),
123
+ },
124
+ {
125
+ type: 'password',
126
+ name: 'password',
127
+ message: 'Deezer password:',
128
+ validate: (v) => (v ? true : 'Password cannot be empty'),
129
+ },
130
+ ], {
131
+ onCancel: () => {
132
+ console.log(signale_1.default.note('Aborted.'));
133
+ process.exit();
134
+ },
135
+ });
136
+ if (!email || !password) {
137
+ return null;
138
+ }
139
+ const arl = await (0, exports.tryEmailLogin)(conf, email, password);
140
+ if (!arl) {
141
+ return null;
142
+ }
143
+ const { save } = await (0, prompts_1.default)([
144
+ {
145
+ type: 'confirm',
146
+ name: 'save',
147
+ message: 'Save email & password to config for future logins? (stored in plaintext)',
148
+ initial: false,
149
+ },
150
+ ]);
151
+ if (save) {
152
+ conf.set('cookies.email', email);
153
+ conf.set('cookies.password', password);
154
+ console.log(signale_1.default.warn('Saved in plaintext. Delete them from your config to remove.'));
155
+ }
156
+ return arl;
157
+ };
158
+ exports.promptForEmailLogin = promptForEmailLogin;
159
+ /**
160
+ * Interactive picker for how to configure an arl.
161
+ *
162
+ * Offers email/password login (which fetches the arl automatically via Deezer's
163
+ * mobile OAuth flow) and pasting an arl. Email login falls back to arl paste if
164
+ * it fails. The `_experimentalLogin` parameter is retained for backward
165
+ * compatibility and no longer gates the email option.
166
+ *
167
+ * Returns the arl on success or `null` if nothing was configured.
168
+ */
169
+ const chooseAndConfigureArl = async (conf, _experimentalLogin = false) => {
170
+ const { method } = await (0, prompts_1.default)([
171
+ {
172
+ type: 'select',
173
+ name: 'method',
174
+ message: 'How would you like to log in?',
175
+ choices: [
176
+ { title: 'Email & password (fetches your arl automatically)', value: 'email' },
177
+ { title: 'Paste an arl cookie', value: 'arl' },
178
+ ],
179
+ initial: 0,
180
+ },
181
+ ]);
182
+ if (method === 'email') {
183
+ const arl = await (0, exports.promptForEmailLogin)(conf);
184
+ if (arl) {
185
+ return arl;
186
+ }
187
+ console.log(signale_1.default.note('Falling back to entering an arl manually.'));
188
+ }
189
+ (0, exports.printArlInstructions)();
190
+ console.log('');
191
+ return (0, exports.promptForArl)(conf);
192
+ };
193
+ exports.chooseAndConfigureArl = chooseAndConfigureArl;
194
+ /**
195
+ * Ensure a usable arl is configured before attempting to log in.
196
+ *
197
+ * When no user-supplied arl exists (empty or still the bundled default) and we
198
+ * are in an interactive terminal, print instructions and prompt for one.
199
+ * In non-interactive / headless contexts this is a no-op so existing scripted
200
+ * behavior is preserved.
201
+ *
202
+ * Returns the arl to use for the session.
203
+ */
204
+ const ensureArl = async (conf, headless, experimentalLogin = false) => {
205
+ if (conf.hasUserArl()) {
206
+ return conf.getArl();
207
+ }
208
+ // Non-interactive: no prompting. If email/password are configured (env/config)
209
+ // but no arl is set yet, attempt a login so scripted/headless runs can work
210
+ // from credentials alone. Otherwise keep the existing (default arl) behavior.
211
+ if (!(0, exports.isInteractive)(headless)) {
212
+ const creds = (0, exports.resolveCredentials)(conf);
213
+ if (creds) {
214
+ const arl = await (0, exports.tryEmailLogin)(conf, creds.email, creds.password);
215
+ if (arl) {
216
+ return arl;
217
+ }
218
+ }
219
+ return conf.getArl();
220
+ }
221
+ console.log(signale_1.default.warn('No arl cookie configured yet (using the shared default).'));
222
+ const arl = await (0, exports.chooseAndConfigureArl)(conf, experimentalLogin);
223
+ return arl ?? conf.getArl();
224
+ };
225
+ exports.ensureArl = ensureArl;
226
+ /**
227
+ * On-demand guided setup, triggered by `gerdur setup` / `--setup`.
228
+ *
229
+ * Always prints instructions and prompts for an arl (even when one is already
230
+ * configured), so users can (re)configure without hunting for the flag.
231
+ * Requires an interactive terminal.
232
+ */
233
+ const runSetup = async (conf, headless, experimentalLogin = false) => {
234
+ if (!(0, exports.isInteractive)(headless)) {
235
+ console.error(signale_1.default.error('Setup requires an interactive terminal.'));
236
+ console.error(signale_1.default.note('Use ' + chalk_1.default.cyan('gerdur --set-arl <arl>') + ' or the GERDUR_ARL env var instead.'));
237
+ process.exit(1);
238
+ }
239
+ console.log(signale_1.default.info('gerdur guided setup'));
240
+ if (conf.hasUserArl()) {
241
+ console.log(signale_1.default.note('An arl is already configured; continuing will replace it.'));
242
+ }
243
+ await (0, exports.chooseAndConfigureArl)(conf, experimentalLogin);
244
+ console.log(signale_1.default.success('Setup complete. Run ' + chalk_1.default.cyan('gerdur') + ' to start downloading.'));
245
+ process.exit();
246
+ };
247
+ exports.runSetup = runSetup;
248
+ /**
249
+ * Handle a failed login by offering to enter a fresh arl and retrying.
250
+ *
251
+ * Returns the new arl if the user provided one, otherwise `null` (and the
252
+ * original error should be surfaced by the caller).
253
+ */
254
+ const recoverFromLoginFailure = async (conf, headless, err, experimentalLogin = false) => {
255
+ if (!(0, exports.isInteractive)(headless)) {
256
+ return null;
257
+ }
258
+ console.log(signale_1.default.error('Could not log in: ' + err.message));
259
+ console.log(signale_1.default.note('Your arl cookie is likely missing, expired, or invalid.'));
260
+ console.log('');
261
+ const { retry } = await (0, prompts_1.default)([
262
+ {
263
+ type: 'confirm',
264
+ name: 'retry',
265
+ message: 'Set up a new login now?',
266
+ initial: true,
267
+ },
268
+ ]);
269
+ if (!retry) {
270
+ return null;
271
+ }
272
+ return (0, exports.chooseAndConfigureArl)(conf, experimentalLogin);
273
+ };
274
+ exports.recoverFromLoginFailure = recoverFromLoginFailure;
@@ -0,0 +1,2 @@
1
+ declare const updateBinary: (pkg: any) => Promise<void>;
2
+ export default updateBinary;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = require("fs");
7
+ const got_1 = __importDefault(require("got"));
8
+ const adm_zip_1 = __importDefault(require("adm-zip"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const log_update_1 = __importDefault(require("log-update"));
11
+ const signale_1 = __importDefault(require("./signale"));
12
+ const util_1 = require("./util");
13
+ const updateCheck = async (pkg) => {
14
+ const beta = pkg.version.includes('beta');
15
+ const releases = await (0, got_1.default)('https://api.github.com/repos/soulwax/gerdur/releases').json();
16
+ const data = releases.filter((r) => r.prerelease === beta)[0];
17
+ if (data.tag_name > pkg.version) {
18
+ return data;
19
+ }
20
+ log_update_1.default.clear();
21
+ console.info(signale_1.default.info(`gerdur is up-to-date (${data.tag_name})`));
22
+ };
23
+ const updateBinary = async (pkg) => {
24
+ (0, log_update_1.default)(signale_1.default.info('Checking for new updates...'));
25
+ const data = await updateCheck(pkg);
26
+ log_update_1.default.clear();
27
+ if (data) {
28
+ let platform = '';
29
+ switch (process.platform) {
30
+ case 'linux':
31
+ case 'cygwin':
32
+ platform = 'linux';
33
+ break;
34
+ case 'darwin':
35
+ platform = 'macos';
36
+ break;
37
+ case 'win32':
38
+ platform = 'win';
39
+ break;
40
+ default:
41
+ throw new Error('Unknown platform: ' + process.platform);
42
+ }
43
+ const bin = `gerdur-${data.tag_name}${platform === 'win' ? '.exe' : ''}`;
44
+ if ((0, fs_1.existsSync)(bin)) {
45
+ throw new Error(`${bin} already exists`);
46
+ }
47
+ const asset = data.assets.find((a) => a.name.includes(platform));
48
+ if (asset) {
49
+ console.log(signale_1.default.info('Changelog:'));
50
+ console.log(data.body + '\n');
51
+ console.log(` Downloading ${asset.browser_download_url}`);
52
+ const bar = (0, util_1.progressBar)(asset.size, 40);
53
+ const humanSizeTotal = (asset.size / 1024 / 1024).toFixed(2);
54
+ const { body } = await (0, got_1.default)(asset.browser_download_url, { responseType: 'buffer' }).on('downloadProgress', ({ transferred }) => {
55
+ (0, log_update_1.default)(` ${bar(transferred)} | ${humanSizeTotal}MiB`);
56
+ });
57
+ log_update_1.default.done();
58
+ (0, log_update_1.default)(signale_1.default.info('Extracting archive...'));
59
+ const zip = new adm_zip_1.default(body).getEntries()[0].getData();
60
+ log_update_1.default.clear();
61
+ console.log(signale_1.default.info('Writing to ' + bin));
62
+ (0, fs_1.writeFileSync)(bin, zip, { mode: '755' });
63
+ console.log(signale_1.default.info(`Updated. Run ${chalk_1.default.yellow(bin)} to use the new version.`));
64
+ }
65
+ else {
66
+ throw new Error(`Unable to find asset for ${platform}`);
67
+ }
68
+ }
69
+ };
70
+ exports.default = updateBinary;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Default per-user (global) config location, so `gerdur` works from any directory
3
+ * without needing a `gerdur.config.json` in the current folder.
4
+ * Honors `XDG_CONFIG_HOME` on Linux/macOS, otherwise falls back to `~/.config`.
5
+ */
6
+ export declare const globalConfigPath: () => string;
7
+ /**
8
+ * Resolve which config file to use when the user did not pass `--config-file`.
9
+ * Preference order:
10
+ * 1. `gerdur.config.json` in the current directory (project-local, legacy behavior)
11
+ * 2. the global config file (if it already exists)
12
+ * 3. the global config path (created on first write)
13
+ */
14
+ export declare const resolveConfigFile: (configFile: string) => string;
15
+ type keysType = 'concurrency' | 'saveLayout' | 'saveLayout.track' | 'saveLayout.album' | 'saveLayout.artist' | 'saveLayout.playlist' | 'playlist.resolveFullPath' | 'trackNumber' | 'fallbackTrack' | 'fallbackQuality' | 'overwrite' | 'coverSize' | 'coverSize.128' | 'coverSize.320' | 'coverSize.flac' | 'cookies.arl' | 'cookies.email' | 'cookies.password';
16
+ declare class Config {
17
+ userConfigLocation: string | null;
18
+ private configFile;
19
+ private store;
20
+ constructor(configFile?: string);
21
+ private getConfig;
22
+ /**
23
+ * Get an item.
24
+ * @param key - The key of the item to get.
25
+ * @param defaultValue - The default value if the item does not exist.
26
+ */
27
+ get(key: keysType, defaultValue?: string | boolean | number): string | number | boolean | undefined;
28
+ /**
29
+ * Set an item or multiple items at once.
30
+ * @param {key|object} - You can use [dot-notation](https://github.com/sindresorhus/dot-prop) in a key to access nested properties. Or a hashmap of items to set at once.
31
+ * @param value - Must be JSON serializable. Trying to set the type `undefined`, `function`, or `symbol` will result in a `TypeError`.
32
+ */
33
+ set(key: keysType, value: string | boolean | number, persist?: boolean): string;
34
+ /**
35
+ * Delete an item.
36
+ * @param key - The key of the item to delete.
37
+ */
38
+ delete(key: keysType): void;
39
+ /**
40
+ * The arl to use for the session. The `GERDUR_ARL` environment variable takes
41
+ * precedence over the stored config so it can be supplied without writing a
42
+ * config file (useful for CI, containers, and quick one-offs).
43
+ */
44
+ getArl(): string;
45
+ /**
46
+ * Whether a usable arl cookie has been provided by the user, via the
47
+ * `GERDUR_ARL` environment variable or config. Returns `false` when the arl is
48
+ * empty or still the bundled default (which is shared, rate-limited, and
49
+ * expires) so callers can prompt for one.
50
+ */
51
+ hasUserArl(): boolean;
52
+ }
53
+ export default Config;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveConfigFile = exports.globalConfigPath = void 0;
7
+ const fs_1 = require("fs");
8
+ const os_1 = require("os");
9
+ const path_1 = require("path");
10
+ const dot_prop_1 = __importDefault(require("dot-prop"));
11
+ const signale_1 = __importDefault(require("./signale"));
12
+ /**
13
+ * Default per-user (global) config location, so `gerdur` works from any directory
14
+ * without needing a `gerdur.config.json` in the current folder.
15
+ * Honors `XDG_CONFIG_HOME` on Linux/macOS, otherwise falls back to `~/.config`.
16
+ */
17
+ const globalConfigPath = () => {
18
+ const base = process.env.XDG_CONFIG_HOME || (0, path_1.join)((0, os_1.homedir)(), '.config');
19
+ return (0, path_1.join)(base, 'gerdur', 'gerdur.config.json');
20
+ };
21
+ exports.globalConfigPath = globalConfigPath;
22
+ /**
23
+ * Resolve which config file to use when the user did not pass `--config-file`.
24
+ * Preference order:
25
+ * 1. `gerdur.config.json` in the current directory (project-local, legacy behavior)
26
+ * 2. the global config file (if it already exists)
27
+ * 3. the global config path (created on first write)
28
+ */
29
+ const resolveConfigFile = (configFile) => {
30
+ const isDefaultName = configFile === 'gerdur.config.json';
31
+ if (!isDefaultName) {
32
+ return configFile;
33
+ }
34
+ if ((0, fs_1.existsSync)(configFile)) {
35
+ return configFile;
36
+ }
37
+ const global = (0, exports.globalConfigPath)();
38
+ if ((0, fs_1.existsSync)(global)) {
39
+ return global;
40
+ }
41
+ return configFile;
42
+ };
43
+ exports.resolveConfigFile = resolveConfigFile;
44
+ const old_arl = 'c911a4ac9f44a52bf23720cc88588557d999b975094068d258e617bf3e9110a2626c2ff7f5d3cb471b435512e0f5a4de4d7d7e3becad4bf80b0a0e230d9001a814124f87833fe772fb6b1327d2be740f65bc5bcfc1de9171926b5ea9aae69db7';
45
+ const defaultConfig = {
46
+ concurrency: 4,
47
+ saveLayout: {
48
+ track: 'Music/{ALB_TITLE}/{SNG_TITLE}',
49
+ album: 'Music/{ALB_TITLE}/{SNG_TITLE}',
50
+ artist: 'Music/{ALB_TITLE}/{SNG_TITLE}',
51
+ playlist: 'Playlist/{TITLE}/{SNG_TITLE}',
52
+ },
53
+ playlist: {
54
+ resolveFullPath: false,
55
+ },
56
+ trackNumber: true,
57
+ fallbackTrack: true,
58
+ fallbackQuality: true,
59
+ overwrite: false,
60
+ coverSize: {
61
+ '128': 500,
62
+ '320': 500,
63
+ flac: 1000,
64
+ },
65
+ cookies: {
66
+ arl: 'c973964816688562722418b5200c1515dffaad15a42643ebf87cc72824a54612ec51c2ad42d566743f9e424c774e98ccae7737770acff59251328e6cd598c7bcac38ca269adf78bfb88ec5bbad6cd800db3c0b88b2af645bb22b99e71de26416',
67
+ },
68
+ };
69
+ class Config {
70
+ userConfigLocation;
71
+ configFile;
72
+ store;
73
+ constructor(configFile = 'gerdur.config.json') {
74
+ this.userConfigLocation = null;
75
+ this.configFile = (0, exports.resolveConfigFile)(configFile);
76
+ this.store = this.getConfig(this.configFile);
77
+ // migrate data
78
+ if (this.store.cookies.arl === old_arl) {
79
+ this.set('cookies.arl', defaultConfig.cookies.arl);
80
+ }
81
+ }
82
+ getConfig(configFile) {
83
+ if (!(0, fs_1.existsSync)(configFile)) {
84
+ return defaultConfig;
85
+ }
86
+ try {
87
+ const userConfig = JSON.parse((0, fs_1.readFileSync)(configFile, 'utf-8'));
88
+ if (userConfig.saveLayout) {
89
+ userConfig.saveLayout = { ...defaultConfig.saveLayout, ...userConfig.saveLayout };
90
+ }
91
+ if (userConfig.playlist) {
92
+ userConfig.playlist = { ...defaultConfig.playlist, ...userConfig.playlist };
93
+ }
94
+ if (userConfig.coverSize) {
95
+ userConfig.coverSize = { ...defaultConfig.coverSize, ...userConfig.coverSize };
96
+ }
97
+ if (userConfig.cookies) {
98
+ userConfig.cookies = { ...defaultConfig.cookies, ...userConfig.cookies };
99
+ }
100
+ this.userConfigLocation = configFile;
101
+ return { ...defaultConfig, ...userConfig };
102
+ }
103
+ catch (err) {
104
+ console.error(signale_1.default.error(`Unable to parse config: ${configFile}`));
105
+ console.error(signale_1.default.note(err.message));
106
+ console.warn(signale_1.default.warn('Falling back to default config'));
107
+ return defaultConfig;
108
+ }
109
+ }
110
+ /**
111
+ * Get an item.
112
+ * @param key - The key of the item to get.
113
+ * @param defaultValue - The default value if the item does not exist.
114
+ */
115
+ get(key, defaultValue) {
116
+ return dot_prop_1.default.get(this.store, key, defaultValue);
117
+ }
118
+ /**
119
+ * Set an item or multiple items at once.
120
+ * @param {key|object} - You can use [dot-notation](https://github.com/sindresorhus/dot-prop) in a key to access nested properties. Or a hashmap of items to set at once.
121
+ * @param value - Must be JSON serializable. Trying to set the type `undefined`, `function`, or `symbol` will result in a `TypeError`.
122
+ */
123
+ set(key, value, persist = true) {
124
+ dot_prop_1.default.set(this.store, key, value);
125
+ if (persist) {
126
+ const dir = (0, path_1.dirname)(this.configFile);
127
+ if (dir && !(0, fs_1.existsSync)(dir)) {
128
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
129
+ }
130
+ (0, fs_1.writeFileSync)(this.configFile, JSON.stringify(this.store, null, 2));
131
+ }
132
+ return this.configFile;
133
+ }
134
+ /**
135
+ * Delete an item.
136
+ * @param key - The key of the item to delete.
137
+ */
138
+ delete(key) {
139
+ dot_prop_1.default.delete(this.store, key);
140
+ }
141
+ /**
142
+ * The arl to use for the session. The `GERDUR_ARL` environment variable takes
143
+ * precedence over the stored config so it can be supplied without writing a
144
+ * config file (useful for CI, containers, and quick one-offs).
145
+ */
146
+ getArl() {
147
+ const env = (process.env.GERDUR_ARL || '').trim();
148
+ if (env) {
149
+ return env;
150
+ }
151
+ return dot_prop_1.default.get(this.store, 'cookies.arl', '');
152
+ }
153
+ /**
154
+ * Whether a usable arl cookie has been provided by the user, via the
155
+ * `GERDUR_ARL` environment variable or config. Returns `false` when the arl is
156
+ * empty or still the bundled default (which is shared, rate-limited, and
157
+ * expires) so callers can prompt for one.
158
+ */
159
+ hasUserArl() {
160
+ const arl = this.getArl();
161
+ return Boolean(arl) && arl !== defaultConfig.cookies.arl && arl !== old_arl;
162
+ }
163
+ }
164
+ exports.default = Config;
@@ -0,0 +1,4 @@
1
+ /// <reference types="node" />
2
+ export { decryptDownload, TrackDecryptStream } from 'gerdur-core';
3
+ /** Decrypt a track that was streamed to a temp file. */
4
+ export declare const decryptDownloadFile: (tmpfile: string, trackId: string) => Buffer;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decryptDownloadFile = exports.TrackDecryptStream = exports.decryptDownload = void 0;
4
+ /**
5
+ * Track decryption. As of gerdur-core@1.0.2 the core `decryptDownload` is a
6
+ * correct, dependency-free Blowfish that works on every Node (it no longer
7
+ * touches OpenSSL's removed `bf-cbc`), at ~290 MiB/s — so there is nothing left
8
+ * to wrap or fall back to here.
9
+ */
10
+ const fs_1 = require("fs");
11
+ const gerdur_core_1 = require("gerdur-core");
12
+ var gerdur_core_2 = require("gerdur-core");
13
+ Object.defineProperty(exports, "decryptDownload", { enumerable: true, get: function () { return gerdur_core_2.decryptDownload; } });
14
+ Object.defineProperty(exports, "TrackDecryptStream", { enumerable: true, get: function () { return gerdur_core_2.TrackDecryptStream; } });
15
+ /** Decrypt a track that was streamed to a temp file. */
16
+ const decryptDownloadFile = (tmpfile, trackId) => (0, gerdur_core_1.decryptDownload)((0, fs_1.readFileSync)(tmpfile), trackId);
17
+ exports.decryptDownloadFile = decryptDownloadFile;