@@ -4,39 +4,35 @@ const { readFileSync, writeFileSync } = require('fs');
44const { join } = require ( 'path' ) ;
55const https = require ( 'https' ) ;
66if ( ! KEY ) throw new Error ( 'API key missing' ) ;
7- const excludedIds = new Set (
8- readFileSync ( join ( __dirname , '..' , 'yt' , 'exclude.txt' ) , 'utf8' )
9- . split ( ',' ) . map ( id => id . trim ( ) ) . filter ( Boolean )
10- ) ;
11- const htmlContent = readFileSync ( join ( __dirname , '..' , 'index.html' ) , 'utf8' ) ;
12- const searchDiv = htmlContent . match ( / i d = " s h u f f l e " > ( [ \s \S ] * ?) < \/ d i v > / ) ?. [ 1 ] || '' ;
13- const cleanId = id => id . split ( '?' ) [ 0 ] ;
14- const isVideoId = id => cleanId ( id ) . length === 11 ;
15- const isPlaylistId = id => cleanId ( id ) . length > 11 ;
167const fetchUrl = url => new Promise ( ( resolve , reject ) => {
178 https . get ( url . toString ( ) , res => {
189 let data = '' ;
1910 res . on ( 'data' , c => data += c ) ;
2011 res . on ( 'end' , ( ) => resolve ( JSON . parse ( data ) ) ) ;
2112 } ) . on ( 'error' , reject ) ;
2213} ) ;
23- const buildUrl = ( base , params ) => {
24- const u = new URL ( base ) ;
25- u . search = new URLSearchParams ( params ) ;
26- return u ;
27- } ;
14+ const buildUrl = ( base , params ) => { const u = new URL ( base ) ;
15+ u . search = new URLSearchParams ( params ) ; return u ; } ;
2816const extractIds = ( regex , src ) => {
2917 const out = [ ] ;
3018 let m ;
3119 while ( ( m = regex . exec ( src ) ) ) {
3220 if ( m [ 1 ] ) out . push ( ...m [ 1 ] . split ( ',' ) . map ( s => s . trim ( ) ) ) ;
33- if ( m [ 2 ] ) out . push ( ...m [ 2 ] . split ( ',' ) . map ( s => s . trim ( ) ) ) ;
3421 }
3522 return out ;
3623} ;
37- const allIds = extractIds ( / (?: p = " ( [ ^ " ] + ) " | v = " ( [ ^ " ] + ) " ) / g, searchDiv ) ;
24+ const htmlContent = readFileSync ( join ( __dirname , '..' , 'index.html' ) , 'utf8' ) ;
25+ const searchDiv = htmlContent . match ( / i d = " s h u f f l e " > ( [ \s \S ] * ?) < \/ d i v > / ) ?. [ 1 ] || '' ;
26+ const allIds = extractIds ( / (?: p | v ) = " ( [ ^ " ] + ) " / g, searchDiv ) ;
27+ const cleanId = id => id . split ( '?' ) [ 0 ] ;
28+ const isVideoId = id => cleanId ( id ) . length === 11 ;
29+ const isPlaylistId = id => cleanId ( id ) . length > 11 ;
3830const playlistIds = allIds . filter ( isPlaylistId ) ;
39- const videoIds = allIds . filter ( isVideoId ) ;
31+ const videoIds = allIds . filter ( isVideoId ) ;
32+ const excludedIds = new Set (
33+ readFileSync ( join ( __dirname , '..' , 'yt' , 'exclude.txt' ) , 'utf8' )
34+ . split ( ',' ) . map ( id => id . trim ( ) ) . filter ( Boolean )
35+ ) ;
4036const writeOutput = async ( ) => {
4137 const playlistVideos = { } ;
4238 let playlistErrors = 0 ;
@@ -76,49 +72,60 @@ const writeOutput = async () => {
7672 ...videoIds . map ( cleanId ) ,
7773 ...Object . values ( playlistVideos ) . flat ( )
7874 ] ) ] . filter ( id => ! excludedIds . has ( id ) ) ;
79- const embeddableMap = { } ;
75+ const infoMap = { } ;
8076 let videoErrors = 0 ;
8177 for ( let i = 0 ; i < allCleanIds . length ; i += 50 ) {
8278 const batch = allCleanIds . slice ( i , i + 50 ) ;
8379 try {
8480 const data = await fetchUrl ( buildUrl ( 'https://www.googleapis.com/youtube/v3/videos' , {
85- part : 'status' ,
81+ part : 'status,contentDetails ' ,
8682 id : batch . join ( ',' ) ,
8783 key : KEY
8884 } ) ) ;
8985 if ( data . error ) throw new Error ( data . error . message ) ;
9086 for ( const item of data . items || [ ] ) {
9187 if ( item . status ?. privacyStatus === 'private' ) continue ;
92- embeddableMap [ item . id ] = item . status . embeddable ?? null ;
88+ const durationSeconds = ( ( ) => { const m = ( item . contentDetails ?. duration || 'PT0S' ) . match ( / P T (?: ( \d + ) H ) ? (?: ( \d + ) M ) ? (?: ( \d + ) S ) ? / ) ; return ( ( + m ?. [ 1 ] || 0 ) * 3600 ) + ( ( + m ?. [ 2 ] || 0 ) * 60 ) + ( + m ?. [ 3 ] || 0 ) ; } ) ( ) ;
89+ infoMap [ item . id ] = {
90+ embeddable : item . status . embeddable ?? null , durationSeconds
91+ } ;
9392 }
94- } catch ( e ) {
95- videoErrors ++ ;
96- console . error ( `Videos batch error: ${ e . message } ` ) ;
97- }
93+ } catch ( e ) { videoErrors ++ ; console . error ( `Videos batch error: ${ e . message } ` ) ; }
9894 }
9995 const originalMap = new Map ( allIds . map ( id => [ cleanId ( id ) , id ] ) ) ;
10096 let total = 0 ;
10197 const updated = searchDiv . replace ( / < y - t ( [ ^ > ] * ) > / g, ( _ , attrs ) => {
102- const p = ( attrs . match ( / p = " ( [ ^ " ] * ) " / ) ?. [ 1 ] || '' ) . split ( ',' ) . map ( s => s . trim ( ) ) . filter ( isPlaylistId ) ;
103- const v = ( attrs . match ( / v = " ( [ ^ " ] * ) " / ) ?. [ 1 ] || '' ) . split ( ',' ) . map ( s => s . trim ( ) ) ;
104- const u = ( attrs . match ( / u = " ( [ ^ " ] * ) " / ) ?. [ 1 ] || '' ) . split ( ',' ) . map ( s => s . trim ( ) ) . filter ( Boolean ) ;
105- const fromPlaylists = p . flatMap ( pid => playlistVideos [ cleanId ( pid ) ] || [ ] ) ;
106- const combinedClean = [ ...new Set ( [ ...fromPlaylists , ...v . map ( cleanId ) ] ) ]
107- . filter ( id => ! excludedIds . has ( id ) ) ;
108- const embeddableClean = combinedClean . filter ( id => embeddableMap [ id ] === true ) ;
109- const nonEmbeddableClean = combinedClean . filter ( id => embeddableMap [ id ] === false || embeddableMap [ id ] === null ) ;
98+ const p_match = attrs . match ( / p = " ( [ ^ " ] * ) " / ) ;
99+ const v_match = attrs . match ( / v = " ( [ ^ " ] * ) " / ) ;
100+ const u_match = attrs . match ( / u = " ( [ ^ " ] * ) " / ) ;
101+ const p_split = p_match ? p_match [ 1 ] . split ( ',' ) . map ( s => s . trim ( ) ) . filter ( Boolean ) : [ ] ;
102+ const v_split = v_match ? v_match [ 1 ] . split ( ',' ) . map ( s => s . trim ( ) ) . filter ( Boolean ) : [ ] ;
103+ const u_split = u_match ? u_match [ 1 ] . split ( ',' ) . map ( s => s . trim ( ) ) . filter ( Boolean ) : [ ] ;
104+ const all_attr_ids = [ ...p_split , ...v_split ] ;
105+ const playlist_full = all_attr_ids . filter ( isPlaylistId ) ;
106+ const video_full = all_attr_ids . filter ( isVideoId ) ;
107+ const fromPlaylists = playlist_full . map ( full => playlistVideos [ cleanId ( full ) ] || [ ] ) . flat ( ) ;
108+ const v_clean = video_full . map ( cleanId ) ;
109+ const combinedClean = [ ...new Set ( [ ...fromPlaylists , ...v_clean ] ) ] . filter ( id => ! excludedIds . has ( id ) ) ;
110+ const infoFilter = ( id , condition ) => {
111+ const info = infoMap [ id ] ;
112+ return info && condition ( info . embeddable ) && info . durationSeconds >= 150 ;
113+ } ;
114+ const embeddableClean = combinedClean . filter ( id => infoFilter ( id , emb => emb === true ) ) ;
115+ const nonEmbeddableClean = combinedClean . filter ( id => infoFilter ( id , emb => emb === false || emb === null ) ) ;
110116 total += embeddableClean . length + nonEmbeddableClean . length ;
111117 const embeddableFull = embeddableClean . map ( id => originalMap . get ( id ) || id ) ;
112118 const nonEmbeddableFull = [ ...new Set ( [
113119 ...nonEmbeddableClean . map ( id => originalMap . get ( id ) || id ) ,
114- ...u
115- ] ) ] . filter ( Boolean ) ;
116- let newAttrs = attrs
117- . replace ( / p = " [ ^ " ] * " / g, p . length ? `p="${ p . join ( ',' ) } "` : 'p=""' )
118- . replace ( / v = " [ ^ " ] * " / g, `v="${ embeddableFull . join ( ',' ) } "` )
119- . replace ( / u = " [ ^ " ] * " / g, '' ) ;
120- if ( nonEmbeddableFull . length > 0 ) newAttrs += ` u="${ nonEmbeddableFull . join ( ',' ) } "` ;
121- return `<y-t ${ newAttrs . trim ( ) . replace ( / \s + / g, ' ' ) } >` ;
120+ ...u_split
121+ ] ) ] . filter ( Boolean ) ;
122+ const otherAttrs = attrs . replace ( / [ \s ] * ( p | v | u ) = " [ ^ " ] * " / g, '' ) . trim ( ) ;
123+ const attrParts = [ ] ;
124+ if ( playlist_full . length > 0 ) attrParts . push ( `p="${ playlist_full . join ( ',' ) } "` ) ;
125+ attrParts . push ( `v="${ embeddableFull . join ( ',' ) } "` ) ;
126+ if ( nonEmbeddableFull . length > 0 ) attrParts . push ( `u="${ nonEmbeddableFull . join ( ',' ) } "` ) ;
127+ const newAttrs = [ otherAttrs , ...attrParts ] . filter ( Boolean ) . join ( ' ' ) ;
128+ return `<y-t${ newAttrs ? ' ' + newAttrs . replace ( / \s + / g, ' ' ) : '' } >` ;
122129 } ) ;
123130 writeFileSync ( join ( __dirname , '..' , 'index.html' ) ,
124131 htmlContent
@@ -129,4 +136,4 @@ const writeOutput = async () => {
129136 console . log ( `\nTotal videos processed: ${ total . toLocaleString ( ) } ` ) ;
130137 console . log ( `Errors — playlists: ${ playlistErrors } , videos: ${ videoErrors } ` ) ;
131138} ;
132- writeOutput ( ) . catch ( console . error ) ;
139+ writeOutput ( ) . catch ( console . error ) ;
0 commit comments