// ---- Helper Functions ---- function showNotification(message, type = 'info') { toastr.options = { closeButton: true, newestOnTop: true, progressBar: true, positionClass: "toast-top-center", preventDuplicates: true, onclick: null, showDuration: "1000", hideDuration: "3000", timeOut: "10000", extendedTimeOut: "1000", showEasing: "swing", hideEasing: "linear", showMethod: "fadeIn", hideMethod: "fadeOut", }; toastr[type](message); } function addGenre() { const genreValue = document.getElementById('genreInput').value.trim(); if (!genreValue) { showNotification('Please enter a valid genre.', 'error'); return; } const genreIndex = document.getElementById('genreInputs').children.length; const genreHtml = `
${genreValue}
`; document.getElementById('genreInputs').insertAdjacentHTML('beforeend', genreHtml); document.getElementById('genreInput').value = ''; // Clear input for next genre } function removeGenre(genreId) { document.getElementById(genreId).remove(); } function addStreamInput() { const streamIndex = document.getElementById('streamInputs').children.length; // Adjusted HTML to include 'Optional' guidance in labels const streamInputHtml = `
Stream #${streamIndex + 1}
`; document.getElementById('streamInputs').insertAdjacentHTML('beforeend', streamInputHtml); } // Function to toggle the disabled state of one input based on the value of another function toggleField(targetId, sourceValue) { const targetField = document.getElementById(targetId); targetField.disabled = !!sourceValue; } function removeStreamInput(streamId) { document.getElementById(streamId).remove(); } function setElementDisplay(elementId, displayStatus) { document.getElementById(elementId).style.display = displayStatus; } function resetButton(submitBtn, loadingSpinner) { submitBtn.disabled = false; loadingSpinner.style.display = 'none'; } document.querySelectorAll('input[name="m3uInputType"]').forEach(input => { input.addEventListener('change', function () { if (this.value === 'url') { setElementDisplay('m3uPlaylistUrlInput', ''); setElementDisplay('m3uPlaylistFileInput', 'none'); document.getElementById('m3uPlaylistFile').value = ''; // Clear file input } else { setElementDisplay('m3uPlaylistUrlInput', 'none'); setElementDisplay('m3uPlaylistFileInput', ''); document.getElementById('m3uPlaylistUrl').value = ''; // Clear URL input } }); }); function toggleSpiderSpecificFields() { const spiderName = document.getElementById('spiderName').value; const isTamil = spiderName === 'tamilmv' || spiderName === 'tamil_blasters'; const isOtherSpider = !isTamil; // Display the specific options for TamilMV and TamilBlasters setElementDisplay('tamilmvTamilblastersParams', isTamil ? 'block' : 'none'); setElementDisplay('scrapeAllOption', isOtherSpider ? 'block' : 'none'); // Initially set to page scraping if (isTamil) { document.querySelector('input[name="mode"][value="page_scraping"]').checked = true; toggleModeSpecificFields(); } // Reset fields when changing mode document.getElementById('keyword').value = ''; document.getElementById('pages').value = '1'; document.getElementById('startPage').value = '1'; document.getElementById('scrape_all').checked = false; } function setupPasswordToggle(passwordInputId, toggleButtonId, toggleIconId) { document.getElementById(toggleButtonId).addEventListener('click', function (_) { const passwordInput = document.getElementById(passwordInputId); const passwordIcon = document.getElementById(toggleIconId); if (passwordInput.type === "password") { passwordInput.type = "text"; passwordIcon.className = "bi bi-eye-slash"; } else { passwordInput.type = "password"; passwordIcon.className = "bi bi-eye"; } }); } function toggleModeSpecificFields() { const selectedMode = document.querySelector('input[name="mode"]:checked').value; const displayKeywordSearch = selectedMode === 'keyword_search'; const displayPageScraping = selectedMode === 'page_scraping'; // Display the appropriate input fields setElementDisplay('keywordSearchInput', displayKeywordSearch ? 'block' : 'none'); setElementDisplay('pageScrapingInput', displayPageScraping ? 'block' : 'none'); } function showConfirmationDialog(validationErrors, torrentData, infoHash) { return new Promise((resolve) => { // Remove any existing modal const existingModal = document.getElementById('confirmationModal'); if (existingModal) { existingModal.remove(); } const errorMessages = validationErrors.map(error => `
  • ${error.message}
  • `).join(''); const modalHtml = ` `; // Add modal to document document.body.insertAdjacentHTML('beforeend', modalHtml); const modalElement = document.getElementById('confirmationModal'); const modal = new bootstrap.Modal(modalElement); // Enable/disable confirm button based on checkbox document.getElementById('confirmGuidelines').addEventListener('change', (e) => { document.getElementById('confirmImport').disabled = !e.target.checked; }); // Handle confirmation button click document.getElementById('confirmImport').addEventListener('click', () => { if (document.getElementById('confirmGuidelines').checked) { modal.hide(); resolve(true); } }); // Handle modal hidden event modalElement.addEventListener('hidden.bs.modal', () => { modal.dispose(); modalElement.remove(); resolve(false); }); modal.show(); }); } function updateMetaType() { const metaType = document.getElementById('metaType').value; if (metaType === 'movie') { setElementDisplay('catalogsSeries', 'none'); setElementDisplay('catalogsMovie', 'block'); setElementDisplay('seriesParameters', 'none'); } else { setElementDisplay('catalogsMovie', 'none'); setElementDisplay('catalogsSeries', 'block'); setElementDisplay('seriesParameters', 'block'); } } function toggleInput(disableId, input) { document.getElementById(disableId).disabled = !!input.value; } // Function to update form fields based on scraper selection function updateFormFields() { // Hide all sections initially setElementDisplay('scrapyParameters', 'none'); setElementDisplay('tvMetadataInput', 'none'); setElementDisplay('m3uPlaylistInput', 'none'); setElementDisplay("imdbDataParameters", "none"); setElementDisplay("torrentUploadParameters", "none"); setElementDisplay("apiPasswordContainer", "none"); setElementDisplay('blockTorrentParameters', 'none'); // Get the selected scraper type const scraperType = document.getElementById('scraperSelect').value; let authRequired = document.getElementById('apiPasswordEnabled').value === "true"; // Show the relevant section based on the selected scraper type switch (scraperType) { case 'add_torrent': setElementDisplay("torrentUploadParameters", "block"); authRequired = false; updateMetaType(); break; case 'scrapy': // Show Scrapy-specific parameters setElementDisplay('scrapyParameters', 'block'); toggleSpiderSpecificFields(); authRequired = false; break; case 'add_tv_metadata': // Show TV Metadata input form setElementDisplay('tvMetadataInput', 'block'); // Ensure a stream input is displayed initially if (document.getElementById('streamInputs').children.length === 0) { addStreamInput(); } break; case 'add_m3u_playlist': setElementDisplay('m3uPlaylistInput', 'block'); break; case 'update_imdb_data': setElementDisplay("imdbDataParameters", "block"); authRequired = false; break; case 'block_torrent': setElementDisplay("blockTorrentParameters", "block"); break; default: // Optionally handle any default cases if needed break; } // Setup password toggle if the API password field is displayed if (authRequired) { setElementDisplay("apiPasswordContainer", "block"); setupPasswordToggle('api_password', 'toggleApiPassword', 'toggleApiPasswordIcon'); } } function constructTvMetadata() { // Basic TV Metadata collection let tvMetaData = { title: document.getElementById('title').value.trim(), poster: document.getElementById('poster').value.trim(), background: document.getElementById('background').value.trim(), country: document.getElementById('country').value.trim(), tv_language: document.getElementById('language').value.trim(), logo: document.getElementById('logo').value.trim(), genres: [], streams: [] }; // Collecting Genres document.querySelectorAll('#genreInputs div span').forEach(genre => { tvMetaData.genres.push(genre.textContent); }); // Validating and Collecting Streams const streamContainers = document.querySelectorAll('#streamInputs .card'); streamContainers.forEach((container, index) => { console.log(`streamName-${index}`); let stream = { name: document.getElementById(`streamName-${index}`).value.trim(), url: document.getElementById(`streamUrl-${index}`).value.trim(), ytId: document.getElementById(`streamYtId-${index}`).value.trim(), source: document.getElementById(`streamSource-${index}`).value.trim(), country: document.getElementById(`streamCountry-${index}`).value.trim(), drm_key_id: document.getElementById(`streamDrmKeyId-${index}`).value.trim(), drm_key: document.getElementById(`streamDrmKey-${index}`).value.trim(), behaviorHints: { proxyHeaders: document.getElementById(`streamProxyHeaders-${index}`) ? JSON.parse(document.getElementById(`streamProxyHeaders-${index}`).value || "{}") : {}, notWebReady: true } }; // Basic validation to ensure at least one of URL or YouTube ID is provided if (!stream.url && !stream.ytId) { throw new Error(`Stream #${index + 1}: Either a URL or a YouTube ID is required.`); } tvMetaData.streams.push(stream); }); // Additional validation logic as necessary if (tvMetaData.title === '') { throw new Error('Title is required.'); } // Assuming at least one stream is required if (tvMetaData.streams.length === 0) { throw new Error('At least one stream is required.'); } return tvMetaData; } async function handleAddTorrent(submitBtn, loadingSpinner, forceImport = false) { let formData = new FormData(); const imdbId = document.getElementById('torrentImdbId').value; const metaType = document.getElementById('metaType').value; // Basic validation const imdbIdNumeric = parseInt(imdbId.slice(2), 10); if (!imdbId.startsWith('tt') || imdbId.length < 3 || imdbId.length > 10 || isNaN(imdbIdNumeric)) { showNotification('Invalid IMDb ID', 'error'); resetButton(submitBtn, loadingSpinner); return; } formData.append('meta_id', imdbId); formData.append('meta_type', metaType); // Handle optional catalogs const catalogInputs = metaType === 'movie' ? document.querySelectorAll('#catalogsMovie input[name="catalogs"]:checked') : document.querySelectorAll('#catalogsSeries input[name="catalogs"]:checked'); const catalogs = Array.from(catalogInputs).map(el => el.value); if (catalogs.length > 0) { formData.append('catalogs', catalogs.join(',')); } // Handle languages const selectedLanguages = Array.from(document.querySelectorAll('input[name="languages"]:checked')) .map(el => el.value); if (selectedLanguages.length > 0) { formData.append('languages', selectedLanguages.join(',')); } const createdAt = document.getElementById('createdAt').value; if (!createdAt) { showNotification('Created At is required.', 'error'); resetButton(submitBtn, loadingSpinner); return; } formData.append('created_at', createdAt); if (metaType === 'series') { const season = document.getElementById('season').value; let episodes = document.getElementById('episodes').value; if (!season || !episodes) { showNotification('Season and Episodes are required for TV Series.', 'error'); resetButton(submitBtn, loadingSpinner); return; } if (episodes.includes('-')) { const [start, end] = episodes.split('-'); episodes = Array.from({length: end - start + 1}, (_, i) => parseInt(start) + i).join(','); } else { episodes = episodes.split(',').map(e => e.trim()); } formData.append('season', season); formData.append('episodes', episodes); } const magnetLink = document.getElementById('magnetLink').value; const torrentFile = document.getElementById('torrentFile').files[0]; if (!magnetLink && !torrentFile) { showNotification('Either Magnet Link or Torrent File is required.', 'error'); resetButton(submitBtn, loadingSpinner); return; } if (magnetLink) { formData.append('magnet_link', magnetLink); } else { formData.append('torrent_file', torrentFile); } // Add force import flag if needed if (forceImport) { formData.append('force_import', 'true'); } try { const response = await fetch('/scraper/torrent', { method: 'POST', body: formData }); const data = await response.json(); if (data.status === 'validation_failed' && !forceImport) { // Show confirmation dialog const shouldForceImport = await showConfirmationDialog( data.errors, data.torrent_data, data.info_hash ); if (shouldForceImport) { // Retry with force import submitBtn.disabled = true; loadingSpinner.style.display = 'inline-block'; await handleAddTorrent(submitBtn, loadingSpinner, true); return; } } else if (data.detail) { showNotification(data.detail, 'error'); } else { showNotification(data.status, 'success'); } } catch (error) { console.error('Error submitting scraper form:', error); showNotification(`Error submitting scraper form. Error: ${error.toString()}`, 'error'); } finally { resetButton(submitBtn, loadingSpinner); } } async function handleAddTvMetadata(payload, submitBtn, loadingSpinner) { try { payload['tv_metadata'] = constructTvMetadata(); const response = await fetch('/scraper/add_tv_metadata', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }); const data = await response.json(); if (data.detail) { showNotification(data.detail, 'error'); } else { showNotification(data.status, 'success'); } } catch (error) { console.error('Error constructing TV Metadata:', error); showNotification(error.message, 'error'); } finally { resetButton(submitBtn, loadingSpinner); } } async function handleAddM3uPlaylist(apiPassword, submitBtn, loadingSpinner) { let formData = new FormData(); formData.append('scraper_type', 'add_m3u_playlist'); formData.append('api_password', apiPassword); formData.append('m3u_playlist_source', document.getElementById('m3uPlaylistSource').value); const inputType = document.querySelector('input[name="m3uInputType"]:checked').value; if (inputType === 'url') { const m3uUrl = document.getElementById('m3uPlaylistUrl').value.trim(); if (!m3uUrl) { showNotification('M3U Playlist URL is required.', 'error'); resetButton(submitBtn, loadingSpinner); return; } formData.append('m3u_playlist_url', m3uUrl); } else { const file = document.getElementById('m3uPlaylistFile').files[0]; if (!file) { showNotification('M3U Playlist file is required.', 'error'); resetButton(submitBtn, loadingSpinner); return; } formData.append('m3u_playlist_file', file); } try { const response = await fetch('/scraper/m3u_upload', { method: 'POST', body: formData }); const data = await response.json(); if (data.detail) { showNotification(data.detail, 'error'); } else { showNotification(data.status, 'success'); } } catch (error) { console.error('Error submitting scraper form:', error); showNotification(`Error submitting scraper form. Error: ${error.stringify()}`, 'error'); } finally { resetButton(submitBtn, loadingSpinner); } } async function handleUpdateImdbData(submitBtn, loadingSpinner) { const imdbId = document.getElementById('imdbId').value; const imdbIdNumeric = parseInt(imdbId.slice(2), 10); if (!imdbId.startsWith('tt') || imdbId.length < 3 || imdbId.length > 10 || isNaN(imdbIdNumeric)) { showNotification('Invalid IMDb ID', 'error'); resetButton(submitBtn, loadingSpinner); return; } const imdbType = document.getElementById('imdbType').value; try { const response = await fetch(`/scraper/imdb_data?meta_id=${imdbId}&media_type=${imdbType}`, { method: 'GET', headers: {'Content-Type': 'application/json'} }); const data = await response.json(); if (data.detail) { showNotification(data.detail, 'error'); } else { showNotification(data.status, 'success'); } } catch (error) { console.error('Error submitting scraper form:', error); showNotification(`Error submitting scraper form. Error: ${error.stringify()}`, 'error'); } finally { resetButton(submitBtn, loadingSpinner); } } async function handleScrapyParameters(payload, submitBtn, loadingSpinner) { document.querySelectorAll('#scrapyParameters input, #scrapyParameters select').forEach(input => { if (!input.disabled && input.type !== 'radio' && input.type !== 'checkbox') { payload[input.name] = input.value; } else if (input.type === 'radio' && input.checked) { payload[input.name] = input.value; } else if (input.type === 'checkbox') { payload[input.name] = input.checked; } }); try { const response = await fetch('/scraper/run', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload) }); const data = await response.json(); if (data.detail) { showNotification(data.detail, 'error'); } else { showNotification(data.status, 'success'); } } catch (error) { console.error('Error submitting scraper form:', error); showNotification(`Error submitting scraper form. Error: ${error.stringify()}`, 'error'); } finally { resetButton(submitBtn, loadingSpinner); } } async function handleBlockTorrent(apiPassword, submitBtn, loadingSpinner) { const infoHash = document.getElementById('blockTorrentInfoHash').value.trim(); if (!infoHash) { showNotification('Torrent Info Hash is required.', 'error'); resetButton(submitBtn, loadingSpinner); return; } try { const response = await fetch('/scraper/block_torrent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ info_hash: infoHash, api_password: apiPassword }) }); const data = await response.json(); if (data.detail) { showNotification(data.detail, 'error'); } else { showNotification(data.status, 'success'); } } catch (error) { console.error('Error blocking torrent:', error); showNotification(`Error blocking torrent. Error: ${error.toString()}`, 'error'); } finally { resetButton(submitBtn, loadingSpinner); } } // Main function async function submitScraperForm() { const apiPassword = document.getElementById('api_password').value; const scraperType = document.getElementById('scraperSelect').value; const submitBtn = document.getElementById('submitBtn'); const loadingSpinner = document.getElementById('loadingSpinner'); let payload = {scraper_type: scraperType, api_password: apiPassword}; // Disable button and show loading spinner submitBtn.disabled = true; loadingSpinner.style.display = 'inline-block'; // Call the appropriate handler based on scraper type switch (scraperType) { case 'add_torrent': await handleAddTorrent(submitBtn, loadingSpinner); break; case 'add_tv_metadata': await handleAddTvMetadata(payload, submitBtn, loadingSpinner); break; case 'add_m3u_playlist': await handleAddM3uPlaylist(apiPassword, submitBtn, loadingSpinner); break; case 'update_imdb_data': await handleUpdateImdbData(submitBtn, loadingSpinner); break; case 'block_torrent': await handleBlockTorrent(apiPassword, submitBtn, loadingSpinner); break; default: await handleScrapyParameters(payload, submitBtn, loadingSpinner); break; } } // Initial update for form fields on page load document.addEventListener('DOMContentLoaded', updateFormFields); document.getElementById('spiderName').addEventListener('change', toggleSpiderSpecificFields);