')) {
if (passed === 1) {
break;
}
passed = 1;
}
result.push(line);
}
resolve(result.join('\n'));
});
}
const parser = new window.DOMParser();
async function createXMLRequest(nfo) {
return fetch(nfo)
.then((response) => response.text())
.then((text) => deduplicateJointEpisode(text))
.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) {
const original = timestamp.innerText;
const airdate = restampDateTime(
(airedEl || premieredEl).textContent,
false
);
const wrapper = `${airdate}`;
const originalWrapper = ` (${original})`;
timestamp.innerHTML = wrapper + originalWrapper;
}
}
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}
`;
let movieDescription = ``;
let movieButtons = ``;
if (!thumbnail && !nfo) {
return;
}
if (original.parentElement.classList.contains('enhanced')) {
imgTag = original.querySelector('.thumbnail');
movieTitle = original.querySelector('.movie-title');
movieDescription = original.querySelector('.movie-description');
movieButtons = original.querySelector('.movie-buttons');
if (thumbnail) {
imgTag.src = reencodeURI(getHref(thumbnail));
}
} else {
original.classList.add('movie');
original.innerHTML = `${imgTag}${movieTitle}${movieDescription}${movieButtons}
`;
original.parentElement.classList.add('enhanced');
// quick innerHTML hack to create elements here lol
imgTag = original.querySelector('.thumbnail');
movieTitle = original.querySelector('.movie-title');
movieDescription = original.querySelector('.movie-description');
movieButtons = original.querySelector('.movie-buttons');
}
// Fetch metadata from the nfo file and populate the info box
if (nfo) {
createXMLRequest(reencodeURI(getHref(nfo)))
.then((content) =>
fillMeta(
content,
original,
nfo,
movieTitle,
movieDescription,
movieButtons
)
)
.catch((e) => {
infoFilesAccounted += 1;
console.error(e);
});
}
}
function timestampify() {
Array.from(document.body.querySelectorAll('.timestamp')).forEach((stamp) => {
stamp.innerText = restampDateTime(stamp.innerText);
});
}
// Promise that resolves when all info files that were found have been parsed
function waitUntilInfoComplete() {
return new Promise((resolve, reject) => {
if (infoFilesAccounted >= infoFiles) {
resolve(true);
return;
}
let seconds = 0;
const accountWait = setInterval(() => {
if (infoFilesAccounted >= infoFiles || seconds > 20) {
clearInterval(accountWait);
resolve(true);
return;
}
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, '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)))
.forEach((tr) => tableElem.appendChild(tr));
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=".."])');
} else {
if (tvShow) {
findOriginal = tvShow;
} else {
const wrapper = document.createElement('div');
wrapper.className = 'highlight';
tvShow = document.createElement('a');
tvShow.className = 'movie';
wrapper.appendChild(tvShow);
bodyInner.insertBefore(wrapper, tableElem);
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(({ innerText }) => {
return (
innerText.endsWith('/') &&
findWholeNumberInText(innerText, parseInt(sn[1], 10))
);
});
// Find specials folder
} else if (url.includes('specials')) {
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;
}
createOrImproveMovieMeta(
findOriginal,
url.match(/.jpg$/) ? link : undefined,
url.match(/.nfo$/) ? link : undefined
);
}
}
// Replace the back button with a more descriptive one
if (url === '..') {
link.parentElement.parentElement.classList.add('btn-back');
link.innerHTML = arrow + 'Back (..)';
}
});
waitUntilInfoComplete().then(() => sortTableValues(0));
}
function createToggleCheckbox(field, value, description) {
const form = document.createElement('div');
const checkbox = document.createElement('input');
const label = document.createElement('label');
form.className = 'form ' + field;
checkbox.type = 'checkbox';
checkbox.id = field;
label.innerText = description;
label.setAttribute('for', field);
form.appendChild(checkbox);
form.appendChild(label);
bodyInner.appendChild(form);
checkbox.addEventListener('change', (e) => {
if (checkbox.checked) {
window.localStorage.setItem(field, 'true');
} else {
window.localStorage.removeItem(field);
}
window.location.reload();
});
checkbox.checked = value === 'true';
}
function createDirTree() {
dirnameElem.classList.add('directory');
const dirSplit = dirnameElem.innerText.split('/').slice(1).filter((item) => !!item);
dirSplit.unshift('');
dirnameElem.innerHTML = '';
let lastPath = '';
for (const dir of dirSplit) {
lastPath += dir + '/';
const atag = document.createElement('a');
atag.innerText = dir;
atag.classList.add('directory-link');
atag.href = lastPath;
dirnameElem.appendChild(atag);
}
}
if ('localStorage' in window) {
const plainMode = window.localStorage.getItem('plainMode');
if (plainMode !== 'true') {
timestampify();
accountForMetadata();
}
createToggleCheckbox(
'plainMode',
plainMode,
'Disable metadata / display plain index listing (less bandwidth required)'
);
} else {
timestampify();
accountForMetadata();
}
document.querySelectorAll('th').forEach((th) =>
th.addEventListener('click', () => {
sortTableValues(Array.from(th.parentNode.children).indexOf(th));
})
);
createDirTree();