`;
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('totalPages').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 = `
Validation Warning
Community Guidelines:
Do not upload adult or inappropriate content
Only upload content that matches the IMDb title
Avoid spamming
Validation issues found:
${errorMessages}
Do you want to proceed with the import?
`;
// 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 updateContentType() {
const metaType = document.getElementById('metaType').value;
// Hide all catalog sections first
setElementDisplay('movieCatalogs', 'none');
setElementDisplay('seriesCatalogs', 'none');
setElementDisplay('sportsCatalogs', 'none');
// Show relevant catalog section based on content type
if (metaType === 'movie') {
setElementDisplay('movieCatalogs', 'block');
setElementDisplay('metaIdContainer', 'block');
} else if (metaType === 'series') {
setElementDisplay('seriesCatalogs', 'block');
setElementDisplay('metaIdContainer', 'block');
} else if (metaType === 'sports') {
setElementDisplay('sportsCatalogs', 'block');
setElementDisplay('metaIdContainer', 'none');
}
}
function parseSeasonsInput(input) {
const seasons = [];
const parts = input.split(',');
for (const part of parts) {
if (part.includes('-')) {
const [start, end] = part.split('-').map(num => parseInt(num.trim()));
for (let i = start; i <= end; i++) {
seasons.push(i);
}
} else {
const season = parseInt(part.trim());
if (!isNaN(season)) {
seasons.push(season);
}
}
}
return seasons;
}
function showFileAnnotationModal(files) {
const modal = document.getElementById('fileAnnotationModal');
const fileList = document.getElementById('fileAnnotationList');
fileList.innerHTML = '';
const isSportsContent = document.getElementById('metaType').value === 'sports';
// Sort files by filename
files.sort((a, b) => {
return a.filename.localeCompare(b.filename, undefined, {
numeric: true,
sensitivity: 'base'
});
});
files.forEach((file, index) => {
const fileRow = `
${file.filename}
${isSportsContent ? `
` : ''}
`;
fileList.insertAdjacentHTML('beforeend', fileRow);
});
files.forEach((file, index) => {
// Initialize date picker for each file
setupDateInput(`release-${index}`, false);
});
const bsModal = new bootstrap.Modal(modal);
bsModal.show();
return new Promise((resolve, reject) => {
document.getElementById('confirmAnnotation').onclick = () => {
const annotatedFiles = [];
files.forEach((file, index) => {
// Only include files that are marked for inclusion
if (document.getElementById(`include-file-${index}`)?.checked) {
const baseData = {
...file,
season_number: parseInt(document.getElementById(`season-${index}`).value) || null,
episode_number: parseInt(document.getElementById(`episode-${index}`).value) || null,
};
if (isSportsContent) {
const releaseDate = document.getElementById(`release-${index}`).value;
if (releaseDate) {
const dateParts = releaseDate.split('/');
const date = new Date(`${dateParts[1]}/${dateParts[0]}/${dateParts[2]}`);
baseData.release_date = date.toISOString().split('T')[0]
}
annotatedFiles.push({
...baseData,
title: document.getElementById(`title-${index}`).value || null,
overview: document.getElementById(`overview-${index}`).value || null,
thumbnail: document.getElementById(`thumbnail-${index}`).value || null,
});
} else {
annotatedFiles.push(baseData);
}
}
});
bsModal.hide();
resolve(annotatedFiles);
};
modal.addEventListener('hidden.bs.modal', () => {
reject(new Error('Annotation cancelled'));
}, {once: true});
});
}
// Function to apply episode numbering starting from a specific index
function applyEpisodeNumberingFrom(startIndex) {
const startEpisodeNumber = 1;
const resetOnSeasonChange = true;
// Get all included file rows
const fileRows = Array.from(document.querySelectorAll('.card[id^="file-row-"]'));
// Filter rows starting from the specified index
const relevantFiles = fileRows.slice(fileRows.findIndex(row => row.id === `file-row-${startIndex}`));
let episodeCounter = startEpisodeNumber;
let lastSeason = null;
// Clear any previous highlights
document.querySelectorAll('.card[id^="file-row-"]').forEach(row => {
row.classList.remove('numbered-file');
});
// Apply episode numbers with visual feedback
relevantFiles.forEach((fileRow, idx) => {
const index = fileRow.id.split('-')[2];
// Skip excluded files
if (!document.getElementById(`include-file-${index}`)?.checked) {
return;
}
// Get the season input for this file
const seasonInput = document.getElementById(`season-${index}`);
const currentSeason = parseInt(seasonInput.value) || null;
// Reset episode counter if season changes and reset option is enabled
if (resetOnSeasonChange && lastSeason !== null && currentSeason !== null && currentSeason !== lastSeason) {
episodeCounter = 1;
}
// Set the episode number
document.getElementById(`episode-${index}`).value = episodeCounter++;
// Store the current season for next iteration
if (currentSeason !== null) {
lastSeason = currentSeason;
}
// Add visual highlight with slight delay for visual effect
setTimeout(() => {
fileRow.classList.add('numbered-file');
// Remove highlight after 1.5 seconds
setTimeout(() => {
fileRow.classList.remove('numbered-file');
}, 1500);
}, idx * 50);
});
showNotification('Episode numbering applied successfully', 'success');
}
// Function to apply season numbering starting from a specific index
function applySeasonNumberingFrom(startIndex) {
// Get all file rows
const fileRows = Array.from(document.querySelectorAll('.card[id^="file-row-"]'));
// Get the starting season number from the user
const startSeasonNumber = parseInt(document.getElementById(`season-${startIndex}`).value) || parseInt(prompt("Enter starting season number:", "1")) || 1;
// Filter rows starting from the specified index
const relevantFiles = fileRows.slice(fileRows.findIndex(row => row.id === `file-row-${startIndex}`));
let seasonCounter = startSeasonNumber;
// Clear any previous highlights
document.querySelectorAll('.card[id^="file-row-"]').forEach(row => {
row.classList.remove('numbered-file');
});
// Apply season numbers with visual feedback
relevantFiles.forEach((fileRow, idx) => {
const index = fileRow.id.split('-')[2];
// Skip excluded files
if (!document.getElementById(`include-file-${index}`)?.checked) {
return;
}
// Set the season number
document.getElementById(`season-${index}`).value = seasonCounter;
// Add visual highlight with slight delay for visual effect
setTimeout(() => {
fileRow.classList.add('numbered-file');
// Remove highlight after 1.5 seconds
setTimeout(() => {
fileRow.classList.remove('numbered-file');
}, 1500);
}, idx * 50);
});
showNotification('Season numbering applied successfully', 'success');
}
// Function to toggle file inclusion
function toggleFileInclusion(index) {
const checkbox = document.getElementById(`include-file-${index}`);
const inputsContainer = document.getElementById(`file-inputs-${index}`);
const fileRow = document.getElementById(`file-row-${index}`);
const episodeMetadata = document.getElementById(`episode-metadata-${index}`);
if (checkbox.checked) {
// File is included
inputsContainer.style.opacity = '1';
inputsContainer.style.pointerEvents = 'auto';
fileRow.classList.remove('excluded-file');
// Enable all inputs in the container
inputsContainer.querySelectorAll('input, textarea').forEach(input => {
input.disabled = false;
});
episodeMetadata.style.opacity = '1';
episodeMetadata.style.pointerEvents = 'auto';
episodeMetadata.querySelector('input, textarea').forEach(input => {
input.disabled = false;
});
} else {
// File is excluded
inputsContainer.style.opacity = '0.5';
inputsContainer.style.pointerEvents = 'none';
fileRow.classList.add('excluded-file');
// Disable all inputs in the container
inputsContainer.querySelectorAll('input, textarea').forEach(input => {
input.disabled = true;
});
episodeMetadata.style.opacity = '0.5';
episodeMetadata.style.pointerEvents = 'none';
episodeMetadata.querySelector('input, textarea').forEach(input => {
input.disabled = true;
});
}
}
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('quickImportParameters', 'none');
setElementDisplay('scrapyParameters', 'none');
setElementDisplay('tvMetadataInput', 'none');
setElementDisplay('m3uPlaylistInput', 'none');
setElementDisplay("imdbDataParameters", "none");
setElementDisplay("torrentUploadParameters", "none");
setElementDisplay("apiPasswordContainer", "none");
setElementDisplay('blockTorrentParameters', 'none');
setElementDisplay('migrationParameters', 'none');
setElementDisplay('updateImagesParameters', '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 'quick_import':
setElementDisplay('quickImportParameters', 'block');
authRequired = false;
break;
case 'add_torrent':
setElementDisplay("torrentUploadParameters", "block");
authRequired = false;
updateContentType();
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;
case 'migrate_id':
setElementDisplay('migrationParameters', 'block');
authRequired = false;
break;
case 'update_images':
setElementDisplay('updateImagesParameters', 'block');
authRequired = false;
break;
}
// Setup password toggle if the API password field is displayed
if (authRequired) {
setElementDisplay("apiPasswordContainer", "block");
setupPasswordToggle('api_password', 'toggleApiPassword', 'toggleApiPasswordIcon');
}
}
function handleInitialSetup() {
const urlParams = new URLSearchParams(window.location.search);
const action = urlParams.get('action');
// Set initial scraper type based on action
if (action) document.getElementById('scraperSelect').value = action;
// Update form fields based on initial selection
updateFormFields();
}
function setupDateInput(inputId, defaultToToday = false, initialDate = null) {
const fp = flatpickr(`#${inputId}`, {
dateFormat: "d/m/Y",
altInput: true,
altFormat: "d/m/Y",
allowInput: true,
defaultHour: 12,
maxDate: "today",
locale: {
firstDayOfWeek: 1
},
theme: "dark",
defaultDate: initialDate // Set initial date if provided
});
if (defaultToToday && !initialDate) {
const today = new Date();
fp.setDate(today);
}
}
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 handleQuickImport() {
const metaType = document.getElementById('quickMetaType').value;
const torrentFile = document.getElementById('quickTorrentFile').files[0];
const magnetLink = document.getElementById('quickMagnetLink').value.trim();
// Store the uploader in localStorage for persistence
localStorage.setItem('quickUploaderName', document.getElementById('quickUploaderName').value);
if (!torrentFile && !magnetLink) {
showNotification('Please provide either a torrent file or magnet link', 'error');
return;
}
const formData = new FormData();
formData.append('meta_type', metaType);
if (torrentFile) {
formData.append('torrent_file', torrentFile);
} else {
formData.append('magnet_link', magnetLink);
}
try {
setElementDisplay('analysisLoading', 'block');
setElementDisplay('matchResults', 'none');
const response = await fetch('/scraper/analyze_torrent', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
if (data.matches && data.matches.length > 0) {
displayMatchResults(data.matches, data.torrent_data);
} else {
showNotification('No matches found. Try manual import.', 'warning');
switchToManualImport(data.torrent_data);
}
} else {
showNotification(data.detail || 'Failed to analyze torrent', 'error');
}
} catch (error) {
showNotification('Error analyzing torrent: ' + error.message, 'error');
} finally {
setElementDisplay('analysisLoading', 'none');
}
}
function displayMatchResults(matches, torrentData) {
const container = document.getElementById('matchResultsContent');
container.innerHTML = '';
if (matches && matches.length > 0) {
matches.forEach(match => {
const safeMatchData = btoa(encodeURIComponent(JSON.stringify(match)));
const safeTorrentData = btoa(encodeURIComponent(JSON.stringify(torrentData)));
// Format runtime nicely
const runtime = match.runtime ? match.runtime.replace('min', '').trim() + ' minutes' : 'N/A';
// Format rating with stars
const rating = match.imdb_rating ?
` ${match.imdb_rating}/10` :
'No rating';
// Handle multiple AKA titles
const akaTitles = match.aka_titles && match.aka_titles.length > 0 ?
`