q-fi-core 2.3.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.
- package/CHANGELOG.md +109 -0
- package/LICENSE +1 -0
- package/README.md +186 -0
- package/dist/api/api.d.ts +78 -0
- package/dist/api/api.js +182 -0
- package/dist/api/cache.d.ts +3 -0
- package/dist/api/cache.js +12 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +18 -0
- package/dist/api/request.d.ts +23 -0
- package/dist/api/request.js +88 -0
- package/dist/converter/deezer.d.ts +3 -0
- package/dist/converter/deezer.js +45 -0
- package/dist/converter/index.d.ts +5 -0
- package/dist/converter/index.js +34 -0
- package/dist/converter/parse.d.ts +18 -0
- package/dist/converter/parse.js +176 -0
- package/dist/converter/spotify.d.ts +40 -0
- package/dist/converter/spotify.js +154 -0
- package/dist/converter/tidal.d.ts +146 -0
- package/dist/converter/tidal.js +199 -0
- package/dist/converter/youtube.d.ts +6 -0
- package/dist/converter/youtube.js +102 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +24 -0
- package/dist/lib/decrypt.d.ts +9 -0
- package/dist/lib/decrypt.js +63 -0
- package/dist/lib/fast-lru.d.ts +63 -0
- package/dist/lib/fast-lru.js +121 -0
- package/dist/lib/get-url.d.ts +16 -0
- package/dist/lib/get-url.js +154 -0
- package/dist/lib/http.d.ts +66 -0
- package/dist/lib/http.js +235 -0
- package/dist/lib/metaflac-js.d.ts +124 -0
- package/dist/lib/metaflac-js.js +343 -0
- package/dist/lib/request.d.ts +18 -0
- package/dist/lib/request.js +86 -0
- package/dist/metadata-writer/abumCover.d.ts +10 -0
- package/dist/metadata-writer/abumCover.js +37 -0
- package/dist/metadata-writer/flacmetata.d.ts +3 -0
- package/dist/metadata-writer/flacmetata.js +80 -0
- package/dist/metadata-writer/getTrackLyrics.d.ts +2 -0
- package/dist/metadata-writer/getTrackLyrics.js +26 -0
- package/dist/metadata-writer/id3.d.ts +3 -0
- package/dist/metadata-writer/id3.js +119 -0
- package/dist/metadata-writer/index.d.ts +9 -0
- package/dist/metadata-writer/index.js +44 -0
- package/dist/metadata-writer/musixmatchLyrics.d.ts +1 -0
- package/dist/metadata-writer/musixmatchLyrics.js +39 -0
- package/dist/metadata-writer/useragents.d.ts +1 -0
- package/dist/metadata-writer/useragents.js +86 -0
- package/dist/types/album.d.ts +123 -0
- package/dist/types/album.js +2 -0
- package/dist/types/artist.d.ts +36 -0
- package/dist/types/artist.js +2 -0
- package/dist/types/channel.d.ts +33 -0
- package/dist/types/channel.js +2 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +26 -0
- package/dist/types/playlist-channel.d.ts +81 -0
- package/dist/types/playlist-channel.js +2 -0
- package/dist/types/playlist.d.ts +49 -0
- package/dist/types/playlist.js +2 -0
- package/dist/types/profile.d.ts +29 -0
- package/dist/types/profile.js +2 -0
- package/dist/types/radio.d.ts +7 -0
- package/dist/types/radio.js +2 -0
- package/dist/types/search.d.ts +79 -0
- package/dist/types/search.js +2 -0
- package/dist/types/show.d.ts +61 -0
- package/dist/types/show.js +2 -0
- package/dist/types/tracks.d.ts +144 -0
- package/dist/types/tracks.js +2 -0
- package/dist/types/user.d.ts +16 -0
- package/dist/types/user.js +2 -0
- package/package.json +69 -0
- package/types/index.d.ts +1 -0
- package/types/index.js +3 -0
- package/types/package.json +4 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { playlistInfo, trackType } from '../types';
|
|
2
|
+
interface commonType {
|
|
3
|
+
id: number;
|
|
4
|
+
title: string;
|
|
5
|
+
duration: number;
|
|
6
|
+
premiumStreamingOnly: boolean;
|
|
7
|
+
trackNumber: number;
|
|
8
|
+
copyright: string;
|
|
9
|
+
url: string;
|
|
10
|
+
explicit: boolean;
|
|
11
|
+
audioQuality: string;
|
|
12
|
+
artist: {
|
|
13
|
+
id: number;
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
};
|
|
17
|
+
album: {
|
|
18
|
+
id: number;
|
|
19
|
+
title: string;
|
|
20
|
+
cover: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
interface tidalTrackType extends commonType {
|
|
24
|
+
isrc: string;
|
|
25
|
+
editable: boolean;
|
|
26
|
+
audioQuality: string;
|
|
27
|
+
album: {
|
|
28
|
+
id: number;
|
|
29
|
+
title: string;
|
|
30
|
+
cover: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
interface tidalAlbumType extends commonType {
|
|
34
|
+
cover: string;
|
|
35
|
+
videoCover: null | string;
|
|
36
|
+
upc: string;
|
|
37
|
+
audioQuality: string;
|
|
38
|
+
}
|
|
39
|
+
interface tidalPlaylistType {
|
|
40
|
+
uuid: string;
|
|
41
|
+
title: string;
|
|
42
|
+
numberOfTracks: number;
|
|
43
|
+
numberOfVideos: number;
|
|
44
|
+
creator: {
|
|
45
|
+
id: number;
|
|
46
|
+
};
|
|
47
|
+
description: string;
|
|
48
|
+
duration: number;
|
|
49
|
+
lastUpdated: string;
|
|
50
|
+
created: string;
|
|
51
|
+
type: string;
|
|
52
|
+
publicPlaylist: boolean;
|
|
53
|
+
url: string;
|
|
54
|
+
image: string;
|
|
55
|
+
}
|
|
56
|
+
interface listType {
|
|
57
|
+
limit: number;
|
|
58
|
+
offset: number;
|
|
59
|
+
totalNumberOfItems: number;
|
|
60
|
+
}
|
|
61
|
+
interface tidalArtistTopTracksType extends listType {
|
|
62
|
+
items: tidalTrackType[];
|
|
63
|
+
}
|
|
64
|
+
interface tidalAlbumsTracksType extends listType {
|
|
65
|
+
items: tidalAlbumType[];
|
|
66
|
+
}
|
|
67
|
+
interface tidalPlaylistTracksType extends listType {
|
|
68
|
+
items: tidalTrackType[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get a track by its id
|
|
72
|
+
* @param string} id - track id
|
|
73
|
+
* @example tidal.getTrack('64975224')
|
|
74
|
+
*/
|
|
75
|
+
export declare const getTrack: (id: string) => Promise<tidalTrackType>;
|
|
76
|
+
/**
|
|
77
|
+
* Convert a tidal track to deezer
|
|
78
|
+
* @param {string} id - track id
|
|
79
|
+
*/
|
|
80
|
+
export declare const track2deezer: (id: string) => Promise<trackType>;
|
|
81
|
+
/**
|
|
82
|
+
* Get an album by its id
|
|
83
|
+
* @param {string} id - album id
|
|
84
|
+
* @example tidal.getAlbum('80216363')
|
|
85
|
+
*/
|
|
86
|
+
export declare const getAlbum: (id: string) => Promise<tidalAlbumType>;
|
|
87
|
+
/**
|
|
88
|
+
* Convert a tidal albums to deezer
|
|
89
|
+
* @param {string} id - album id
|
|
90
|
+
*/
|
|
91
|
+
export declare const album2deezer: (id: string) => Promise<[import("../types").albumType, trackType[]]>;
|
|
92
|
+
/**
|
|
93
|
+
* Get album tracks by album id
|
|
94
|
+
* @param {string} id - album id
|
|
95
|
+
* @example tidal.getAlbumTracks('80216363')
|
|
96
|
+
*/
|
|
97
|
+
export declare const getAlbumTracks: (id: string) => Promise<tidalAlbumsTracksType>;
|
|
98
|
+
/**
|
|
99
|
+
* Get artist albums by artist id
|
|
100
|
+
* @param {string} id - artist id
|
|
101
|
+
* @example tidal.getArtistAlbums('3575680')
|
|
102
|
+
*/
|
|
103
|
+
export declare const getArtistAlbums: (id: string) => Promise<tidalAlbumsTracksType>;
|
|
104
|
+
/**
|
|
105
|
+
* Get top tracks by artist
|
|
106
|
+
* @param {string} id - artist id
|
|
107
|
+
* @example tidal.getArtistTopTracks('3575680')
|
|
108
|
+
*/
|
|
109
|
+
export declare const getArtistTopTracks: (id: string) => Promise<tidalArtistTopTracksType>;
|
|
110
|
+
/**
|
|
111
|
+
* Get a playlist by its uuid
|
|
112
|
+
* @param {string} uuid - playlist uuid
|
|
113
|
+
* @example tidal.getPlaylist('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
114
|
+
*/
|
|
115
|
+
export declare const getPlaylist: (uuid: string) => Promise<tidalPlaylistType>;
|
|
116
|
+
/**
|
|
117
|
+
* Get playlist tracks by playlist uuid
|
|
118
|
+
* @param {string} uuid - playlist uuid
|
|
119
|
+
* @example tidal.getPlaylistTracks('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
120
|
+
*/
|
|
121
|
+
export declare const getPlaylistTracks: (uuid: string) => Promise<tidalPlaylistTracksType>;
|
|
122
|
+
/**
|
|
123
|
+
* Get valid urls to album art
|
|
124
|
+
* @param {string} uuid - album art uuid (can be found as cover property in album object)
|
|
125
|
+
* @example tidal.albumArtToUrl('9a56f482-e9cf-46c3-bb21-82710e7854d4')
|
|
126
|
+
* @returns {Object}
|
|
127
|
+
*/
|
|
128
|
+
export declare const albumArtToUrl: (uuid: string) => {
|
|
129
|
+
sm: string;
|
|
130
|
+
md: string;
|
|
131
|
+
lg: string;
|
|
132
|
+
xl: string;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Find tidal artists tracks on deezer
|
|
136
|
+
* @param {string} id - artist id
|
|
137
|
+
* @example tidal.artist2Deezer('3575680')
|
|
138
|
+
*/
|
|
139
|
+
export declare const artist2Deezer: (id: string, onError?: ((item: tidalTrackType, index: number, err: Error) => void) | undefined) => Promise<trackType[]>;
|
|
140
|
+
/**
|
|
141
|
+
* Find same set of playlist tracks on deezer
|
|
142
|
+
* @param {string} uuid - playlist uuid
|
|
143
|
+
* @example tidal.playlist2Deezer('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
144
|
+
*/
|
|
145
|
+
export declare const playlist2Deezer: (uuid: string, onError?: ((item: tidalTrackType, index: number, err: Error) => void) | undefined) => Promise<[playlistInfo, trackType[]]>;
|
|
146
|
+
export {};
|
|
@@ -0,0 +1,199 @@
|
|
|
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.playlist2Deezer = exports.artist2Deezer = exports.albumArtToUrl = exports.getPlaylistTracks = exports.getPlaylist = exports.getArtistTopTracks = exports.getArtistAlbums = exports.getAlbumTracks = exports.album2deezer = exports.getAlbum = exports.track2deezer = exports.getTrack = void 0;
|
|
7
|
+
const p_queue_1 = __importDefault(require("p-queue"));
|
|
8
|
+
const deezer_1 = require("./deezer");
|
|
9
|
+
const http_1 = require("../lib/http");
|
|
10
|
+
const client = new http_1.HttpClient({
|
|
11
|
+
baseURL: 'https://api.tidal.com/v1/',
|
|
12
|
+
timeout: 15000,
|
|
13
|
+
headers: {
|
|
14
|
+
'user-agent': 'TIDAL/3704 CFNetwork/1220.1 Darwin/20.3.0',
|
|
15
|
+
'x-tidal-token': 'i4ZDjcyhed7Mu47q',
|
|
16
|
+
},
|
|
17
|
+
params: { limit: 500, countryCode: 'US' },
|
|
18
|
+
});
|
|
19
|
+
const queue = new p_queue_1.default({ concurrency: 25 });
|
|
20
|
+
/**
|
|
21
|
+
* Get a track by its id
|
|
22
|
+
* @param string} id - track id
|
|
23
|
+
* @example tidal.getTrack('64975224')
|
|
24
|
+
*/
|
|
25
|
+
const getTrack = async (id) => {
|
|
26
|
+
const { data } = await client.get(`tracks/${id}`);
|
|
27
|
+
return data;
|
|
28
|
+
};
|
|
29
|
+
exports.getTrack = getTrack;
|
|
30
|
+
/**
|
|
31
|
+
* Convert a tidal track to deezer
|
|
32
|
+
* @param {string} id - track id
|
|
33
|
+
*/
|
|
34
|
+
const track2deezer = async (id) => {
|
|
35
|
+
const track = await (0, exports.getTrack)(id);
|
|
36
|
+
return await (0, deezer_1.isrc2deezer)(track.title, track.isrc);
|
|
37
|
+
};
|
|
38
|
+
exports.track2deezer = track2deezer;
|
|
39
|
+
/**
|
|
40
|
+
* Get an album by its id
|
|
41
|
+
* @param {string} id - album id
|
|
42
|
+
* @example tidal.getAlbum('80216363')
|
|
43
|
+
*/
|
|
44
|
+
const getAlbum = async (id) => {
|
|
45
|
+
const { data } = await client.get(`albums/${id}`);
|
|
46
|
+
return data;
|
|
47
|
+
};
|
|
48
|
+
exports.getAlbum = getAlbum;
|
|
49
|
+
/**
|
|
50
|
+
* Convert a tidal albums to deezer
|
|
51
|
+
* @param {string} id - album id
|
|
52
|
+
*/
|
|
53
|
+
const album2deezer = async (id) => {
|
|
54
|
+
const album = await (0, exports.getAlbum)(id);
|
|
55
|
+
return await (0, deezer_1.upc2deezer)(album.title, album.upc);
|
|
56
|
+
};
|
|
57
|
+
exports.album2deezer = album2deezer;
|
|
58
|
+
/**
|
|
59
|
+
* Get album tracks by album id
|
|
60
|
+
* @param {string} id - album id
|
|
61
|
+
* @example tidal.getAlbumTracks('80216363')
|
|
62
|
+
*/
|
|
63
|
+
const getAlbumTracks = async (id) => {
|
|
64
|
+
const { data } = await client.get(`albums/${id}/tracks`);
|
|
65
|
+
return data;
|
|
66
|
+
};
|
|
67
|
+
exports.getAlbumTracks = getAlbumTracks;
|
|
68
|
+
/**
|
|
69
|
+
* Get artist albums by artist id
|
|
70
|
+
* @param {string} id - artist id
|
|
71
|
+
* @example tidal.getArtistAlbums('3575680')
|
|
72
|
+
*/
|
|
73
|
+
const getArtistAlbums = async (id) => {
|
|
74
|
+
const { data } = await client.get(`artists/${id}/albums`);
|
|
75
|
+
data.items = data.items.filter((item) => item.artist.id.toString() === id);
|
|
76
|
+
return data;
|
|
77
|
+
};
|
|
78
|
+
exports.getArtistAlbums = getArtistAlbums;
|
|
79
|
+
/**
|
|
80
|
+
* Get top tracks by artist
|
|
81
|
+
* @param {string} id - artist id
|
|
82
|
+
* @example tidal.getArtistTopTracks('3575680')
|
|
83
|
+
*/
|
|
84
|
+
const getArtistTopTracks = async (id) => {
|
|
85
|
+
const { data } = await client.get(`artists/${id}/toptracks`);
|
|
86
|
+
data.items = data.items.filter((item) => item.artist.id.toString() === id);
|
|
87
|
+
return data;
|
|
88
|
+
};
|
|
89
|
+
exports.getArtistTopTracks = getArtistTopTracks;
|
|
90
|
+
/**
|
|
91
|
+
* Get a playlist by its uuid
|
|
92
|
+
* @param {string} uuid - playlist uuid
|
|
93
|
+
* @example tidal.getPlaylist('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
94
|
+
*/
|
|
95
|
+
const getPlaylist = async (uuid) => {
|
|
96
|
+
const { data } = await client.get(`playlists/${uuid}`);
|
|
97
|
+
return data;
|
|
98
|
+
};
|
|
99
|
+
exports.getPlaylist = getPlaylist;
|
|
100
|
+
/**
|
|
101
|
+
* Get playlist tracks by playlist uuid
|
|
102
|
+
* @param {string} uuid - playlist uuid
|
|
103
|
+
* @example tidal.getPlaylistTracks('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
104
|
+
*/
|
|
105
|
+
const getPlaylistTracks = async (uuid) => {
|
|
106
|
+
const { data } = await client.get(`playlists/${uuid}/tracks`);
|
|
107
|
+
return data;
|
|
108
|
+
};
|
|
109
|
+
exports.getPlaylistTracks = getPlaylistTracks;
|
|
110
|
+
/**
|
|
111
|
+
* Get valid urls to album art
|
|
112
|
+
* @param {string} uuid - album art uuid (can be found as cover property in album object)
|
|
113
|
+
* @example tidal.albumArtToUrl('9a56f482-e9cf-46c3-bb21-82710e7854d4')
|
|
114
|
+
* @returns {Object}
|
|
115
|
+
*/
|
|
116
|
+
const albumArtToUrl = (uuid) => {
|
|
117
|
+
const baseUrl = `https://resources.tidal.com/images/${uuid.replace(/-/g, '/')}`;
|
|
118
|
+
return {
|
|
119
|
+
sm: `${baseUrl}/160x160.jpg`,
|
|
120
|
+
md: `${baseUrl}/320x320.jpg`,
|
|
121
|
+
lg: `${baseUrl}/640x640.jpg`,
|
|
122
|
+
xl: `${baseUrl}/1280x1280.jpg`,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
exports.albumArtToUrl = albumArtToUrl;
|
|
126
|
+
/**
|
|
127
|
+
* Find tidal artists tracks on deezer
|
|
128
|
+
* @param {string} id - artist id
|
|
129
|
+
* @example tidal.artist2Deezer('3575680')
|
|
130
|
+
*/
|
|
131
|
+
const artist2Deezer = async (id, onError) => {
|
|
132
|
+
const { items } = await (0, exports.getArtistTopTracks)(id);
|
|
133
|
+
const tracks = [];
|
|
134
|
+
await queue.addAll(items.map((item, index) => {
|
|
135
|
+
return async () => {
|
|
136
|
+
try {
|
|
137
|
+
const track = await (0, deezer_1.isrc2deezer)(item.title, item.isrc);
|
|
138
|
+
// console.log(signale.success(`Track #${index}: ${item.name}`));
|
|
139
|
+
tracks.push(track);
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
if (onError) {
|
|
143
|
+
onError(item, index, err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}));
|
|
148
|
+
return tracks;
|
|
149
|
+
};
|
|
150
|
+
exports.artist2Deezer = artist2Deezer;
|
|
151
|
+
/**
|
|
152
|
+
* Find same set of playlist tracks on deezer
|
|
153
|
+
* @param {string} uuid - playlist uuid
|
|
154
|
+
* @example tidal.playlist2Deezer('1c5d01ed-4f05-40c4-bd28-0f73099e9648')
|
|
155
|
+
*/
|
|
156
|
+
const playlist2Deezer = async (uuid, onError) => {
|
|
157
|
+
const body = await (0, exports.getPlaylist)(uuid);
|
|
158
|
+
const { items } = await (0, exports.getPlaylistTracks)(uuid);
|
|
159
|
+
const tracks = [];
|
|
160
|
+
await queue.addAll(items.map((item, index) => {
|
|
161
|
+
return async () => {
|
|
162
|
+
try {
|
|
163
|
+
const track = await (0, deezer_1.isrc2deezer)(item.title, item.isrc);
|
|
164
|
+
// console.log(signale.success(`Track #${index}: ${item.track.name}`));
|
|
165
|
+
track.TRACK_POSITION = index + 1;
|
|
166
|
+
tracks.push(track);
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
if (onError) {
|
|
170
|
+
onError(item, index, err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}));
|
|
175
|
+
const userId = body.creator.id.toString();
|
|
176
|
+
const playlistInfoData = {
|
|
177
|
+
PLAYLIST_ID: body.uuid,
|
|
178
|
+
PARENT_USERNAME: userId,
|
|
179
|
+
PARENT_USER_ID: userId,
|
|
180
|
+
PICTURE_TYPE: 'cover',
|
|
181
|
+
PLAYLIST_PICTURE: body.image,
|
|
182
|
+
TITLE: body.title,
|
|
183
|
+
TYPE: '0',
|
|
184
|
+
STATUS: '0',
|
|
185
|
+
USER_ID: userId,
|
|
186
|
+
DATE_ADD: body.created,
|
|
187
|
+
DATE_MOD: body.lastUpdated,
|
|
188
|
+
DATE_CREATE: body.created,
|
|
189
|
+
NB_SONG: body.numberOfTracks,
|
|
190
|
+
NB_FAN: 0,
|
|
191
|
+
CHECKSUM: body.created,
|
|
192
|
+
HAS_ARTIST_LINKED: false,
|
|
193
|
+
IS_SPONSORED: false,
|
|
194
|
+
IS_EDITO: false,
|
|
195
|
+
__TYPE__: 'playlist',
|
|
196
|
+
};
|
|
197
|
+
return [playlistInfoData, tracks];
|
|
198
|
+
};
|
|
199
|
+
exports.playlist2Deezer = playlist2Deezer;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.track2deezer = void 0;
|
|
4
|
+
const node_html_parser_1 = require("node-html-parser");
|
|
5
|
+
const api_1 = require("../api");
|
|
6
|
+
const http_1 = require("../lib/http");
|
|
7
|
+
const parseInlineObject = (document, variableName) => {
|
|
8
|
+
const prefix = `${variableName} = `;
|
|
9
|
+
const start = document.indexOf(prefix);
|
|
10
|
+
if (start === -1) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const jsonStart = start + prefix.length;
|
|
14
|
+
let depth = 0;
|
|
15
|
+
let isInString = false;
|
|
16
|
+
let isEscaped = false;
|
|
17
|
+
for (let index = jsonStart; index < document.length; index++) {
|
|
18
|
+
const char = document[index];
|
|
19
|
+
if (isInString) {
|
|
20
|
+
if (isEscaped) {
|
|
21
|
+
isEscaped = false;
|
|
22
|
+
}
|
|
23
|
+
else if (char === '\\') {
|
|
24
|
+
isEscaped = true;
|
|
25
|
+
}
|
|
26
|
+
else if (char === '"') {
|
|
27
|
+
isInString = false;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === '"') {
|
|
32
|
+
isInString = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === '{') {
|
|
36
|
+
depth += 1;
|
|
37
|
+
}
|
|
38
|
+
else if (char === '}') {
|
|
39
|
+
depth -= 1;
|
|
40
|
+
if (depth === 0) {
|
|
41
|
+
return JSON.parse(document.slice(jsonStart, index + 1));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
const searchTrackByTitle = async (title) => {
|
|
48
|
+
const normalizedTitle = title
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/\(off.*\)/i, '')
|
|
51
|
+
.replace(/official video/gi, '')
|
|
52
|
+
.replace(/lyrics?/gi, '')
|
|
53
|
+
.replace(/ft.*/i, '')
|
|
54
|
+
.replace(/[,-.]/g, ' ')
|
|
55
|
+
.replace(/ +/g, ' ')
|
|
56
|
+
.trim();
|
|
57
|
+
if (!normalizedTitle) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const { TRACK } = await (0, api_1.searchMusic)(normalizedTitle, ['TRACK'], 20);
|
|
61
|
+
const filteredTracks = TRACK.data.filter((track) => normalizedTitle.includes(track.ART_NAME.toLowerCase()));
|
|
62
|
+
return filteredTracks[0] || TRACK.data[0];
|
|
63
|
+
};
|
|
64
|
+
const getTrack = async (id) => {
|
|
65
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
66
|
+
const response = await (0, http_1.getText)(`https://www.youtube.com/watch?v=${id}&hl=en`);
|
|
67
|
+
const scripts = (0, node_html_parser_1.parse)(response)
|
|
68
|
+
.querySelectorAll('script')
|
|
69
|
+
.map((script) => script.text)
|
|
70
|
+
.join('\n');
|
|
71
|
+
const playerResponse = parseInlineObject(scripts, 'var ytInitialPlayerResponse');
|
|
72
|
+
const initialData = parseInlineObject(scripts, 'var ytInitialData');
|
|
73
|
+
const rows = (_j = (_h = (_g = (_f = (_e = (_d = (_c = (_b = (_a = initialData === null || initialData === void 0 ? void 0 : initialData.contents) === null || _a === void 0 ? void 0 : _a.twoColumnWatchNextResults) === null || _b === void 0 ? void 0 : _b.results) === null || _c === void 0 ? void 0 : _c.results) === null || _d === void 0 ? void 0 : _d.contents) === null || _e === void 0 ? void 0 : _e.find((content) => Boolean(content.videoSecondaryInfoRenderer))) === null || _f === void 0 ? void 0 : _f.videoSecondaryInfoRenderer) === null || _g === void 0 ? void 0 : _g.metadataRowContainer) === null || _h === void 0 ? void 0 : _h.metadataRowContainerRenderer) === null || _j === void 0 ? void 0 : _j.rows;
|
|
74
|
+
if (rows && rows.length > 0) {
|
|
75
|
+
const song = rows.find((row) => { var _a, _b; return ((_b = (_a = row.metadataRowRenderer) === null || _a === void 0 ? void 0 : _a.title) === null || _b === void 0 ? void 0 : _b.simpleText) === 'Song'; });
|
|
76
|
+
const artist = rows.find((row) => { var _a, _b; return ((_b = (_a = row.metadataRowRenderer) === null || _a === void 0 ? void 0 : _a.title) === null || _b === void 0 ? void 0 : _b.simpleText) === 'Artist'; });
|
|
77
|
+
const songName = (_m = (_l = (_k = song === null || song === void 0 ? void 0 : song.metadataRowRenderer) === null || _k === void 0 ? void 0 : _k.contents) === null || _l === void 0 ? void 0 : _l[0]) === null || _m === void 0 ? void 0 : _m.simpleText;
|
|
78
|
+
const artistName = (_s = (_r = (_q = (_p = (_o = artist === null || artist === void 0 ? void 0 : artist.metadataRowRenderer) === null || _o === void 0 ? void 0 : _o.contents) === null || _p === void 0 ? void 0 : _p[0]) === null || _q === void 0 ? void 0 : _q.runs) === null || _r === void 0 ? void 0 : _r[0]) === null || _s === void 0 ? void 0 : _s.text;
|
|
79
|
+
if (songName && artistName) {
|
|
80
|
+
const { TRACK } = await (0, api_1.searchAlternative)(artistName, songName, 1);
|
|
81
|
+
if (TRACK.data[0]) {
|
|
82
|
+
return TRACK.data[0];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if ((_t = playerResponse === null || playerResponse === void 0 ? void 0 : playerResponse.videoDetails) === null || _t === void 0 ? void 0 : _t.title) {
|
|
87
|
+
return await searchTrackByTitle(playerResponse.videoDetails.title);
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Convert a youtube video to track by video id
|
|
93
|
+
* @param {String} id - video id
|
|
94
|
+
*/
|
|
95
|
+
const track2deezer = async (id) => {
|
|
96
|
+
const track = await getTrack(id);
|
|
97
|
+
if (track) {
|
|
98
|
+
return track;
|
|
99
|
+
}
|
|
100
|
+
throw new Error('No track found for youtube video ' + id);
|
|
101
|
+
};
|
|
102
|
+
exports.track2deezer = track2deezer;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.initDeezerApi = void 0;
|
|
18
|
+
var request_1 = require("./lib/request");
|
|
19
|
+
Object.defineProperty(exports, "initDeezerApi", { enumerable: true, get: function () { return request_1.initDeezerApi; } });
|
|
20
|
+
__exportStar(require("./api"), exports);
|
|
21
|
+
__exportStar(require("./converter"), exports);
|
|
22
|
+
__exportStar(require("./lib/decrypt"), exports);
|
|
23
|
+
__exportStar(require("./lib/get-url"), exports);
|
|
24
|
+
__exportStar(require("./metadata-writer"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { trackType } from '../types';
|
|
3
|
+
export declare const getSongFileName: ({ MD5_ORIGIN, SNG_ID, MEDIA_VERSION }: trackType, quality: number) => string;
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param source Downloaded song from `getTrackDownloadUrl`
|
|
7
|
+
* @param trackId Song ID as string
|
|
8
|
+
*/
|
|
9
|
+
export declare const decryptDownload: (source: Buffer, trackId: string) => Buffer;
|
|
@@ -0,0 +1,63 @@
|
|
|
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.decryptDownload = exports.getSongFileName = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const md5 = (data, type = 'ascii') => {
|
|
9
|
+
const md5sum = crypto_1.default.createHash('md5');
|
|
10
|
+
md5sum.update(data.toString(), type);
|
|
11
|
+
return md5sum.digest('hex');
|
|
12
|
+
};
|
|
13
|
+
const getSongFileName = ({ MD5_ORIGIN, SNG_ID, MEDIA_VERSION }, quality) => {
|
|
14
|
+
if (!MD5_ORIGIN) {
|
|
15
|
+
throw new Error(`Missing MD5_ORIGIN for track ${SNG_ID}`);
|
|
16
|
+
}
|
|
17
|
+
const step1 = [MD5_ORIGIN, quality, SNG_ID, MEDIA_VERSION].join('¤');
|
|
18
|
+
let step2 = md5(step1) + '¤' + step1 + '¤';
|
|
19
|
+
while (step2.length % 16 > 0)
|
|
20
|
+
step2 += ' ';
|
|
21
|
+
return crypto_1.default.createCipheriv('aes-128-ecb', 'jo6aey6haid2Teih', '').update(step2, 'ascii', 'hex');
|
|
22
|
+
};
|
|
23
|
+
exports.getSongFileName = getSongFileName;
|
|
24
|
+
const getBlowfishKey = (trackId) => {
|
|
25
|
+
const SECRET = 'g4el58wc' + '0zvf9na1';
|
|
26
|
+
const idMd5 = md5(trackId);
|
|
27
|
+
let bfKey = '';
|
|
28
|
+
for (let i = 0; i < 16; i++) {
|
|
29
|
+
bfKey += String.fromCharCode(idMd5.charCodeAt(i) ^ idMd5.charCodeAt(i + 16) ^ SECRET.charCodeAt(i));
|
|
30
|
+
}
|
|
31
|
+
return bfKey;
|
|
32
|
+
};
|
|
33
|
+
const decryptChunk = (chunk, blowFishKey) => {
|
|
34
|
+
const cipher = crypto_1.default.createDecipheriv('bf-cbc', blowFishKey, Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]));
|
|
35
|
+
cipher.setAutoPadding(false);
|
|
36
|
+
return Buffer.concat([cipher.update(chunk), cipher.final()]);
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param source Downloaded song from `getTrackDownloadUrl`
|
|
41
|
+
* @param trackId Song ID as string
|
|
42
|
+
*/
|
|
43
|
+
const decryptDownload = (source, trackId) => {
|
|
44
|
+
const chunkSize = 2048;
|
|
45
|
+
const blowFishKey = getBlowfishKey(trackId);
|
|
46
|
+
let chunkIndex = 0;
|
|
47
|
+
let position = 0;
|
|
48
|
+
const destBuffer = Buffer.alloc(source.length);
|
|
49
|
+
while (position < source.length) {
|
|
50
|
+
const currentChunkSize = Math.min(chunkSize, source.length - position);
|
|
51
|
+
const sourceChunk = source.subarray(position, position + currentChunkSize);
|
|
52
|
+
if (chunkIndex % 3 > 0 || currentChunkSize < chunkSize) {
|
|
53
|
+
sourceChunk.copy(destBuffer, position);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
decryptChunk(sourceChunk, blowFishKey).copy(destBuffer, position);
|
|
57
|
+
}
|
|
58
|
+
position += currentChunkSize;
|
|
59
|
+
chunkIndex++;
|
|
60
|
+
}
|
|
61
|
+
return destBuffer;
|
|
62
|
+
};
|
|
63
|
+
exports.decryptDownload = decryptDownload;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fast LRU & TTL cache
|
|
3
|
+
* @param {Integer} options.max - Max entries in the cache. @default Infinity
|
|
4
|
+
* @param {Integer} options.ttl - Timeout before removing entries. @default Infinity
|
|
5
|
+
*/
|
|
6
|
+
declare class FastLRU {
|
|
7
|
+
_max: number;
|
|
8
|
+
_ttl: number;
|
|
9
|
+
_cache: Map<any, any>;
|
|
10
|
+
_meta: {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
constructor({ maxSize, ttl }: {
|
|
14
|
+
maxSize?: number | undefined;
|
|
15
|
+
ttl?: number | undefined;
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Add new entry
|
|
19
|
+
*/
|
|
20
|
+
set(key: string, value: any, ttl?: number): void;
|
|
21
|
+
/**
|
|
22
|
+
* Get entry
|
|
23
|
+
*/
|
|
24
|
+
get(key: string): any;
|
|
25
|
+
/**
|
|
26
|
+
* Get without hitting hits
|
|
27
|
+
*/
|
|
28
|
+
peek(key: string): any;
|
|
29
|
+
/**
|
|
30
|
+
* Remove entry
|
|
31
|
+
*/
|
|
32
|
+
delete(key: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Remove all entries
|
|
35
|
+
*/
|
|
36
|
+
clear(): void;
|
|
37
|
+
/**
|
|
38
|
+
* Check has entry
|
|
39
|
+
*/
|
|
40
|
+
has(key: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Get all kies
|
|
43
|
+
* @returns {Iterator} Iterator on all kies
|
|
44
|
+
*/
|
|
45
|
+
keys(): IterableIterator<any>;
|
|
46
|
+
/**
|
|
47
|
+
* Iterate over values
|
|
48
|
+
*/
|
|
49
|
+
values(): IterableIterator<any>;
|
|
50
|
+
/**
|
|
51
|
+
* Iterate over entries
|
|
52
|
+
*/
|
|
53
|
+
entries(): IterableIterator<[any, any]>;
|
|
54
|
+
/**
|
|
55
|
+
* For each
|
|
56
|
+
*/
|
|
57
|
+
forEach(cb: any): void;
|
|
58
|
+
/**
|
|
59
|
+
* Entries total size
|
|
60
|
+
*/
|
|
61
|
+
get size(): number;
|
|
62
|
+
}
|
|
63
|
+
export default FastLRU;
|