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 (..)';