diff --git a/autoindex.js b/autoindex.js index 5bb86bf..4ffc7c7 100644 --- a/autoindex.js +++ b/autoindex.js @@ -19,7 +19,7 @@ const arrow = ` -` +`; function reencodeURI(uri) { return encodeURIComponent(decodeURIComponent(uri)); @@ -35,10 +35,17 @@ function findByHref(href) { }); } +function findWholeNumberInText(text, matchNumber) { + const numbers = text.match(/\d+/g); + return numbers + && numbers.length + && numbers.map((x) => parseInt(x, 10)).some((number) => number === matchNumber); +} + function findByInfoFile(infoFile) { - const found = ['avi', 'mp4', 'mkv'].reduce((last, current) => { + const found = ['mkv', 'mp4', 'avi'].reduce((last, current) => { const element = findByHref(infoFile.replace(infoRegex, '.' + current))[0]; - return element ? element : last; + return element || last; }, null); return found; } @@ -69,6 +76,86 @@ async function createXMLRequest(nfo) { .then((str) => parser.parseFromString(str, 'text/xml')); } +// Fill metadata boxes, generate buttons +function fillMeta( + content, original, nfo, movieTitle, movieDescription, movieButtons +) { + infoFilesAccounted += 1; + if (!content) { + return; + } + + const titleEl = content.querySelector('title'); + const plotEl = content.querySelector('plot'); + const airedEl = content.querySelector('aired'); + const premieredEl = content.querySelector('premiered'); + const imdbEl = content.querySelector('uniqueid[type="imdb"]'); + const tvdbEl = content.querySelector('uniqueid[type="tvdb"]'); + + if (!titleEl || !plotEl) { + return; + } + + if (airedEl || premieredEl) { + const timestamp = original.parentElement.parentElement.querySelector('.timestamp'); + if (timestamp) { + timestamp.innerText = (airedEl || premieredEl).textContent; + } + } + + const episode = content.querySelector('episode'); + const season = content.querySelector('season'); + let title = titleEl.textContent + if (episode && season) { + title = `(S${season.textContent} E${episode.textContent}) ` + title; + original.parentElement.setAttribute('data-episode', episode.textContent); + } + + movieTitle.innerText = title; + movieDescription.innerText = plotEl.textContent; + + if (imdbEl) { + const imlink = document.createElement('a'); + imlink.target = '_blank'; + imlink.innerText = 'IMDb'; + imlink.href = `https://www.imdb.com/title/${imdbEl.textContent}`; + movieButtons.appendChild(imlink); + } + + if (tvdbEl) { + const tvlink = document.createElement('a'); + tvlink.target = '_blank'; + tvlink.innerText = 'The TVDB'; + tvlink.href = `http://www.thetvdb.com/?tab=series&id=${tvdbEl.textContent}`; + movieButtons.appendChild(tvlink); + } + + // Add "copy direct url" button + if ( + getHref(nfo) !== 'tvshow.nfo' + && navigator + && navigator.clipboard + && navigator.clipboard.writeText + ) { + const copybutton = document.createElement('button'); + copybutton.innerText = 'Copy direct URL'; + movieButtons.appendChild(copybutton); + copybutton.addEventListener('click', (e) => { + e.preventDefault(); + const popup = document.createElement('div'); + popup.className = 'tooltip'; + popup.innerText = 'Copied!'; + const url = window.location.href + reencodeURI(getHref(original)); + navigator.clipboard.writeText(url).then(() => { + copybutton.appendChild(popup); + setTimeout(() => copybutton.removeChild(popup), 1000); + }); + }); + } +} + +// Replace list entry with a metadata box +// Either create or populate function createOrImproveMovieMeta(original, thumbnail, nfo) { let imgTag = ``; let movieTitle = `
${original.innerText}
`; @@ -98,82 +185,18 @@ function createOrImproveMovieMeta(original, thumbnail, nfo) { movieButtons = original.querySelector('.movie-buttons'); } + // Fetch metadata from the nfo file and populate the info box if (nfo) { createXMLRequest(reencodeURI(getHref(nfo))) - .then((content) => { + .then((content) => fillMeta(content, original, nfo, movieTitle, movieDescription, movieButtons)) + .catch((e) => { infoFilesAccounted += 1; - if (!content) { - return; - } - - const titleEl = content.querySelector('title'); - const plotEl = content.querySelector('plot'); - const airedEl = content.querySelector('aired'); - const premieredEl = content.querySelector('premiered'); - const imdbEl = content.querySelector('uniqueid[type="imdb"]'); - const tvdbEl = content.querySelector('uniqueid[type="tvdb"]'); - - if (!titleEl || !plotEl) { - return; - } - - if (airedEl || premieredEl) { - const timestamp = original.parentElement.parentElement.querySelector('.timestamp'); - if (timestamp) { - timestamp.innerText = (airedEl || premieredEl).textContent; - } - } - - const episode = content.querySelector('episode'); - const season = content.querySelector('season'); - let title = titleEl.textContent - if (episode && season) { - title = `(S${season.textContent} E${episode.textContent}) ` + title; - original.parentElement.setAttribute('data-episode', episode.textContent); - } - - movieTitle.innerText = title; - movieDescription.innerText = plotEl.textContent; - if (imdbEl) { - const imlink = document.createElement('a'); - imlink.target = '_blank'; - imlink.innerText = 'IMDb'; - imlink.href = `https://www.imdb.com/title/${imdbEl.textContent}`; - movieButtons.appendChild(imlink); - } - if (tvdbEl) { - const tvlink = document.createElement('a'); - tvlink.target = '_blank'; - tvlink.innerText = 'The TVDB'; - tvlink.href = `http://www.thetvdb.com/?tab=series&id=${tvdbEl.textContent}`; - movieButtons.appendChild(tvlink); - } - if (getHref(nfo) !== 'tvshow.nfo' && - navigator && navigator.clipboard && - navigator.clipboard.writeText) { - const copybutton = document.createElement('button'); - copybutton.innerText = 'Copy direct URL'; - movieButtons.appendChild(copybutton); - copybutton.addEventListener('click', (e) => { - e.preventDefault(); - const popup = document.createElement('div'); - popup.className = 'tooltip'; - popup.innerText = 'Copied!'; - const url = window.location.href + reencodeURI(getHref(original)); - navigator.clipboard.writeText(url).then(() => { - copybutton.appendChild(popup); - setTimeout(() => copybutton.removeChild(popup), 1000); - }); - }); - } - }) - .catch((e) => { - infoFilesAccounted += 1; - console.error(e); - }); + console.error(e); + }); } } +// Promise that resolves when all info files that were found have been parsed function waitUntilInfoComplete() { return new Promise((resolve, reject) => { if (infoFilesAccounted >= infoFiles) { @@ -183,25 +206,28 @@ function waitUntilInfoComplete() { let seconds = 0; const accountWait = setInterval(() => { - if (infoFilesAccounted >= infoFiles || seconds > 5000) { + if (infoFilesAccounted >= infoFiles || seconds > 20) { clearInterval(accountWait); resolve(true); return; } - seconds += 500; + seconds += 1; }, 500); }); } +// Get episode name or name of file const getCellValue = (tr, idx) => tr.children[idx].getAttribute('data-episode') || tr.children[idx].innerText || tr.children[idx].textContent; +// Compare numeric or (locale-aware, numeric-aware) by string values const comparer = (idx, asc) => (a, b) => ((v1, v2) => - v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2) + v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2, 'et', { numeric: true }) )(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx)); +// Table sort by column index const sortTableValues = (index) => Array.from(tableElem.querySelectorAll('tr:nth-child(n+2):not(.btn-back)')) .sort(comparer(index, this.asc = !this.asc)) @@ -211,9 +237,13 @@ let tvShow; function accountForMetadata() { allLinks.forEach((link) => { const url = getHref(link); + + // If nfo or jpg (or subtitle, for hiding it).. if (url.match(infoRegex)) { + // Hide metadata file link.parentElement.parentElement.style.display = 'none'; let findOriginal + // Place TV show information in the beginning of the table if (url === 'tvshow.nfo' || url === 'poster.jpg') { if (url === 'poster.jpg' && !allLinks.find((found) => getHref(found) === 'tvshow.nfo')) { findOriginal = document.querySelector('a:not([href=".."])'); @@ -230,16 +260,30 @@ function accountForMetadata() { findOriginal = tvShow; } } + // Give posters to seasons } else if (url.startsWith('season') && url.includes('poster')) { + // Find season by number const sn = url.match(/season(\d+)/); if (sn) { - findOriginal = allLinks.find((allurl) => allurl.innerText.includes(parseInt(sn[1], 10))); + findOriginal = allLinks.find( + ({ innerText }) => { + return innerText.endsWith('/') + && findWholeNumberInText(innerText, parseInt(sn[1], 10)); + }); + // Find specials folder } else if (url.includes('specials')) { - findOriginal = allLinks.find((allurl) => allurl.innerText.toLowerCase().startsWith('special')); + findOriginal = allLinks.find(({ innerText }) => { + const matchAgainst = innerText.toLowerCase(); + return (matchAgainst.startsWith('specials') || matchAgainst.startsWith('extras')) + && innerText.endsWith('/'); + }); } + // Find the video file associated with this info file } else { findOriginal = findByInfoFile(url); } + + // Populate video file/season folder/tv show info box if (findOriginal) { if (url.match(/.nfo$/)) { infoFiles += 1; @@ -250,6 +294,8 @@ function accountForMetadata() { ); } } + + // Replace the back button with a more descriptive one if (url === '..') { link.parentElement.parentElement.classList.add('btn-back'); link.innerHTML = arrow + 'Back (..)';