YT Music ( Search, Home, Detail..)
JavaScriptPublicby zx-apiOct 6, 2026, 09:50 AMExpires: Never238 views
1const axios = require('axios');
2
3class YTMusicScraper {
4 constructor(options = {}) {
5 this.baseUrl = 'https://music.youtube.com/youtubei/v1';
6 this.apiKey = '';
7 this.context = {};
8 this.debug = options.debug || false;
9 this.delay = options.delay || 500; // ms antar request
10 this.headers = {
11 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36',
12 'Accept': '*/*',
13 'Accept-Language': 'id,en-US;q=0.9,en;q=0.8,pt;q=0.7,ar;q=0.6,ja;q=0.5',
14 'Content-Type': 'application/json',
15 'Origin': 'https://music.youtube.com',
16 'Referer': 'https://music.youtube.com/',
17 'sec-ch-ua': '"Chromium";v="154", "Google Chrome";v="154", "Not A(Brand";v="99"',
18 'sec-ch-ua-arch': '"x86"',
19 'sec-ch-ua-bitness': '"64"',
20 'sec-ch-ua-form-factors': '"Desktop"',
21 'sec-ch-ua-platform': '"Windows"',
22 'sec-ch-ua-platform-version': '"15.0.0"',
23 'sec-fetch-dest': 'empty',
24 'sec-fetch-mode': 'cors',
25 'sec-fetch-site': 'same-origin'
26 };
27 }
28
29 async init() {
30 try {
31 console.log('[*] Menginisialisasi scraper...');
32 const { data: html } = await axios.get('https://music.youtube.com/', {
33 headers: this.headers
34 });
35
36 const keyMatch = html.match(/"INNERTUBE_API_KEY":"([a-zA-Z0-9_-]+)"/);
37 this.apiKey = keyMatch ? keyMatch[1] : 'AIzaSyC9XL3ZjWddXya6I7t8l2Qw5Yv8zX1z2Qw';
38
39 const ctxMatch = html.match(/"INNERTUBE_CONTEXT":(\{.+?\})\},\s*"INNERTUBE_CONTEXT_CLIENT_NAME"/);
40 if (ctxMatch) {
41 try { this.context = JSON.parse(ctxMatch[1]); }
42 catch { this.context = this._defaultContext(); }
43 } else {
44 this.context = this._defaultContext();
45 }
46
47 const visMatch = html.match(/"VISITOR_DATA":"([a-zA-Z0-9_-]+)"/);
48 if (visMatch) {
49 this.headers['X-Goog-Visitor-Id'] = visMatch[1];
50 if (this.context.client) this.context.client.visitorData = visMatch[1];
51 }
52
53 console.log('[✓] Scraper siap!');
54 return this;
55 } catch (err) {
56 console.error('[!] Init gagal:', err.message);
57 this.apiKey = 'AIzaSyC9XL3ZjWddXya6I7t8l2Qw5Yv8zX1z2Qw';
58 this.context = this._defaultContext();
59 return this;
60 }
61 }
62
63 _defaultContext() {
64 return {
65 client: {
66 clientName: 'WEB_REMIX',
67 clientVersion: '1.20261006.01.00',
68 hl: 'id',
69 gl: 'ID',
70 }
71 };
72 }
73
74 _sleep(ms) {
75 return new Promise(resolve => setTimeout(resolve, ms));
76 }
77
78 async _request(endpoint, body = {}) {
79 await this._sleep(this.delay);
80
81 const url = `${this.baseUrl}/${endpoint}?prettyPrint=false`;
82 const payload = {
83 context: this.context.client ? { client: this.context.client } : this.context,
84 ...body
85 };
86
87 if (this.debug) {
88 console.log(`\n[DEBUG] POST /${endpoint}`);
89 console.log('[DEBUG] Payload:', JSON.stringify(payload).substring(0, 300));
90 }
91
92 const { data } = await axios.post(url, payload, { headers: this.headers });
93
94 if (this.debug) {
95 console.log('[DEBUG] Response keys:', Object.keys(data));
96 }
97 return data;
98 }
99
100 _text(runs) {
101 if (!runs) return '';
102 if (typeof runs === 'string') return runs;
103 if (Array.isArray(runs)) return runs.map(r => r?.text || '').join('');
104 return runs.text || '';
105 }
106
107 _thumbs(obj) {
108 if (!obj) return [];
109 if (obj.musicThumbnailRenderer?.thumbnail?.thumbnails) {
110 return obj.musicThumbnailRenderer.thumbnail.thumbnails;
111 }
112 if (obj.musicItemThumbnailRenderer?.thumbnail?.thumbnails) {
113 return obj.musicItemThumbnailRenderer.thumbnail.thumbnails;
114 }
115 if (obj.thumbnail?.thumbnails) return obj.thumbnail.thumbnails;
116 if (obj.thumbnails) return obj.thumbnails;
117 if (obj.thumbnailRenderer) return this._thumbs(obj.thumbnailRenderer);
118 return [];
119 }
120
121 _bestThumb(obj) {
122 const thumbs = this._thumbs(obj);
123 if (thumbs.length === 0) return null;
124 return thumbs[thumbs.length - 1].url; // yang terakhir biasanya paling besar
125 }
126
127 _findAll(obj, key, results = []) {
128 if (!obj || typeof obj !== 'object') return results;
129 if (obj[key] !== undefined) results.push(obj[key]);
130 for (const val of Object.values(obj)) {
131 if (val && typeof val === 'object') {
132 this._findAll(val, key, results);
133 }
134 }
135 return results;
136 }
137
138 _parseListItem(renderer) {
139 if (!renderer) return null;
140 try {
141 const cols = renderer.flexColumns || [];
142 const col0 = cols[0]?.musicResponsiveListItemFlexColumnRenderer;
143 const col1 = cols[1]?.musicResponsiveListItemFlexColumnRenderer;
144 const col2 = cols[2]?.musicResponsiveListItemFlexColumnRenderer;
145
146 const title = this._text(col0?.text?.runs);
147 if (!title) return null;
148
149 let videoId = null;
150
151 const playBtn = renderer.overlay
152 ?.musicItemThumbnailOverlayRenderer
153 ?.content
154 ?.musicPlayButtonRenderer;
155 if (playBtn?.playNavigationEndpoint?.watchEndpoint?.videoId) {
156 videoId = playBtn.playNavigationEndpoint.watchEndpoint.videoId;
157 }
158
159 if (!videoId) {
160 const runs0 = col0?.text?.runs || [];
161 for (const run of runs0) {
162 if (run.navigationEndpoint?.watchEndpoint?.videoId) {
163 videoId = run.navigationEndpoint.watchEndpoint.videoId;
164 break;
165 }
166 }
167 }
168
169 if (!videoId && renderer.playlistItemData?.videoId) {
170 videoId = renderer.playlistItemData.videoId;
171 }
172
173 let type = '';
174 let artist = '';
175 let plays = '';
176 let artistId = null;
177
178 const runs1 = col1?.text?.runs || [];
179 for (const run of runs1) {
180 const t = (run.text || '').trim();
181 if (!t || t === '•' || t === '·') continue;
182
183 if (['Song', 'Lagu', 'Video', 'Album', 'Single', 'EP',
184 'Playlist', 'Artis', 'Artist', 'Podcast'].includes(t)) {
185 type = t;
186 continue;
187 }
188
189 if (/^[\d.,]+\s*(plays|views|listeners|jt|rb|ribu|M|K|k)/i.test(t)) {
190 plays = t;
191 continue;
192 }
193
194 if (run.navigationEndpoint?.browseEndpoint) {
195 const pageType = run.navigationEndpoint.browseEndpoint
196 ?.browseEndpointContextSupportedConfigs
197 ?.browseEndpointContextMusicConfig
198 ?.pageType || '';
199
200 if (pageType === 'MUSIC_PAGE_TYPE_ARTIST' || pageType === 'MUSIC_PAGE_TYPE_USER_CHANNEL') {
201 artist = t;
202 artistId = run.navigationEndpoint.browseEndpoint.browseId;
203 }
204 continue;
205 }
206
207 if (!artist && !type.includes('Artis')) {
208 artist = t;
209 }
210 }
211
212 let album = '';
213 let albumId = null;
214 if (col2) {
215 album = this._text(col2.text?.runs);
216 const runs2 = col2.text?.runs || [];
217 for (const run of runs2) {
218 if (run.navigationEndpoint?.browseEndpoint?.browseId) {
219 albumId = run.navigationEndpoint.browseEndpoint.browseId;
220 break;
221 }
222 }
223 }
224
225 let browseId = null;
226 let pageType = '';
227 if (renderer.navigationEndpoint?.browseEndpoint) {
228 browseId = renderer.navigationEndpoint.browseEndpoint.browseId;
229 pageType = renderer.navigationEndpoint.browseEndpoint
230 ?.browseEndpointContextSupportedConfigs
231 ?.browseEndpointContextMusicConfig
232 ?.pageType || '';
233 }
234
235 return {
236 type: type || pageType || 'Unknown',
237 videoId,
238 browseId,
239 title,
240 artist,
241 artistId,
242 album,
243 albumId,
244 plays,
245 thumbnail: this._bestThumb(renderer.thumbnail || renderer)
246 };
247 } catch (err) {
248 if (this.debug) console.error('[DEBUG] Parse error:', err.message);
249 return null;
250 }
251 }
252
253 _parseCard(card) {
254 if (!card) return null;
255 try {
256 const title = this._text(card.title?.runs);
257 const subtitle = this._text(card.subtitle?.runs);
258
259 let videoId = null;
260 let browseId = null;
261
262 const buttons = card.buttons || [];
263 for (const btn of buttons) {
264 const playBtn = btn.musicPlayButtonRenderer || btn.buttonRenderer;
265 if (playBtn?.playNavigationEndpoint?.watchEndpoint?.videoId) {
266 videoId = playBtn.playNavigationEndpoint.watchEndpoint.videoId;
267 }
268 if (playBtn?.playNavigationEndpoint?.browseEndpoint?.browseId) {
269 browseId = playBtn.playNavigationEndpoint.browseEndpoint.browseId;
270 }
271 }
272
273 // Fallback dari navigationEndpoint langsung
274 if (!browseId && card.onTap?.watchEndpoint?.videoId) {
275 videoId = card.onTap.watchEndpoint.videoId;
276 }
277 if (!browseId && card.onTap?.browseEndpoint?.browseId) {
278 browseId = card.onTap.browseEndpoint.browseId;
279 }
280
281 // Thumbnail
282 let thumb = this._bestThumb(card.thumbnail);
283 if (!thumb) {
284 thumb = this._bestThumb(card.thumbnailRenderer);
285 }
286
287 return {
288 type: 'Top Result',
289 videoId,
290 browseId,
291 title,
292 subtitle,
293 thumbnail: thumb
294 };
295 } catch (err) {
296 if (this.debug) console.error('[DEBUG] Card parse error:', err.message);
297 return null;
298 }
299 }
300
301 _parseTwoRow(renderer) {
302 if (!renderer) return null;
303 try {
304 const title = this._text(renderer.title?.runs);
305 const subtitle = this._text(renderer.subtitle?.runs);
306
307 let videoId = null;
308 let browseId = null;
309 let pageType = '';
310
311 const nav = renderer.navigationEndpoint;
312 if (nav?.watchEndpoint?.videoId) videoId = nav.watchEndpoint.videoId;
313 if (nav?.browseEndpoint?.browseId) {
314 browseId = nav.browseEndpoint.browseId;
315 pageType = nav.browseEndpoint
316 ?.browseEndpointContextSupportedConfigs
317 ?.browseEndpointContextMusicConfig
318 ?.pageType || '';
319 }
320
321 return {
322 type: pageType || (videoId ? 'Song' : 'Browse'),
323 videoId,
324 browseId,
325 title,
326 subtitle,
327 thumbnail: this._bestThumb(renderer.thumbnailRenderer || renderer.thumbnail)
328 };
329 } catch {
330 return null;
331 }
332 }
333
334 async search(query, filter = '') {
335 console.log(`\n[*] Mencari: "${query}"${filter ? ` (filter: ${filter})` : ''}...`);
336
337 const body = { query };
338
339 const filters = {
340 songs: 'EgWKAQIIAWoKEAkQBRAKEAMQBQ%3D%3D',
341 videos: 'EgWKAQIQAWoKEAkQBRAKEAMQBQ%3D%3D',
342 albums: 'EgWKAQIYAWoKEAkQBRAKEAMQBQ%3D%3D',
343 artists: 'EgWKAQIgAWoKEAkQBRAKEAMQBQ%3D%3D',
344 playlists: 'EgWKAQIoAWoKEAkQBRAKEAMQBQ%3D%3D'
345 };
346 if (filter && filters[filter]) body.params = filters[filter];
347
348 const data = await this._request('search', body);
349 const results = [];
350
351 const tabs = data?.contents?.tabbedSearchResultsRenderer?.tabs || [];
352 const sections = tabs[0]?.tabRenderer?.content?.sectionListRenderer?.contents || [];
353
354 if (this.debug) {
355 console.log(`[DEBUG] Sections found: ${sections.length}`);
356 sections.forEach((s, i) => {
357 console.log(`[DEBUG] [${i}] keys:`, Object.keys(s));
358 });
359 }
360
361 for (const section of sections) {
362 if (section.musicCardShelfRenderer) {
363 const card = this._parseCard(section.musicCardShelfRenderer);
364 if (card) results.push(card);
365 }
366
367 if (section.musicShelfRenderer) {
368 const shelfTitle = this._text(section.musicShelfRenderer.title?.runs);
369 const items = section.musicShelfRenderer.contents || [];
370
371 if (this.debug) {
372 console.log(`[DEBUG] Shelf "${shelfTitle}": ${items.length} items`);
373 }
374
375 for (const item of items) {
376 const parsed = this._parseListItem(item.musicResponsiveListItemRenderer);
377 if (parsed) {
378 parsed.category = shelfTitle;
379 results.push(parsed);
380 }
381 }
382 }
383 }
384
385 if (results.length <= 1) {
386 if (this.debug) console.log('[DEBUG] Fallback: recursive search...');
387
388 const allItems = this._findAll(data, 'musicResponsiveListItemRenderer');
389 for (const renderer of allItems) {
390 const parsed = this._parseListItem(renderer);
391 if (parsed) {
392 const isDup = results.some(r =>
393 (r.videoId && r.videoId === parsed.videoId) ||
394 (r.browseId && r.browseId === parsed.browseId) ||
395 (r.title === parsed.title && r.artist === parsed.artist)
396 );
397 if (!isDup) results.push(parsed);
398 }
399 }
400 }
401
402 if (results.length === 0) {
403 console.log('[!] Tidak ada hasil. Aktifkan debug: true untuk lihat response.');
404 if (this.debug) {
405 console.log('[DEBUG] Full response:', JSON.stringify(data, null, 2).substring(0, 2000));
406 }
407 }
408
409 return results;
410 }
411
412 async getSuggestions(query) {
413 console.log(`\n[*] Suggestions untuk: "${query}"...`);
414 const data = await this._request('music/get_search_suggestions', { input: query });
415
416 const suggestions = [];
417 const sections = data?.contents || [];
418
419 for (const section of sections) {
420 const items = section?.searchSuggestionsSectionRenderer?.contents || [];
421 for (const item of items) {
422 if (item.searchSuggestionRenderer) {
423 const text = this._text(item.searchSuggestionRenderer.suggestion?.runs);
424 const navQuery = item.searchSuggestionRenderer
425 ?.navigationEndpoint
426 ?.searchEndpoint
427 ?.query;
428 suggestions.push({
429 type: 'suggestion',
430 text,
431 query: navQuery || text
432 });
433 }
434
435 if (item.musicResponsiveListItemRenderer) {
436 const parsed = this._parseListItem(item.musicResponsiveListItemRenderer);
437 if (parsed) {
438 parsed.type = 'rich_suggestion';
439 suggestions.push(parsed);
440 }
441 }
442 }
443 }
444
445 return suggestions;
446 }
447
448 async getHome() {
449 console.log('\n[*] Mengambil Home...');
450 const data = await this._request('browse', { browseId: 'FEmusic_home' });
451 return this._parseBrowseSections(data);
452 }
453
454 async getExplore() {
455 console.log('\n[*] Mengambil Explore...');
456 const data = await this._request('browse', { browseId: 'FEmusic_explore' });
457 return this._parseBrowseSections(data);
458 }
459
460 async getCharts() {
461 console.log('\n[*] Mengambil Charts...');
462 const data = await this._request('browse', { browseId: 'FEmusic_charts' });
463 return this._parseBrowseSections(data);
464 }
465
466 _parseBrowseSections(data) {
467 const sections = [];
468 const tabs = data?.contents?.singleColumnBrowseResultsRenderer?.tabs || [];
469 const contents = tabs[0]?.tabRenderer?.content?.sectionListRenderer?.contents || [];
470
471 for (const section of contents) {
472 const shelf = section.musicCarouselShelfRenderer
473 || section.musicImmersiveCarouselShelfRenderer
474 || section.musicShelfRenderer;
475
476 if (shelf) {
477 const header = this._text(
478 shelf.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs
479 || shelf.header?.musicShelfHeaderRenderer?.title?.runs
480 || shelf.title?.runs
481 ) || 'Section';
482
483 const items = (shelf.contents || []).map(c => {
484 if (c.musicTwoRowItemRenderer) {
485 return this._parseTwoRow(c.musicTwoRowItemRenderer);
486 }
487 if (c.musicResponsiveListItemRenderer) {
488 return this._parseListItem(c.musicResponsiveListItemRenderer);
489 }
490 if (c.musicNavigationButtonRenderer) {
491 const btn = c.musicNavigationButtonRenderer;
492 return {
493 type: 'shortcut',
494 title: this._text(btn.buttonText?.runs),
495 browseId: btn.clickCommand?.browseEndpoint?.browseId || null
496 };
497 }
498 return null;
499 }).filter(Boolean);
500
501 sections.push({ header, items });
502 }
503 }
504
505 return sections;
506 }
507
508 async getDetail(videoId) {
509 console.log(`\n[*] Detail untuk: ${videoId}...`);
510 const data = await this._request('next', {
511 videoId,
512 isAudioOnly: true
513 });
514
515 const detail = {
516 videoId,
517 title: '',
518 artist: '',
519 album: '',
520 duration: '',
521 lyricsBrowseId: null,
522 relatedBrowseId: null,
523 queue: []
524 };
525
526 try {
527 const tabs = data?.contents
528 ?.singleColumnMusicWatchNextResultsRenderer
529 ?.tabbedRenderer
530 ?.watchNextTabbedResultsRenderer
531 ?.tabs || [];
532
533 if (tabs[0]) {
534 const queueRenderer = tabs[0].tabRenderer?.content
535 ?.musicQueueRenderer?.content
536 ?.playlistPanelRenderer;
537
538 if (queueRenderer?.contents) {
539 detail.queue = queueRenderer.contents.map(item => {
540 const r = item.playlistPanelVideoRenderer;
541 if (!r) return null;
542 return {
543 videoId: r.videoId,
544 title: this._text(r.title?.runs),
545 artist: this._text(r.longBylineText?.runs || r.shortBylineText?.runs),
546 duration: this._text(r.lengthText?.runs),
547 thumbnail: this._bestThumb(r.thumbnail)
548 };
549 }).filter(Boolean);
550
551 if (detail.queue.length > 0) {
552 detail.title = detail.queue[0].title;
553 detail.artist = detail.queue[0].artist;
554 detail.duration = detail.queue[0].duration;
555 }
556 }
557 }
558
559 if (tabs[1]?.tabRenderer?.endpoint?.browseEndpoint?.browseId) {
560 detail.lyricsBrowseId = tabs[1].tabRenderer.endpoint.browseEndpoint.browseId;
561 }
562
563 if (tabs[2]?.tabRenderer?.endpoint?.browseEndpoint?.browseId) {
564 detail.relatedBrowseId = tabs[2].tabRenderer.endpoint.browseEndpoint.browseId;
565 }
566 } catch (err) {
567 console.error('[!] Error parsing detail:', err.message);
568 }
569
570 return detail;
571 }
572
573 async getLyrics(browseId) {
574 if (!browseId) return null;
575 console.log('\n[*] Mengambil lirik...');
576 const data = await this._request('browse', { browseId });
577
578 try {
579 const contents = data?.contents?.sectionListRenderer?.contents || [];
580 for (const section of contents) {
581 const desc = section?.musicDescriptionShelfRenderer;
582 if (desc) {
583 return {
584 source: this._text(desc.header?.runs),
585 lyrics: this._text(desc.description?.runs),
586 footer: this._text(desc.footer?.runs)
587 };
588 }
589 }
590 } catch (err) {
591 console.error('[!] Error parsing lyrics:', err.message);
592 }
593 return null;
594 }
595
596 async getAlbum(browseId) {
597 console.log(`\n[*] Mengambil album: ${browseId}...`);
598 const data = await this._request('browse', { browseId });
599
600 try {
601 const header = data?.header?.musicImmersiveHeaderRenderer
602 || data?.header?.musicDetailHeaderRenderer
603 || data?.header?.musicVisualHeaderRenderer;
604
605 const album = {
606 browseId,
607 title: this._text(header?.title?.runs),
608 artist: this._text(header?.subtitle?.runs),
609 year: '',
610 tracks: []
611 };
612
613 const contents = data?.contents?.singleColumnBrowseResultsRenderer
614 ?.tabs?.[0]?.tabRenderer?.content
615 ?.sectionListRenderer?.contents || [];
616
617 for (const section of contents) {
618 const shelf = section?.musicShelfRenderer;
619 if (shelf?.contents) {
620 for (const item of shelf.contents) {
621 const r = item?.musicResponsiveListItemRenderer;
622 if (r) {
623 const parsed = this._parseListItem(r);
624 if (parsed) album.tracks.push(parsed);
625 }
626 }
627 }
628 }
629
630 // Kalau kosong, coba recursive
631 if (album.tracks.length === 0) {
632 const allItems = this._findAll(data, 'musicResponsiveListItemRenderer');
633 album.tracks = allItems
634 .map(r => this._parseListItem(r))
635 .filter(Boolean);
636 }
637
638 return album;
639 } catch (err) {
640 console.error('[!] Error parsing album:', err.message);
641 return null;
642 }
643 }
644
645 async getArtist(browseId) {
646 console.log(`\n[*] Mengambil artis: ${browseId}...`);
647 const data = await this._request('browse', { browseId });
648
649 try {
650 const header = data?.header?.musicImmersiveHeaderRenderer;
651 const artist = {
652 browseId,
653 name: this._text(header?.title?.runs),
654 description: this._text(header?.description?.runs),
655 subscribers: this._text(header?.subscriptionButton?.subscribeButtonRenderer?.subscriberCountText?.runs),
656 sections: []
657 };
658
659 const contents = data?.contents?.singleColumnBrowseResultsRenderer
660 ?.tabs?.[0]?.tabRenderer?.content
661 ?.sectionListRenderer?.contents || [];
662
663 for (const section of contents) {
664 const shelf = section?.musicShelfRenderer
665 || section?.musicCarouselShelfRenderer;
666 if (shelf) {
667 const title = this._text(
668 shelf.title?.runs
669 || shelf.header?.musicCarouselShelfBasicHeaderRenderer?.title?.runs
670 ) || 'Section';
671
672 const items = (shelf.contents || []).map(c => {
673 if (c.musicResponsiveListItemRenderer) {
674 return this._parseListItem(c.musicResponsiveListItemRenderer);
675 }
676 if (c.musicTwoRowItemRenderer) {
677 return this._parseTwoRow(c.musicTwoRowItemRenderer);
678 }
679 return null;
680 }).filter(Boolean);
681
682 artist.sections.push({ title, items });
683 }
684 }
685
686 return artist;
687 } catch (err) {
688 console.error('[!] Error parsing artist:', err.message);
689 return null;
690 }
691 }
692
693 async getPlaylist(browseId) {
694 console.log(`\n[*] Mengambil playlist: ${browseId}...`);
695 const data = await this._request('browse', { browseId });
696
697 try {
698 const header = data?.header?.musicDetailHeaderRenderer;
699 const playlist = {
700 browseId,
701 title: this._text(header?.title?.runs),
702 description: this._text(header?.description?.runs),
703 author: this._text(header?.subtitle?.runs),
704 tracks: []
705 };
706
707 const contents = data?.contents?.singleColumnBrowseResultsRenderer
708 ?.tabs?.[0]?.tabRenderer?.content
709 ?.sectionListRenderer?.contents || [];
710
711 for (const section of contents) {
712 const shelf = section?.musicPlaylistShelfRenderer
713 || section?.musicShelfRenderer;
714 if (shelf?.contents) {
715 for (const item of shelf.contents) {
716 const r = item?.musicResponsiveListItemRenderer;
717 if (r) {
718 const parsed = this._parseListItem(r);
719 if (parsed) playlist.tracks.push(parsed);
720 }
721 }
722 }
723 }
724
725 if (playlist.tracks.length === 0) {
726 const allItems = this._findAll(data, 'musicResponsiveListItemRenderer');
727 playlist.tracks = allItems
728 .map(r => this._parseListItem(r))
729 .filter(Boolean);
730 }
731
732 return playlist;
733 } catch (err) {
734 console.error('[!] Error parsing playlist:', err.message);
735 return null;
736 }
737 }
738}
739
740async function main() {
741 const ytm = new YTMusicScraper({
742 debug: false,
743 delay: 300
744 });
745
746 await ytm.init();
747
748 const searchResults = await ytm.search('Judika');
749 console.log('\n========== HASIL SEARCH ==========');
750 console.log(JSON.stringify(searchResults, null, 2));
751
752 const songResults = await ytm.search('januari glenn fredly', 'songs');
753 console.log('\n========== SEARCH SONGS ==========');
754 console.log(JSON.stringify(songResults, null, 2));
755
756 const suggestions = await ytm.getSuggestions('januari glenn');
757 console.log('\n========== SUGGESTIONS ==========');
758 console.log(JSON.stringify(suggestions, null, 2));
759
760 const home = await ytm.getHome();
761 console.log('\n========== HOME ==========');
762 console.log(JSON.stringify(home, null, 2));
763
764 const explore = await ytm.getExplore();
765 console.log('\n========== EXPLORE ==========');
766 console.log(JSON.stringify(explore, null, 2));
767
768 const song = songResults.find(r => r.videoId) || searchResults.find(r => r.videoId);
769 if (song?.videoId) {
770 const detail = await ytm.getDetail(song.videoId);
771 console.log('\n========== DETAIL ==========');
772 console.log(JSON.stringify(detail, null, 2));
773
774 if (detail.lyricsBrowseId) {
775 const lyrics = await ytm.getLyrics(detail.lyricsBrowseId);
776 console.log('\n========== LYRICS ==========');
777 console.log(JSON.stringify(lyrics, null, 2));
778 }
779 }
780
781 const withAlbum = searchResults.find(r => r.albumId);
782 if (withAlbum?.albumId) {
783 const album = await ytm.getAlbum(withAlbum.albumId);
784 console.log('\n========== ALBUM ==========');
785 console.log(JSON.stringify(album, null, 2));
786 }
787
788 const withArtist = searchResults.find(r => r.artistId);
789 if (withArtist?.artistId) {
790 const artist = await ytm.getArtist(withArtist.artistId);
791 console.log('\n========== ARTIST ==========');
792 console.log(JSON.stringify(artist, null, 2));
793 }
794}
795
796main().catch(err => {
797 console.error('\n[FATAL]', err.message);
798 if (err.response) {
799 console.error('Status:', err.response.status);
800 console.error('Data:', JSON.stringify(err.response.data).substring(0, 500));
801 }
802});802 lines·29,656 chars·29.0 KB
Wwrap·Ffullscreen