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,24 @@
1
+ import type { trackType } from 'gerdur-core/types';
2
+ interface downloadTrackProps {
3
+ track: trackType;
4
+ quality: string | number;
5
+ info: {
6
+ [key: string]: any;
7
+ };
8
+ coverSizes: {
9
+ '128': number;
10
+ '320': number;
11
+ flac: number;
12
+ };
13
+ path: string;
14
+ totalTracks: number;
15
+ trackNumber?: boolean;
16
+ fallbackTrack?: boolean;
17
+ fallbackQuality?: boolean;
18
+ isFallback?: boolean;
19
+ isQualityFallback?: boolean;
20
+ overwrite?: boolean;
21
+ message?: string;
22
+ }
23
+ declare const downloadTrack: ({ track, quality, info, coverSizes, path, totalTracks, trackNumber, fallbackTrack, fallbackQuality, isFallback, isQualityFallback, overwrite, message, }: downloadTrackProps) => Promise<string | undefined>;
24
+ export default downloadTrack;
@@ -0,0 +1,163 @@
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 got_1 = __importDefault(require("got"));
7
+ const stream_1 = __importDefault(require("stream"));
8
+ const fs_1 = require("fs");
9
+ const util_1 = require("util");
10
+ const path_1 = require("path");
11
+ const gerdur_core_1 = require("gerdur-core");
12
+ const log_update_1 = __importDefault(require("log-update"));
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const signale_1 = __importDefault(require("../lib/signale"));
15
+ const util_2 = require("./util");
16
+ const decrypt_1 = require("./decrypt");
17
+ const pipeline = (0, util_1.promisify)(stream_1.default.pipeline);
18
+ const simulate = process.env.SIMULATE;
19
+ const downloadTrack = async ({ track, quality, info, coverSizes, path, totalTracks, trackNumber = true, fallbackTrack = true, fallbackQuality = true, isFallback = false, isQualityFallback = false, overwrite = false, message = '', }) => {
20
+ (0, log_update_1.default)(signale_1.default.pending(track.SNG_TITLE + ' by ' + track.ART_NAME + ' from ' + track.ALB_TITLE));
21
+ try {
22
+ let ext = '.mp3', fileSize = 0, downloaded = 0, coverSize = 500;
23
+ switch (quality) {
24
+ case 1:
25
+ case '1':
26
+ case '128':
27
+ case 'MP3_128':
28
+ case '128kbps':
29
+ quality = 1;
30
+ fileSize = Number(track.FILESIZE_MP3_128);
31
+ coverSize = coverSizes['128'];
32
+ break;
33
+ case 9:
34
+ case '9':
35
+ case 'flac':
36
+ case 'Flac':
37
+ case 'FLAC':
38
+ quality = 9;
39
+ ext = '.flac';
40
+ fileSize = Number(track.FILESIZE_FLAC);
41
+ coverSize = coverSizes['flac'];
42
+ break;
43
+ default:
44
+ quality = 3;
45
+ fileSize = Number(track.FILESIZE_MP3_320);
46
+ coverSize = coverSizes['320'];
47
+ }
48
+ const savePath = (0, util_2.saveLayout)({ track, album: info, path, trackNumber, minimumIntegerDigits: totalTracks >= 100 ? 3 : 2 }) + ext;
49
+ if ((0, fs_1.existsSync)(savePath) && !overwrite) {
50
+ (0, log_update_1.default)(signale_1.default.info(`Skipped "${track.SNG_TITLE}", track already exists.`));
51
+ log_update_1.default.done();
52
+ (0, log_update_1.default)(signale_1.default.note(savePath));
53
+ log_update_1.default.done();
54
+ return savePath;
55
+ }
56
+ let trackData;
57
+ try {
58
+ trackData = await (0, gerdur_core_1.getTrackDownloadUrl)(track, quality);
59
+ }
60
+ catch (err) {
61
+ if (!(err instanceof gerdur_core_1.GeoBlocked) || !track.FALLBACK) {
62
+ throw err;
63
+ }
64
+ }
65
+ if (!trackData) {
66
+ if (fallbackTrack && track.FALLBACK && !isFallback && track.ART_ID === track.FALLBACK.ART_ID) {
67
+ const { FALLBACK, ...CURRENT_TRACK } = track;
68
+ return await downloadTrack({
69
+ track: { ...CURRENT_TRACK, ...FALLBACK },
70
+ quality,
71
+ info,
72
+ coverSizes,
73
+ path,
74
+ totalTracks,
75
+ trackNumber,
76
+ fallbackTrack: false,
77
+ isFallback: true,
78
+ overwrite,
79
+ message,
80
+ });
81
+ }
82
+ else if (fallbackQuality && quality !== 1) {
83
+ return await downloadTrack({
84
+ track,
85
+ quality: quality === 9 ? 3 : 1,
86
+ info,
87
+ coverSizes,
88
+ path,
89
+ totalTracks,
90
+ trackNumber,
91
+ fallbackTrack,
92
+ isFallback,
93
+ isQualityFallback: true,
94
+ overwrite,
95
+ message,
96
+ });
97
+ }
98
+ (0, log_update_1.default)(signale_1.default.warn(`Skipped "${track.SNG_TITLE}", track not available.`));
99
+ log_update_1.default.done();
100
+ return;
101
+ }
102
+ const headers = {};
103
+ const tmpfile = `gerdur_${quality}_${track.SNG_ID}_${simulate ? 'simulate' : track.MD5_ORIGIN}`;
104
+ if (simulate) {
105
+ coverSize = 56;
106
+ headers.range = 'bytes=0-1023';
107
+ }
108
+ else if ((0, fs_1.existsSync)(tmpfile)) {
109
+ const tmpfilestat = (0, fs_1.statSync)(tmpfile);
110
+ downloaded = tmpfilestat.size;
111
+ headers.range = 'bytes=' + tmpfilestat.size + '-';
112
+ }
113
+ fileSize = trackData.fileSize;
114
+ const bar = (0, util_2.progressBar)(fileSize, 40);
115
+ const humanSizeTotal = (fileSize / 1024 / 1024).toFixed(2);
116
+ let transferredLast = downloaded;
117
+ await pipeline(got_1.default.stream(trackData.trackUrl, { responseType: 'buffer', headers }).on('downloadProgress', ({ transferred }) => {
118
+ // Report download progress
119
+ transferred += downloaded;
120
+ if (transferred - transferredLast > 50000) {
121
+ transferredLast = transferred;
122
+ (0, log_update_1.default)(signale_1.default.info(`Downloading ${track.SNG_TITLE} ${message}\n ${bar(transferred)} | ${humanSizeTotal}MiB`));
123
+ }
124
+ }), (0, fs_1.createWriteStream)(tmpfile, { flags: 'a', autoClose: true }));
125
+ let outFile;
126
+ if (trackData.isEncrypted) {
127
+ (0, log_update_1.default)(signale_1.default.pending('Decrypting ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
128
+ outFile = await (0, decrypt_1.decryptDownloadFile)(tmpfile, track.SNG_ID);
129
+ }
130
+ else {
131
+ outFile = (0, fs_1.readFileSync)(tmpfile);
132
+ }
133
+ (0, log_update_1.default)(signale_1.default.pending('Tagging ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
134
+ const trackWithMetadata = await (0, gerdur_core_1.addTrackTags)(outFile, track, coverSize);
135
+ // Delete temporary file now
136
+ (0, fs_1.unlinkSync)(tmpfile);
137
+ (0, log_update_1.default)(signale_1.default.pending('Saving ' + track.SNG_TITLE + ' by ' + track.ART_NAME));
138
+ if (!simulate) {
139
+ // Create directory if not exists
140
+ const dir = (0, path_1.dirname)(savePath);
141
+ if (!(0, fs_1.existsSync)(dir)) {
142
+ (0, fs_1.mkdirSync)(dir, { recursive: true });
143
+ }
144
+ // Save file to disk
145
+ (0, fs_1.writeFileSync)(savePath, trackWithMetadata);
146
+ }
147
+ // Print sucess info
148
+ (0, log_update_1.default)(signale_1.default.success(`${isFallback ? chalk_1.default.yellow('[Fallback] ') : ''}${track.SNG_TITLE} by ${track.ART_NAME}`));
149
+ log_update_1.default.done();
150
+ if (isQualityFallback) {
151
+ (0, log_update_1.default)(signale_1.default.note(`Used ${quality === 3 ? '320kbps' : '128kbps'} as other formats were unavailable`));
152
+ log_update_1.default.done();
153
+ }
154
+ return savePath;
155
+ }
156
+ catch (err) {
157
+ (0, log_update_1.default)(signale_1.default.error(track.SNG_TITLE));
158
+ log_update_1.default.done();
159
+ (0, log_update_1.default)(signale_1.default.note(err.message));
160
+ log_update_1.default.done();
161
+ }
162
+ };
163
+ exports.default = downloadTrack;
@@ -0,0 +1,16 @@
1
+ /** Discriminated result so the caller can branch on the failure reason. */
2
+ export type LoginResult = {
3
+ ok: true;
4
+ arl: string;
5
+ } | {
6
+ ok: false;
7
+ reason: 'wrong-credentials' | 'no-arl' | 'network' | 'unknown';
8
+ message: string;
9
+ };
10
+ /**
11
+ * Attempt to obtain an arl for the given credentials.
12
+ *
13
+ * The password is MD5-hashed and folded into the app hash before transmission;
14
+ * it is never written anywhere by this function.
15
+ */
16
+ export declare const loginWithEmail: (email: string, password: string) => Promise<LoginResult>;
@@ -0,0 +1,111 @@
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.loginWithEmail = void 0;
7
+ const crypto_1 = require("crypto");
8
+ const got_1 = __importDefault(require("got"));
9
+ /**
10
+ * Email/password -> arl login for Deezer.
11
+ *
12
+ * Uses Deezer's mobile OAuth endpoint (connect.deezer.com/oauth/user_auth.php)
13
+ * rather than the web login form, which is bot-protected. The approach and the
14
+ * public app credentials/hash scheme mirror the well-known deemix flow.
15
+ *
16
+ * This is still best-effort: Deezer may reject the login (bad credentials,
17
+ * account protections, or changes on their side), in which case the caller
18
+ * should fall back to pasting an arl. Passwords handled here are never persisted
19
+ * or logged; only the resulting arl is returned.
20
+ */
21
+ // Public Deezer app credentials used by the mobile OAuth flow (same values used
22
+ // by deemix and similar tools). Not secret in any meaningful sense.
23
+ const CLIENT_ID = '447462';
24
+ const CLIENT_SECRET = 'a83bf7f38ad2f137e444727cfc3775cf';
25
+ const UA = 'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0';
26
+ const md5 = (data) => (0, crypto_1.createHash)('md5').update(Buffer.from(data, 'utf8')).digest('hex');
27
+ /**
28
+ * Merge `set-cookie` response headers into a cookie map (last value wins),
29
+ * emulating a cookie jar shared across the login requests.
30
+ */
31
+ const collectCookies = (jar, setCookie) => {
32
+ if (!setCookie) {
33
+ return;
34
+ }
35
+ for (const raw of setCookie) {
36
+ const [pair] = raw.split(';');
37
+ const eq = pair.indexOf('=');
38
+ if (eq > 0) {
39
+ const name = pair.slice(0, eq).trim();
40
+ const value = pair.slice(eq + 1).trim();
41
+ if (name) {
42
+ jar.set(name, value);
43
+ }
44
+ }
45
+ }
46
+ };
47
+ const cookieHeader = (jar) => [...jar.entries()].map(([k, v]) => `${k}=${v}`).join('; ');
48
+ /**
49
+ * Attempt to obtain an arl for the given credentials.
50
+ *
51
+ * The password is MD5-hashed and folded into the app hash before transmission;
52
+ * it is never written anywhere by this function.
53
+ */
54
+ const loginWithEmail = async (email, password) => {
55
+ const jar = new Map();
56
+ try {
57
+ // 1. Bootstrap a session so Deezer sets an initial sid cookie.
58
+ const boot = await (0, got_1.default)('https://www.deezer.com', {
59
+ method: 'POST',
60
+ headers: { 'User-Agent': UA },
61
+ timeout: { request: 15000 },
62
+ throwHttpErrors: false,
63
+ });
64
+ collectCookies(jar, boot.headers['set-cookie']);
65
+ // 2. Authenticate via the mobile OAuth endpoint. The password is hashed,
66
+ // then combined with the public app id/secret into the request hash.
67
+ const passwordHash = md5(password);
68
+ const hash = md5([CLIENT_ID, email, passwordHash, CLIENT_SECRET].join(''));
69
+ let accessToken = null;
70
+ const auth = await (0, got_1.default)('https://connect.deezer.com/oauth/user_auth.php', {
71
+ searchParams: { app_id: CLIENT_ID, login: email, password: passwordHash, hash },
72
+ headers: { 'User-Agent': UA, cookie: cookieHeader(jar) },
73
+ responseType: 'json',
74
+ timeout: { request: 15000 },
75
+ throwHttpErrors: false,
76
+ });
77
+ collectCookies(jar, auth.headers['set-cookie']);
78
+ const authBody = auth.body ?? {};
79
+ if (authBody.access_token && authBody.access_token !== 'undefined') {
80
+ accessToken = authBody.access_token;
81
+ }
82
+ if (!accessToken && !jar.has('arl') && (authBody.error || auth.statusCode >= 400)) {
83
+ return { ok: false, reason: 'wrong-credentials', message: 'Login failed — check your email and password.' };
84
+ }
85
+ // 3. Read the arl for the now-authenticated session.
86
+ const arlResp = await (0, got_1.default)('https://www.deezer.com/ajax/gw-light.php', {
87
+ searchParams: { method: 'user.getArl', input: 3, api_version: '1.0', api_token: 'null' },
88
+ headers: { 'User-Agent': UA, cookie: cookieHeader(jar) },
89
+ responseType: 'json',
90
+ timeout: { request: 15000 },
91
+ throwHttpErrors: false,
92
+ });
93
+ collectCookies(jar, arlResp.headers['set-cookie']);
94
+ const arl = arlResp.body?.results;
95
+ if (typeof arl === 'string' && arl.length === 192) {
96
+ return { ok: true, arl };
97
+ }
98
+ if (jar.has('arl') && jar.get('arl').length === 192) {
99
+ return { ok: true, arl: jar.get('arl') };
100
+ }
101
+ return {
102
+ ok: false,
103
+ reason: 'no-arl',
104
+ message: 'Logged in but could not retrieve an arl. Please paste your arl instead.',
105
+ };
106
+ }
107
+ catch (err) {
108
+ return { ok: false, reason: 'network', message: err?.message || 'Network error during login.' };
109
+ }
110
+ };
111
+ exports.loginWithEmail = loginWithEmail;
@@ -0,0 +1,84 @@
1
+ /// <reference types="node" />
2
+ import { parseInfo, searchMusic } from 'gerdur-core';
3
+ import type { Quality, DownloadTrackOptions, DownloadResult } from './api-download';
4
+ import type { trackType, userType } from 'gerdur-core/types';
5
+ /** Search result categories accepted by {@link Session.search}. */
6
+ export type SearchType = 'ALBUM' | 'ARTIST' | 'TRACK' | 'PLAYLIST' | 'RADIO' | 'SHOW' | 'USER' | 'LIVESTREAM' | 'CHANNEL';
7
+ export interface SessionOptions {
8
+ /** Deezer arl cookie. Provide this OR email+password. */
9
+ arl?: string;
10
+ /** Deezer account email (used with `password` to fetch an arl). */
11
+ email?: string;
12
+ /** Deezer account password. */
13
+ password?: string;
14
+ /** Default download concurrency for `downloadTracks`. Default 4. */
15
+ concurrency?: number;
16
+ }
17
+ export interface DownloadTracksOptions extends DownloadTrackOptions {
18
+ /** Concurrent downloads. Defaults to the session concurrency. */
19
+ concurrency?: number;
20
+ /**
21
+ * Called after each track settles, with the running progress. `result` is the
22
+ * download result (or `null` if the track was unavailable / errored).
23
+ */
24
+ onProgress?: (progress: {
25
+ index: number;
26
+ total: number;
27
+ track: trackType;
28
+ result: DownloadResult | null;
29
+ }) => void;
30
+ }
31
+ /**
32
+ * An authenticated Deezer session exposing high-level query and download
33
+ * helpers. Create one with {@link createSession}. All methods are silent (no
34
+ * console output); use `onProgress` callbacks for progress.
35
+ */
36
+ export declare class Session {
37
+ /** The arl in use for this session. */
38
+ readonly arl: string;
39
+ private concurrency;
40
+ private constructor();
41
+ /**
42
+ * Log in and return a ready-to-use session. Accepts either an `arl` or
43
+ * `email`+`password` (which are exchanged for an arl via Deezer's OAuth flow).
44
+ */
45
+ static create(options: SessionOptions): Promise<Session>;
46
+ /** Fetch the logged-in user profile. */
47
+ getUser(): Promise<userType>;
48
+ /**
49
+ * Resolve any supported URL (Deezer/Spotify/Tidal track, album, playlist, or
50
+ * artist) into its info + track list.
51
+ */
52
+ parseUrl(url: string): ReturnType<typeof parseInfo>;
53
+ /** Search Deezer. Defaults to track results. */
54
+ search(query: string, types?: SearchType[], limit?: number): ReturnType<typeof searchMusic>;
55
+ /** Return a fully tagged audio Buffer for a track (nothing written to disk). */
56
+ getTrackBuffer(track: trackType, quality?: Quality, options?: {}): Promise<Buffer | null>;
57
+ /** Download a single track to disk. */
58
+ downloadTrack(track: trackType, quality?: Quality, options?: DownloadTrackOptions): Promise<DownloadResult | null>;
59
+ /**
60
+ * Download many tracks with bounded concurrency. Resolves to one result per
61
+ * input track (in the original order); entries are `null` for tracks that
62
+ * were unavailable or errored. Progress is reported via `onProgress`.
63
+ */
64
+ downloadTracks(tracks: trackType[], quality?: Quality, options?: DownloadTracksOptions): Promise<(DownloadResult | null)[]>;
65
+ /**
66
+ * Convenience: resolve a URL and download all of its tracks in one call.
67
+ * Uses the linked album/playlist info for filename templating.
68
+ */
69
+ downloadUrl(url: string, quality?: Quality, options?: DownloadTracksOptions): Promise<(DownloadResult | null)[]>;
70
+ }
71
+ /**
72
+ * Create and log in an authenticated {@link Session}.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * const session = await createSession({arl: '...'}); // or {email, password}
77
+ * const {tracks, linkinfo} = await session.parseUrl(url);
78
+ * await session.downloadTracks(tracks, 'flac', {
79
+ * output: 'Music/{ALB_TITLE}/{SNG_TITLE}',
80
+ * onProgress: ({index, total}) => console.log(`${index + 1}/${total}`),
81
+ * });
82
+ * ```
83
+ */
84
+ export declare const createSession: (options: SessionOptions) => Promise<Session>;
@@ -0,0 +1,118 @@
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.createSession = exports.Session = void 0;
7
+ const p_queue_1 = __importDefault(require("p-queue"));
8
+ const gerdur_core_1 = require("gerdur-core");
9
+ const email_login_1 = require("./email-login");
10
+ const api_download_1 = require("./api-download");
11
+ /**
12
+ * An authenticated Deezer session exposing high-level query and download
13
+ * helpers. Create one with {@link createSession}. All methods are silent (no
14
+ * console output); use `onProgress` callbacks for progress.
15
+ */
16
+ class Session {
17
+ /** The arl in use for this session. */
18
+ arl;
19
+ concurrency;
20
+ constructor(arl, concurrency) {
21
+ this.arl = arl;
22
+ this.concurrency = concurrency;
23
+ }
24
+ /**
25
+ * Log in and return a ready-to-use session. Accepts either an `arl` or
26
+ * `email`+`password` (which are exchanged for an arl via Deezer's OAuth flow).
27
+ */
28
+ static async create(options) {
29
+ let arl = options.arl?.trim();
30
+ if (!arl && options.email && options.password) {
31
+ const result = await (0, email_login_1.loginWithEmail)(options.email, options.password);
32
+ if (!result.ok) {
33
+ throw new Error(`Login failed (${result.reason}): ${result.message}`);
34
+ }
35
+ arl = result.arl;
36
+ }
37
+ if (!arl) {
38
+ throw new Error('createSession requires either an arl or email + password.');
39
+ }
40
+ await (0, gerdur_core_1.initDeezerApi)(arl);
41
+ // Verify the session is actually usable before handing it back.
42
+ await (0, gerdur_core_1.getUser)();
43
+ return new Session(arl, options.concurrency ?? 4);
44
+ }
45
+ /** Fetch the logged-in user profile. */
46
+ getUser() {
47
+ return (0, gerdur_core_1.getUser)();
48
+ }
49
+ /**
50
+ * Resolve any supported URL (Deezer/Spotify/Tidal track, album, playlist, or
51
+ * artist) into its info + track list.
52
+ */
53
+ parseUrl(url) {
54
+ return (0, gerdur_core_1.parseInfo)(url);
55
+ }
56
+ /** Search Deezer. Defaults to track results. */
57
+ search(query, types = ['TRACK'], limit) {
58
+ return (0, gerdur_core_1.searchMusic)(query, types, limit);
59
+ }
60
+ /** Return a fully tagged audio Buffer for a track (nothing written to disk). */
61
+ getTrackBuffer(track, quality = '320', options = {}) {
62
+ return (0, api_download_1.getTrackBuffer)(track, quality, options);
63
+ }
64
+ /** Download a single track to disk. */
65
+ downloadTrack(track, quality = '320', options = {}) {
66
+ return (0, api_download_1.downloadTrackToFile)(track, quality, options);
67
+ }
68
+ /**
69
+ * Download many tracks with bounded concurrency. Resolves to one result per
70
+ * input track (in the original order); entries are `null` for tracks that
71
+ * were unavailable or errored. Progress is reported via `onProgress`.
72
+ */
73
+ async downloadTracks(tracks, quality = '320', options = {}) {
74
+ const { concurrency = this.concurrency, onProgress, ...trackOptions } = options;
75
+ const queue = new p_queue_1.default({ concurrency });
76
+ const total = tracks.length;
77
+ const results = new Array(total).fill(null);
78
+ await queue.addAll(tracks.map((track, index) => async () => {
79
+ let result = null;
80
+ try {
81
+ result = await (0, api_download_1.downloadTrackToFile)(track, quality, trackOptions);
82
+ }
83
+ catch {
84
+ result = null;
85
+ }
86
+ results[index] = result;
87
+ if (onProgress) {
88
+ onProgress({ index, total, track, result });
89
+ }
90
+ }));
91
+ return results;
92
+ }
93
+ /**
94
+ * Convenience: resolve a URL and download all of its tracks in one call.
95
+ * Uses the linked album/playlist info for filename templating.
96
+ */
97
+ async downloadUrl(url, quality = '320', options = {}) {
98
+ const data = await (0, gerdur_core_1.parseInfo)(url);
99
+ const albumInfo = data.linkinfo ?? {};
100
+ return this.downloadTracks(data.tracks, quality, { albumInfo, ...options });
101
+ }
102
+ }
103
+ exports.Session = Session;
104
+ /**
105
+ * Create and log in an authenticated {@link Session}.
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * const session = await createSession({arl: '...'}); // or {email, password}
110
+ * const {tracks, linkinfo} = await session.parseUrl(url);
111
+ * await session.downloadTracks(tracks, 'flac', {
112
+ * output: 'Music/{ALB_TITLE}/{SNG_TITLE}',
113
+ * onProgress: ({index, total}) => console.log(`${index + 1}/${total}`),
114
+ * });
115
+ * ```
116
+ */
117
+ const createSession = (options) => Session.create(options);
118
+ exports.createSession = createSession;
@@ -0,0 +1,15 @@
1
+ export declare const info: (message: string) => string;
2
+ export declare const warn: (message: string) => string;
3
+ export declare const pending: (message: string) => string;
4
+ export declare const success: (message: string) => string;
5
+ export declare const error: (message: string) => string;
6
+ export declare const note: (message: string) => string;
7
+ declare const _default: {
8
+ info: (message: string) => string;
9
+ warn: (message: string) => string;
10
+ pending: (message: string) => string;
11
+ success: (message: string) => string;
12
+ error: (message: string) => string;
13
+ note: (message: string) => string;
14
+ };
15
+ export default _default;
@@ -0,0 +1,20 @@
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.note = exports.error = exports.success = exports.pending = exports.warn = exports.info = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const info = (message) => chalk_1.default.blueBright('ℹ info ') + message;
9
+ exports.info = info;
10
+ const warn = (message) => chalk_1.default.yellowBright('⚠ warn ') + message;
11
+ exports.warn = warn;
12
+ const pending = (message) => chalk_1.default.blueBright('● pending ') + message;
13
+ exports.pending = pending;
14
+ const success = (message) => chalk_1.default.greenBright('✔ success ') + message;
15
+ exports.success = success;
16
+ const error = (message) => chalk_1.default.redBright('✖ error ') + message;
17
+ exports.error = error;
18
+ const note = (message) => chalk_1.default.gray(' → ' + message);
19
+ exports.note = note;
20
+ exports.default = { info: exports.info, warn: exports.warn, pending: exports.pending, success: exports.success, error: exports.error, note: exports.note };
@@ -0,0 +1,2 @@
1
+ declare const updateCheck: (pkg: any) => Promise<import("update-check").Result | null | undefined>;
2
+ export default updateCheck;
@@ -0,0 +1,30 @@
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 chalk_1 = __importDefault(require("chalk"));
7
+ const update_check_1 = __importDefault(require("update-check"));
8
+ const gradient_string_1 = __importDefault(require("gradient-string"));
9
+ const updateCheck = async (pkg) => {
10
+ try {
11
+ const distTag = pkg.version.includes('beta') ? 'beta' : 'latest';
12
+ const update = await (0, update_check_1.default)(pkg, { distTag });
13
+ if (update) {
14
+ process.on('exit', () => {
15
+ console.log((0, gradient_string_1.default)('#f50', 'orange', 'red')(' ──────────────────────────────────────────────') +
16
+ '\n' +
17
+ (0, gradient_string_1.default)('#f50', 'orange', 'red')(` Update available ${pkg.version} \u2192 ${update.latest}`) +
18
+ '\n' +
19
+ chalk_1.default.redBright(' Run ') +
20
+ chalk_1.default.cyanBright(`${process.pkg ? 'gerdur -U' : 'npm i -g gerdur@' + distTag}`) +
21
+ chalk_1.default.redBright(' to update') +
22
+ '\n' +
23
+ (0, gradient_string_1.default)('#f50', 'orange', 'red')(' ──────────────────────────────────────────────'));
24
+ });
25
+ }
26
+ return update;
27
+ }
28
+ catch (err) { }
29
+ };
30
+ exports.default = updateCheck;
@@ -0,0 +1,17 @@
1
+ type saveLayoutProps = {
2
+ track: {
3
+ [key: string]: any;
4
+ };
5
+ album: {
6
+ [key: string]: any;
7
+ };
8
+ path: string;
9
+ minimumIntegerDigits: number;
10
+ trackNumber: boolean;
11
+ };
12
+ export declare const sanitizeFilename: (input: string, replacement?: string) => string;
13
+ export declare const formatSecondsReadable: (time: number) => string;
14
+ export declare const saveLayout: ({ track, album, path: filePath, minimumIntegerDigits, trackNumber }: saveLayoutProps) => string;
15
+ export declare const progressBar: (total: number, width: number) => (value: number) => string;
16
+ export declare const commonPath: (paths: string[]) => string;
17
+ export {};