commit 445c5c35be088bddd4928a72839a5dfca7bb3e5a Author: Evert Prants Date: Sun May 23 22:24:04 2021 +0300 initial commit diff --git a/autoindex.css b/autoindex.css new file mode 100644 index 0000000..b67c763 --- /dev/null +++ b/autoindex.css @@ -0,0 +1,99 @@ +* { + margin: 0; + padding: 0; +} +html,body { + width: 100%; + height: 100%; +} +body { + font-family: sans-serif; + font-size: 18px; + background-color: #0b0035; + color: #fff; +} +a { + color: #5799ff; +} +h1 { + padding: 2rem; +} +table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + font-size: 1.2rem; +} +table th, table td { + padding: 0.5rem; +} +table a { + display: block; +} +table tr:nth-child(2n) { + background-color: #09002b; +} +.btn-back { + background-color: #001153 !important; +} +.btn-back a { + text-decoration: none; +} +.btn-back #back-arrow { + width: 16px; + height: 16px; + margin-right: 0.5rem; +} +.wrapper { + display: flex; + flex-direction: column; + min-height: 100%; +} +.inner { + width: 80vw; + margin: auto; + background-color: #070022; + flex-grow: 1; +} +.highlight { + padding: 1rem; +} +.enhanced a { + display: flex; + flex-direction: row; +} +.enhanced a { + text-decoration: none; +} +.enhanced a:hover .movie-title { + text-decoration: underline; +} +.enhanced a .thumbnail { + max-width: 180px; + margin-right: 1rem; + flex-shrink: 0; + height: max-content; +} +.meta-wrap { + display: flex; + flex-direction: column; +} +.movie-title { + font-size: 1.2rem; +} +.movie-description { + color: #fff; +} +.plain-mode { + padding: 1rem; + color: #999; +} +.plain-mode label { + margin-left: 1rem; + cursor: pointer; +} +@media screen and (max-width: 1200px) { + .inner { + width: 100%; + } +} diff --git a/autoindex.js b/autoindex.js new file mode 100644 index 0000000..f9a019a --- /dev/null +++ b/autoindex.js @@ -0,0 +1,207 @@ +const allLinks = [...document.getElementsByTagName('a')]; +const bodyInner = document.querySelector('.inner'); +const tableElem = document.querySelector('table'); +const infoRegex = /(-thumb)?\.(jpg|nfo|srt)$/; + +const arrow = ` + + + + + + +` + +function findByHref(href) { + return allLinks.filter((link) => decodeURIComponent(link.getAttribute('href')) === decodeURIComponent(href)); +} + +function findByInfoFile(infoFile) { + const found = ['avi', 'mp4', 'mkv'].reduce((last, current) => { + const element = findByHref(infoFile.replace(infoRegex, '.' + current))[0]; + return element ? element : last; + }, null); + return found; +} + +function deduplicateJointEpisode(textContent) { + return new Promise((resolve, reject) => { + const lns = textContent.split('\n'); + const result = []; + let passed = 0; + for (const line of lns) { + if (line.startsWith('')) { + 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')); +} + +function createOrImproveMovieMeta(original, thumbnail, nfo) { + let imgTag = ``; + let movieTitle = `
${original.innerText}
`; + let movieDescription = `
`; + + if (!thumbnail && !nfo) { + return; + } + + if (original.parentElement.classList.contains('enhanced')) { + imgTag = original.querySelector('.thumbnail'); + movieTitle = original.querySelector('.movie-title'); + movieDescription = original.querySelector('.movie-description'); + if (thumbnail) { + imgTag.src = thumbnail; + } + } else { + original.classList.add('movie'); + original.innerHTML = imgTag + '
' + movieTitle + movieDescription + '
'; + original.parentElement.classList.add('enhanced'); + imgTag = original.querySelector('.thumbnail'); + movieTitle = original.querySelector('.movie-title'); + movieDescription = original.querySelector('.movie-description'); + } + + if (nfo) { + createXMLRequest(nfo.href) + .then((content) => { + if (!content) { + return; + } + + const titleEl = content.querySelector('title'); + const plotEl = content.querySelector('plot'); + const airedEl = content.querySelector('aired'); + + if (!titleEl || !plotEl) { + return; + } + + if (airedEl) { + const timestamp = original.parentElement.parentElement.querySelector('.timestamp'); + if (timestamp) { + timestamp.innerText = airedEl.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; + } + + movieTitle.innerText = title; + movieDescription.innerText = plotEl.textContent; + }); + } +} + +let tvShow; + +function accountForMetadata() { + allLinks.forEach((link) => { + const url = link.getAttribute('href'); + if (url.match(infoRegex)) { + link.parentElement.parentElement.style.display = 'none'; + let findOriginal + if (url === 'tvshow.nfo' || url === 'poster.jpg') { + // dirty hack + if (url === 'poster.jpg' && window.location.pathname.includes('/movies/')) { + 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; + } + } + } else if (url.startsWith('season') && url.includes('poster')) { + const sn = url.match(/season(\d+)/); + if (sn) { + const number = parseInt(sn[1], 10); + const findByNumber = allLinks.filter((url) => url.innerText.includes(number)); + if (findByNumber.length) { + findOriginal = findByNumber[0]; + } + } + } else { + findOriginal = findByInfoFile(encodeURIComponent(decodeURIComponent(url))); + } + if (findOriginal) { + createOrImproveMovieMeta(findOriginal, + url.match(/.jpg$/) ? link : undefined, + url.match(/.nfo$/) ? link : undefined, + ); + } + } + if (url === '..') { + link.parentElement.parentElement.classList.add('btn-back'); + link.innerHTML = arrow + 'Back (..)'; + } + }); +} + +function createToggleCheckbox(plainMode) { + const form = document.createElement('div'); + const checkbox = document.createElement('input'); + const label = document.createElement('label'); + form.className = 'plain-mode'; + checkbox.type = 'checkbox'; + checkbox.id = 'plainmode' + label.innerText = 'Disable metadata / display plain index listing (less bandwidth required)'; + label.setAttribute('for', 'plainmode'); + form.appendChild(checkbox); + form.appendChild(label) + bodyInner.appendChild(form); + + checkbox.addEventListener('change', (e) => { + if (checkbox.checked) { + window.localStorage.setItem('plainMode', 'true'); + } else { + window.localStorage.removeItem('plainMode'); + } + window.location.reload(); + }); + checkbox.checked = plainMode === 'true'; +} + +if ('localStorage' in window) { + const plainMode = window.localStorage.getItem('plainMode'); + if (plainMode !== 'true') { + accountForMetadata(); + } + createToggleCheckbox(plainMode); +} else { + accountForMetadata(); +} +/* +const loc = 'Index of ' + decodeURIComponent(document.location.pathname); +document.getElementById('dirname').innerHTML = loc; +document.title = loc; +*/ diff --git a/autoindex.xslt b/autoindex.xslt new file mode 100644 index 0000000..5a44876 --- /dev/null +++ b/autoindex.xslt @@ -0,0 +1,74 @@ + + + + + + + + Index of <xsl:value-of select="$path"/> + + +
+
+

Index of

+ + + + + + + + + + + + + + + + + + +
NameSizeTime
..
+ + + + + + + + + + / + + + + + + + TiB + + + GiB + + + MiB + + + KiB + + + B + + + - + + + + +
+
+
+ +
+
diff --git a/nginx.example.conf b/nginx.example.conf new file mode 100644 index 0000000..069b310 --- /dev/null +++ b/nginx.example.conf @@ -0,0 +1,8 @@ +location / { + autoindex on; + autoindex_exact_size off; + autoindex_localtime on; + autoindex_format xml; + xslt_string_param path $uri; + xslt_stylesheet autoindex.xslt; +}