YT Music ( Search, Home, Detail..)

JavaScriptPublic
by zx-apiOct 6, 2026, 09:50 AMExpires: Never238 views
YT Music ( Search, Home, Detail..)
Raw
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