1jQuery(document).ready(function($) { 2 var videoInfo = null; 3 4 // Get video info and show preview 5 $('#get-info-button').click(function() { 6 var url = $('#youtube-url').val().trim(); 7 8 if (!url) { 9 showError('Inserisci un URL di YouTube valido'); 10 return; 11 } 12 13 // Clean URL - rimuovi parametri playlist, radio, etc. 14 url = cleanYouTubeUrl(url); 15 16 if (!isValidYouTubeUrl(url)) { 17 showError('URL non valido. Usa un link come: https://www.youtube.com/watch?v=...'); 18 return; 19 } 20 21 $('#video-info').hide(); 22 $('#download-result').html(''); 23 $('#loading-spinner').show(); 24 $('#loading-text').text('Recupero informazioni video...'); 25 26 $.ajax({ 27 url: youtube_downloader_ajax.ajax_url, 28 type: 'POST', 29 timeout: 120000, // 2 minuti per recuperare info 30 data: { 31 action: 'youtube_downloader_get_info', 32 nonce: youtube_downloader_ajax.nonce, 33 url: url 34 }, 35 success: function(response) { 36 $('#loading-spinner').hide(); 37 38 if (response.success) { 39 videoInfo = response.data; 40 displayVideoInfo(videoInfo); 41 $('#video-info').slideDown(); 42 } else { 43 var debugInfo = { 44 message: response.data.message, 45 debug: response.data.debug, 46 response: response 47 }; 48 showError(response.data.message || 'Errore nel recupero delle informazioni', debugInfo); 49 } 50 }, 51 error: function(jqXHR, textStatus, errorThrown) { 52 $('#loading-spinner').hide(); 53 var errorMsg = 'Errore di connessione: ' + textStatus; 54 var debugData = { 55 status: jqXHR.status, 56 statusText: jqXHR.statusText, 57 textStatus: textStatus, 58 errorThrown: errorThrown, 59 responseText: jqXHR.responseText ? jqXHR.responseText.substring(0, 500) : 'empty' 60 }; 61 62 if (jqXHR.responseText) { 63 try { 64 var errorData = JSON.parse(jqXHR.responseText); 65 if (errorData.data && errorData.data.message) { 66 errorMsg = errorData.data.message; 67 } 68 debugData.parsedResponse = errorData; 69 } catch(e) { 70 debugData.parseError = e.message; 71 } 72 } 73 showError(errorMsg, debugData); 74 } 75 }); 76 }); 77 78 // Handle Enter key on URL input 79 $('#youtube-url').keypress(function(e) { 80 if (e.which === 13) { 81 e.preventDefault(); 82 $('#get-info-button').click(); 83 } 84 }); 85 86 // Download video/audio 87 $('#download-button').click(function() { 88 var url = $('#youtube-url').val().trim(); 89 var format = $('#format-select').val(); 90 var quality = $('#quality-select').val(); 91 92 if (!url) { 93 showError('URL mancante'); 94 return; 95 } 96 97 // Clean URL - rimuovi parametri playlist, radio, etc. 98 url = cleanYouTubeUrl(url); 99 100 $('#download-result').html(''); 101 $('#loading-spinner').show(); 102 $('#loading-text').text('Preparazione download in corso...'); 103 104 $.ajax({ 105 url: youtube_downloader_ajax.ajax_url, 106 type: 'POST', 107 timeout: 300000, // 5 minuti per il download 108 data: { 109 action: 'youtube_downloader', 110 nonce: youtube_downloader_ajax.nonce, 111 url: url, 112 format: format, 113 quality: quality 114 }, 115 success: function(response) { 116 $('#loading-spinner').hide(); 117 118 if (response.success) { 119 var downloadUrl = response.data.download_url; 120 121 // Forza HTTPS per evitare blocchi mixed content 122 if (downloadUrl.indexOf('http://') === 0) { 123 downloadUrl = downloadUrl.replace('http://', 'https://'); 124 } 125 126 var filename = response.data.filename || 'download'; 127 var filesize = response.data.filesize ? formatFileSize(response.data.filesize) : ''; 128 var cached = response.data.cached; 129 130 var resultHtml = '<div class="ytd-success">'; 131 resultHtml += '<div class="ytd-success-icon">â</div>'; 132 resultHtml += '<h4>File Pronto per il Download!</h4>'; 133 if (filesize) { 134 resultHtml += '<p class="ytd-filesize">Dimensione: ' + filesize + '</p>'; 135 } 136 if (cached) { 137 resultHtml += '<p class="ytd-cached-badge">⨠Link dalla cache</p>'; 138 } 139 resultHtml += '<a href="' + downloadUrl + '" class="ytd-download-btn" id="auto-download-trigger">'; 140 resultHtml += '<span class="ytd-btn-icon">â¬</span> Scarica Ora'; 141 resultHtml += '</a>'; 142 resultHtml += '<p class="ytd-expire-notice">â± Il link scade tra 30 minuti</p>'; 143 resultHtml += '</div>'; 144 145 $('#download-result').html(resultHtml); 146 147 // Trigger automatico del click sul link 148 setTimeout(function() { 149 $('#auto-download-trigger')[0].click(); 150 }, 300); 151 } else { 152 showError(response.data.message || 'Errore nel download'); 153 } 154 }, 155 error: function(jqXHR, textStatus, errorThrown) { 156 $('#loading-spinner').hide(); 157 var errorMsg = 'Errore di connessione: ' + textStatus; 158 var debugData = { 159 status: jqXHR.status, 160 statusText: jqXHR.statusText, 161 textStatus: textStatus, 162 errorThrown: errorThrown, 163 responseText: jqXHR.responseText ? jqXHR.responseText.substring(0, 500) : 'empty' 164 }; 165 166 if (jqXHR.responseText) { 167 try { 168 var errorData = JSON.parse(jqXHR.responseText); 169 if (errorData.data && errorData.data.message) { 170 errorMsg = errorData.data.message; 171 } 172 debugData.parsedResponse = errorData; 173 } catch(e) { 174 debugData.parseError = e.message; 175 } 176 } 177 showError(errorMsg, debugData); 178 } 179 }); 180 }); 181 182 // Toggle quality selector based on format 183 $('#format-select').change(function() { 184 var format = $(this).val(); 185 if (format === 'audio') {
186 $('#quality-group').slideUp(); 187 } else { 188 $('#quality-group').slideDown(); 189 } 190 }); 191 192 // Display video information 193 function displayVideoInfo(info) { 194 $('#video-thumbnail').attr('src', info.thumbnail || ''); 195 $('#video-title').text(info.title || 'Titolo non disponibile'); 196 $('#video-uploader').html('<strong>Canale:</strong> ' + (info.uploader || 'Sconosciuto')); 197 198 var duration = info.duration ? formatDuration(info.duration) : 'N/A'; 199 $('#video-duration').html('<strong>Durata:</strong> ' + duration); 200 201 // Set default format and quality from shortcode attributes 202 var container = $('#youtube-downloader-form'); 203 var defaultFormat = container.data('default-format') || 'best'; 204 var defaultQuality = container.data('default-quality') || 'best'; 205 206 $('#format-select').val(defaultFormat); 207 $('#quality-select').val(defaultQuality); 208 209 if (defaultFormat === 'audio') { 210 $('#quality-group').hide(); 211 } 212 } 213 214 // Show error message 215 function showError(message, debugData) { 216 var errorHtml = '<div class="ytd-error">'; 217 errorHtml += '<div class="ytd-error-icon">â </div>'; 218 errorHtml += '<p>' + message + '</p>'; 219 220 // Show debug info if available 221 if (debugData && typeof debugData === 'object') { 222 errorHtml += '<details style="margin-top: 15px; text-align: left;"><summary style="cursor: pointer; color: #666;">Debug Info</summary>'; 223 errorHtml += '<pre style="background: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto; font-size: 12px;">'; 224 errorHtml += JSON.stringify(debugData, null, 2); 225 errorHtml += '</pre></details>'; 226 } 227 228 errorHtml += '</div>'; 229 $('#download-result').html(errorHtml); 230 } 231 232 // Clean YouTube URL - rimuovi parametri playlist, radio, etc. 233 function cleanYouTubeUrl(url) { 234 // Estrai solo il video ID e ricostruisci URL pulito 235 var videoId = extractVideoId(url); 236 if (videoId) { 237 return 'https://www.youtube.com/watch?v=' + videoId; 238 } 239 return url; 240 } 241 242 // Estrai video ID da URL YouTube 243 function extractVideoId(url) { 244 // Pattern per diversi formati YouTube 245 var patterns = [ 246 /[?&]v=([^&#]+)/, // watch?v=VIDEO_ID o watch?v=VIDEO_ID&list=... 247 /youtu\.be\/([^?&#]+)/, // youtu.be/VIDEO_ID 248 /embed\/([^?&#]+)/, // youtube.com/embed/VIDEO_ID 249 /v\/([^?&#]+)/ // youtube.com/v/VIDEO_ID 250 ]; 251 252 for (var i = 0; i < patterns.length; i++) { 253 var match = url.match(patterns[i]); 254 if (match && match[1]) { 255 return match[1]; 256 } 257 } 258 return null; 259 } 260 261 // Validate YouTube URL 262 function isValidYouTubeUrl(url) { 263 var pattern = /^(https?:\/\/)?(www\.)?(youtube\.com\/(watch\?v=|embed\/|v\/)|youtu\.be\/)[\w-]+/; 264 return pattern.test(url); 265 } 266 267 // Format duration from seconds 268 function formatDuration(seconds) { 269 var hours = Math.floor(seconds / 3600); 270 var minutes = Math.floor((seconds % 3600) / 60); 271 var secs = seconds % 60; 272 273 if (hours > 0) { 274 return hours + 'h ' + minutes + 'm ' + secs + 's'; 275 } else if (minutes > 0) { 276 return minutes + 'm ' + secs + 's'; 277 } else { 278 return secs + 's'; 279 } 280 } 281 282 // Format file size 283 function formatFileSize(bytes) { 284 if (bytes === 0) return '0 Bytes'; 285 var k = 1024; 286 var sizes = ['Bytes', 'KB', 'MB', 'GB']; 287 var i = Math.floor(Math.log(bytes) / Math.log(k)); 288 return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; 289 } 290});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.