harmonyy 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.
@@ -0,0 +1,93 @@
1
+ export * from './types.js';
2
+ export default class YoutubeMusic {
3
+ $youtubeClientVersion;
4
+ $youtubeClientBuild;
5
+ $googVisitorId;
6
+ $cookieString;
7
+ async $getData() {
8
+ const dataReq = await fetch('https://music.youtube.com/sw.js_data');
9
+ const dataRes = await dataReq.text();
10
+ const dataArr = dataRes.split(',');
11
+ this.$youtubeClientVersion = dataArr.find(e => !isNaN(parseInt(e)));
12
+ this.$youtubeClientBuild = dataArr.find(e => e.startsWith('"1.')).slice(1, -1);
13
+ this.$googVisitorId = dataArr[15].slice(1, -1);
14
+ }
15
+ async $getCookies() {
16
+ const data = await fetch('https://music.youtube.com');
17
+ this.$cookieString = data.headers.getSetCookie().map(e => e.split(' Domain')[0]).join(' ');
18
+ }
19
+ async $getHeaders() {
20
+ if (!this.$youtubeClientVersion)
21
+ await this.$getData();
22
+ if (!this.$cookieString)
23
+ await this.$getCookies();
24
+ return {
25
+ 'Accept': '*/*',
26
+ 'Accept-Encoding': 'gzip, deflate, br, zstd',
27
+ 'Accept-Language': 'en-US,en;q=0.9',
28
+ 'Cache-Control': 'no-cache',
29
+ 'Content-Type': 'application/json',
30
+ 'Cookie': this.$cookieString,
31
+ 'Origin': 'https://music.youtube.com',
32
+ 'Referer': 'https://music.youtube.com/',
33
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
34
+ 'X-Goog-Visitor-Id': this.$googVisitorId,
35
+ 'x-youtube-bootstrap-logged-in': 'false',
36
+ 'x-youtube-client-name': this.$youtubeClientVersion,
37
+ 'x-youtube-client-version': this.$youtubeClientBuild
38
+ };
39
+ }
40
+ async search(query) {
41
+ if (!this.$youtubeClientBuild)
42
+ await this.$getData();
43
+ const payload = {
44
+ context: {
45
+ client: {
46
+ clientName: 'WEB_REMIX',
47
+ clientVersion: this.$youtubeClientBuild,
48
+ }
49
+ },
50
+ query,
51
+ suggestStats: {
52
+ validationStatus: 'VALID',
53
+ parameterValidationStatus: 'VALID_PARAMETERS',
54
+ clientName: 'youtube-music',
55
+ searchMethod: 'ENTER_KEY',
56
+ inputMethods: ['KEYBOARD'],
57
+ originalQuery: query
58
+ }
59
+ };
60
+ const searchReq = await fetch('https://music.youtube.com/youtubei/v1/search?prettyPrint=false', {
61
+ method: 'POST',
62
+ body: JSON.stringify(payload),
63
+ headers: await this.$getHeaders()
64
+ });
65
+ const searchRes = await searchReq.json();
66
+ return searchRes.contents.tabbedSearchResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents.map((a) => a.musicCardShelfRenderer || a.musicShelfRenderer || a);
67
+ }
68
+ async #browse(browseId) {
69
+ if (!this.$youtubeClientBuild)
70
+ await this.$getData();
71
+ const payload = {
72
+ context: {
73
+ client: {
74
+ clientName: 'WEB_REMIX',
75
+ clientVersion: this.$youtubeClientBuild,
76
+ }
77
+ },
78
+ browseId
79
+ };
80
+ const browseReq = await fetch('https://music.youtube.com/youtubei/v1/browse?prettyPrint=false', {
81
+ method: 'POST',
82
+ body: JSON.stringify(payload),
83
+ headers: await this.$getHeaders()
84
+ });
85
+ return await browseReq.json();
86
+ }
87
+ getAlbum(uri) {
88
+ return this.#browse(uri);
89
+ }
90
+ getArtist(uri) {
91
+ return this.#browse(uri);
92
+ }
93
+ }
@@ -0,0 +1,418 @@
1
+ type Run = {
2
+ text: string;
3
+ navigationEndpoint?: NavigationEndpoint;
4
+ };
5
+ type Runs = {
6
+ runs: Run[];
7
+ };
8
+ type Thumbnail = {
9
+ url: string;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ type MusicThumbnailRenderer = {
14
+ musicThumbnailRenderer: {
15
+ thumbnail: {
16
+ thumbnails: Thumbnail[];
17
+ };
18
+ thumbnailCrop: string;
19
+ thumbnailScale: string;
20
+ };
21
+ };
22
+ type Icon = {
23
+ iconType: string;
24
+ };
25
+ type BrowseEndpoint = {
26
+ browseId: string;
27
+ params?: string;
28
+ browseEndpointContextSupportedConfigs?: {
29
+ browseEndpointContextMusicConfig: {
30
+ pageType: string;
31
+ };
32
+ };
33
+ };
34
+ type WatchEndpoint = {
35
+ videoId?: string;
36
+ playlistId?: string;
37
+ index?: number;
38
+ params?: string;
39
+ playerParams?: string;
40
+ playlistSetVideoId?: string;
41
+ watchEndpointMusicSupportedConfigs?: {
42
+ watchEndpointMusicConfig: {
43
+ musicVideoType: string;
44
+ };
45
+ };
46
+ };
47
+ type WatchPlaylistEndpoint = {
48
+ playlistId: string;
49
+ params?: string;
50
+ };
51
+ type ShareEntityEndpoint = {
52
+ serializedShareEntity: string;
53
+ sharePanelType: string;
54
+ };
55
+ type ModalEndpoint = {
56
+ modal: {
57
+ modalWithTitleAndButtonRenderer: {
58
+ title: Runs;
59
+ content: Runs;
60
+ button: {
61
+ buttonRenderer: ButtonRenderer;
62
+ };
63
+ };
64
+ };
65
+ };
66
+ type QueueAddEndpoint = {
67
+ queueTarget: {
68
+ videoId?: string;
69
+ playlistId?: string;
70
+ onEmptyQueue?: {
71
+ watchEndpoint: WatchEndpoint;
72
+ };
73
+ };
74
+ queueInsertPosition: string;
75
+ commands: unknown[];
76
+ };
77
+ type NavigationEndpoint = {
78
+ browseEndpoint?: BrowseEndpoint;
79
+ watchEndpoint?: WatchEndpoint;
80
+ watchPlaylistEndpoint?: WatchPlaylistEndpoint;
81
+ shareEntityEndpoint?: ShareEntityEndpoint;
82
+ modalEndpoint?: ModalEndpoint;
83
+ };
84
+ type ButtonRenderer = {
85
+ style?: string;
86
+ size?: string;
87
+ isDisabled?: boolean;
88
+ text?: Runs;
89
+ icon?: Icon;
90
+ command?: NavigationEndpoint;
91
+ navigationEndpoint?: NavigationEndpoint;
92
+ };
93
+ type MenuItem = {
94
+ menuNavigationItemRenderer?: {
95
+ text: Runs;
96
+ icon: Icon;
97
+ navigationEndpoint: NavigationEndpoint;
98
+ };
99
+ menuServiceItemRenderer?: {
100
+ text: Runs;
101
+ icon: Icon;
102
+ serviceEndpoint: {
103
+ queueAddEndpoint?: QueueAddEndpoint;
104
+ };
105
+ };
106
+ toggleMenuServiceItemRenderer?: {
107
+ defaultText: Runs;
108
+ defaultIcon: Icon;
109
+ defaultServiceEndpoint: {
110
+ modalEndpoint: ModalEndpoint;
111
+ };
112
+ toggledText: Runs;
113
+ toggledIcon: Icon;
114
+ toggledServiceEndpoint: {
115
+ likeEndpoint: {
116
+ status: string;
117
+ target: {
118
+ videoId?: string;
119
+ playlistId?: string;
120
+ };
121
+ };
122
+ };
123
+ isToggled: boolean;
124
+ };
125
+ menuServiceItemDownloadRenderer?: {
126
+ serviceEndpoint: {
127
+ offlineVideoEndpoint: {
128
+ videoId: string;
129
+ onAddCommand: unknown;
130
+ };
131
+ };
132
+ badgeIcon: Icon;
133
+ };
134
+ };
135
+ type MenuRenderer = {
136
+ menuRenderer: {
137
+ items: MenuItem[];
138
+ topLevelButtons?: {
139
+ likeButtonRenderer: {
140
+ target: {
141
+ videoId: string;
142
+ };
143
+ likeStatus: string;
144
+ likesAllowed: boolean;
145
+ dislikeNavigationEndpoint: NavigationEndpoint;
146
+ likeCommand: NavigationEndpoint;
147
+ };
148
+ }[];
149
+ };
150
+ };
151
+ type MusicPlayButtonRenderer = {
152
+ playNavigationEndpoint: NavigationEndpoint;
153
+ playIcon: Icon;
154
+ pauseIcon: Icon;
155
+ iconColor: number;
156
+ backgroundColor: number;
157
+ activeBackgroundColor: number;
158
+ loadingIndicatorColor: number;
159
+ playingIcon: Icon;
160
+ iconLoadingColor: number;
161
+ activeScaleFactor: number;
162
+ buttonSize: string;
163
+ rippleTarget: string;
164
+ };
165
+ type MusicItemThumbnailOverlayRenderer = {
166
+ musicItemThumbnailOverlayRenderer: {
167
+ background: {
168
+ verticalGradient: {
169
+ gradientLayerColors: string[];
170
+ };
171
+ };
172
+ content: {
173
+ musicPlayButtonRenderer: MusicPlayButtonRenderer;
174
+ };
175
+ contentPosition: string;
176
+ displayStyle: string;
177
+ };
178
+ };
179
+ type MusicInlineBadge = {
180
+ musicInlineBadgeRenderer: {
181
+ icon: Icon;
182
+ };
183
+ };
184
+ type MusicResponsiveListItemRenderer = {
185
+ thumbnail?: MusicThumbnailRenderer;
186
+ overlay?: MusicItemThumbnailOverlayRenderer;
187
+ flexColumns: {
188
+ musicResponsiveListItemFlexColumnRenderer: {
189
+ text: Partial<Runs>;
190
+ displayPriority: string;
191
+ };
192
+ }[];
193
+ fixedColumns?: {
194
+ musicResponsiveListItemFixedColumnRenderer: {
195
+ text: Partial<Runs>;
196
+ displayPriority: string;
197
+ size: string;
198
+ };
199
+ }[];
200
+ menu?: MenuRenderer;
201
+ playlistItemData?: {
202
+ videoId: string;
203
+ playlistSetVideoId?: string;
204
+ };
205
+ flexColumnDisplayStyle?: string;
206
+ itemHeight?: string;
207
+ navigationEndpoint?: NavigationEndpoint;
208
+ badges?: MusicInlineBadge[];
209
+ };
210
+ type MusicTwoRowItemRenderer = {
211
+ thumbnailRenderer: MusicThumbnailRenderer;
212
+ aspectRatio: string;
213
+ title: Runs;
214
+ subtitle: Runs;
215
+ navigationEndpoint: NavigationEndpoint;
216
+ menu: MenuRenderer;
217
+ thumbnailOverlay: MusicItemThumbnailOverlayRenderer;
218
+ subtitleBadges?: MusicInlineBadge[];
219
+ };
220
+ type MusicShelfRenderer = {
221
+ title?: Runs;
222
+ contents: {
223
+ musicResponsiveListItemRenderer: MusicResponsiveListItemRenderer;
224
+ }[];
225
+ bottomText?: Runs;
226
+ bottomEndpoint?: NavigationEndpoint;
227
+ shelfDivider?: {
228
+ musicShelfDividerRenderer: {
229
+ hidden: boolean;
230
+ };
231
+ };
232
+ contentsMultiSelectable?: boolean;
233
+ };
234
+ type MusicCardShelfRenderer = {
235
+ thumbnail: MusicThumbnailRenderer;
236
+ title: Runs;
237
+ subtitle: Runs;
238
+ contents: {
239
+ musicResponsiveListItemRenderer: MusicResponsiveListItemRenderer;
240
+ }[];
241
+ buttons: {
242
+ buttonRenderer: ButtonRenderer;
243
+ }[];
244
+ menu: MenuRenderer;
245
+ onTap: NavigationEndpoint;
246
+ endIcon: Icon;
247
+ subtitleBadges?: MusicInlineBadge[];
248
+ thumbnailOverlay?: MusicItemThumbnailOverlayRenderer;
249
+ };
250
+ type MusicCarouselShelfRenderer = {
251
+ header: {
252
+ musicCarouselShelfBasicHeaderRenderer: {
253
+ title: Runs;
254
+ headerStyle: string;
255
+ moreContentButton?: {
256
+ buttonRenderer: ButtonRenderer;
257
+ };
258
+ };
259
+ };
260
+ contents: {
261
+ musicTwoRowItemRenderer: MusicTwoRowItemRenderer;
262
+ }[];
263
+ itemSize: string;
264
+ };
265
+ type MusicDescriptionShelfRenderer = {
266
+ header: Runs;
267
+ subheader?: Runs;
268
+ description: Runs;
269
+ moreButton: {
270
+ toggleButtonRenderer: ToggleButtonRenderer;
271
+ };
272
+ };
273
+ type ToggleButtonRenderer = {
274
+ isToggled: boolean;
275
+ isDisabled: boolean;
276
+ defaultIcon: Icon;
277
+ defaultText: Runs;
278
+ toggledIcon: Icon;
279
+ toggledText: Runs;
280
+ };
281
+ type MicroformatDataRenderer = {
282
+ urlCanonical: string;
283
+ title: string;
284
+ description: string;
285
+ thumbnail: {
286
+ thumbnails: Thumbnail[];
287
+ };
288
+ siteName: string;
289
+ appName: string;
290
+ androidPackage: string;
291
+ iosAppStoreId: string;
292
+ ogType: string;
293
+ urlApplinksWeb: string;
294
+ urlApplinksIos: string;
295
+ urlApplinksAndroid: string;
296
+ urlTwitterIos: string;
297
+ urlTwitterAndroid: string;
298
+ twitterCardType: string;
299
+ twitterSiteHandle: string;
300
+ };
301
+ type Microformat = {
302
+ microformatDataRenderer: MicroformatDataRenderer;
303
+ };
304
+ type AlbumHeaderRenderer = {
305
+ thumbnail: MusicThumbnailRenderer;
306
+ buttons: {
307
+ toggleButtonRenderer?: ToggleButtonRenderer;
308
+ musicPlayButtonRenderer?: MusicPlayButtonRenderer;
309
+ menuRenderer?: MenuRenderer['menuRenderer'];
310
+ }[];
311
+ title: Runs;
312
+ subtitle: Runs;
313
+ straplineTextOne: Runs;
314
+ straplineThumbnail: MusicThumbnailRenderer;
315
+ subtitleBadge?: MusicInlineBadge[];
316
+ description?: {
317
+ musicDescriptionShelfRenderer: MusicDescriptionShelfRenderer;
318
+ };
319
+ secondSubtitle: Runs;
320
+ };
321
+ type ArtistHeaderRenderer = {
322
+ title: Runs;
323
+ subscriptionButton: {
324
+ subscribeButtonRenderer: {
325
+ subscriberCountText: Runs;
326
+ subscribed: boolean;
327
+ enabled: boolean;
328
+ type: string;
329
+ channelId: string;
330
+ showPreferences: boolean;
331
+ subscriberCountWithSubscribeText: Runs;
332
+ subscribedButtonText: Runs;
333
+ unsubscribedButtonText: Runs;
334
+ unsubscribeButtonText: Runs;
335
+ serviceEndpoints: unknown[];
336
+ longSubscriberCountText: Runs;
337
+ shortSubscriberCountText: Runs;
338
+ signInEndpoint: NavigationEndpoint;
339
+ };
340
+ };
341
+ description?: Runs;
342
+ moreButton?: {
343
+ toggleButtonRenderer: ToggleButtonRenderer;
344
+ };
345
+ menu: MenuRenderer;
346
+ thumbnail: MusicThumbnailRenderer;
347
+ playButton: {
348
+ buttonRenderer: ButtonRenderer;
349
+ };
350
+ startRadioButton: {
351
+ buttonRenderer: ButtonRenderer;
352
+ };
353
+ shareEndpoint: NavigationEndpoint;
354
+ monthlyListenerCount?: Runs;
355
+ };
356
+ export type SearchSection = Partial<MusicCardShelfRenderer> & Partial<MusicShelfRenderer> & {
357
+ itemSectionRenderer?: {
358
+ contents: {
359
+ musicResponsiveListItemRenderer: MusicResponsiveListItemRenderer;
360
+ }[];
361
+ };
362
+ };
363
+ export type SearchResponse = SearchSection[];
364
+ export interface AlbumResponse {
365
+ contents: {
366
+ twoColumnBrowseResultsRenderer: {
367
+ tabs: {
368
+ tabRenderer: {
369
+ content: {
370
+ sectionListRenderer: {
371
+ contents: {
372
+ musicResponsiveHeaderRenderer: AlbumHeaderRenderer;
373
+ }[];
374
+ };
375
+ };
376
+ };
377
+ }[];
378
+ secondaryContents: {
379
+ sectionListRenderer: {
380
+ contents: {
381
+ musicShelfRenderer: MusicShelfRenderer;
382
+ }[];
383
+ };
384
+ };
385
+ };
386
+ };
387
+ microformat: Microformat;
388
+ background: MusicThumbnailRenderer;
389
+ }
390
+ export interface ArtistResponse {
391
+ contents: {
392
+ singleColumnBrowseResultsRenderer: {
393
+ tabs: {
394
+ tabRenderer: {
395
+ endpoint: {
396
+ browseEndpoint: BrowseEndpoint;
397
+ };
398
+ title: string;
399
+ selected: boolean;
400
+ content: {
401
+ sectionListRenderer: {
402
+ contents: {
403
+ musicShelfRenderer?: MusicShelfRenderer;
404
+ musicCarouselShelfRenderer?: MusicCarouselShelfRenderer;
405
+ musicDescriptionShelfRenderer?: MusicDescriptionShelfRenderer;
406
+ }[];
407
+ };
408
+ };
409
+ };
410
+ }[];
411
+ };
412
+ };
413
+ header: {
414
+ musicImmersiveHeaderRenderer: ArtistHeaderRenderer;
415
+ };
416
+ microformat: Microformat;
417
+ }
418
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "harmonyy",
3
+ "description": "a keyless search tool for music streaming services",
4
+ "type": "module",
5
+ "version": "1.0.0",
6
+ "homepage": "https://github.com/VillainsRule/harmony",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/VillainsRule/harmony.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/VillainsRule/harmony/issues"
13
+ },
14
+ "keywords": [
15
+ "spotify",
16
+ "apple music",
17
+ "youtube music"
18
+ ],
19
+ "exports": {
20
+ "./package.json": "./package.json",
21
+ "./apple": {
22
+ "import": "./dist/apple/index.js",
23
+ "types": "./dist/apple/index.d.ts"
24
+ },
25
+ "./spotify": {
26
+ "import": "./dist/spotify/index.js",
27
+ "types": "./dist/spotify/index.d.ts"
28
+ },
29
+ "./youtube": {
30
+ "import": "./dist/youtube/index.js",
31
+ "types": "./dist/youtube/index.d.ts"
32
+ }
33
+ },
34
+ "typesVersions": {
35
+ "*": {
36
+ "apple": [
37
+ "dist/apple/index.d.ts"
38
+ ],
39
+ "spotify": [
40
+ "dist/spotify/index.d.ts"
41
+ ],
42
+ "youtube": [
43
+ "dist/youtube/index.d.ts"
44
+ ]
45
+ }
46
+ },
47
+ "files": [
48
+ "dist/**/*.js",
49
+ "dist/**/*.d.ts",
50
+ "README.md",
51
+ "LICENSE"
52
+ ],
53
+ "scripts": {
54
+ "build": "bunx --bun tsc -P tsconfig.build.json",
55
+ "prod": "bun run build && npm publish"
56
+ },
57
+ "devDependencies": {
58
+ "@types/bun": "^1.4.2",
59
+ "typescript": "^7.0.2"
60
+ }
61
+ }