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,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,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;
|