TV-Center/public/existing.js

348 lines
8.6 KiB
JavaScript

const apiUrls = [
{
url: '/sonarr-hs-series',
name: 'hidden-sea',
domain: 'sonarr-hs',
},
{
url: '/sonarr-ag-series',
name: 'ancient-grove',
domain: 'sonarr-ag',
},
{
url: '/sonarr-bs-series',
name: 'bold-silence',
domain: 'sonarr-bs',
},
{
url: '/sonarr-fd-series',
name: 'frosty-darkness',
domain: 'sonarr-fd',
},
{
url: '/sonarr-rs-series',
name: 'smooth-canyon [4K]',
domain: 'sonarr.srvr.no',
},
{
url: '/radarr-hs-movies',
name: 'hidden-sea',
domain: 'radarr-hs',
},
{
url: '/radarr-ag-movies',
name: 'ancient-grove',
domain: 'radarr-ag',
},
{
url: '/radarr-bs-movies',
name: 'bold-silence',
domain: 'radarr-bs',
},
{
url: '/radarr-fd-movies',
name: 'frosty-darkness',
domain: 'radarr-fd',
},
];
let media = [];
function removeMessage()
{
const message = document.querySelector('#status-message');
if (message) {
message.remove();
}
}
/**
* For use with Array.prototype.map to map a series/movie to a row.
*
* @param {Object} mediaItem
* @returns Array
*/
const editionRegex = /\{edition-(.+)\}$/g;
function mediaToRow(mediaItem) {
let { title, mediaType, movieFile, year, server, folderName } = mediaItem;
let quality = '';
if (movieFile) {
quality = movieFile.quality.quality.name;
}
let size = 0;
if (mediaItem.sizeOnDisk) {
size = mediaItem.sizeOnDisk;
} else if (mediaItem.statistics && mediaItem.statistics.sizeOnDisk) {
size = mediaItem.statistics.sizeOnDisk;
}
if (editionRegex.test(folderName)) {
const match = folderName.match(editionRegex);
const edition = match[0].replace('{edition-', '').replace('}', '');
title += ` [${edition}]`;
}
return [
title,
mediaType,
quality,
year,
size,
server,
];
}
function humanFileSize(input) {
if (input <= 0) {
return '0 B';
}
const i = Math.floor(Math.log(input) / Math.log(1024));
return (input / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
}
let dataTbl = null;
let documentTableCopy = null;
function addDataTable(rows)
{
const tblData = {
headings: [
'Series/Movie name',
'Type',
'Quality',
'Year',
'Size on disk',
'Server',
],
data: rows,
};
let tbl = document.querySelector('#media-table');
if (!documentTableCopy) {
documentTableCopy = tbl.cloneNode(true);
}
const checkDatatableWrapper = document.querySelector('.datatable-wrapper');
if (checkDatatableWrapper) {
checkDatatableWrapper.insertAdjacentElement('afterend', documentTableCopy);
checkDatatableWrapper.remove();
tbl = document.querySelector('#media-table');
}
const dataTblConfig = {
searchable: true,
fixedHeight: false,
perPage: 100,
perPageSelect: [25, 50, 100, 250, 500, 750, 1000],
data: tblData,
layout: {
top: '{search}{select}',
bottom: '{pager}{info}',
},
columns: [
{
select: [tblData.headings.indexOf('Size on disk')],
render: function(data, cell, row) {
if (typeof data !== 'number') {
data = parseInt(data, 10);
}
return humanFileSize(data);
}
},
{
// Resolve based on the heading, since we might shift it from time to time.
select: [tblData.headings.indexOf('Server')],
render: function(data, cell, row) {
const [name, domain, slug, id] = data.split('|');
let url = `https://${domain}`;
if (!domain.includes('srvr.no')) {
url += '.decicus.com';
}
if (domain.includes('sonarr')) {
url += `/series/${slug}`;
}
else {
url += `/movie/${id}`;
}
return `<a href="${url}" target="_blank">${name}</a>`;
},
}
],
};
removeMessage();
if (dataTbl) {
dataTbl.rows.remove();
}
dataTbl = new simpleDatatables.DataTable(tbl, dataTblConfig);
dataTbl.on('datatable.init', function() {
const search = document.querySelector('.datatable-search input');
if (search) {
search.focus();
}
});
// try {
// dataTbl.refresh();
// }
// catch (err) {
// console.error(err);
// }
}
async function apiFetch(apiUrl)
{
const errors = document.querySelector('#errors');
try {
const response = await fetch(apiUrl.url);
let json = await response.json();
json.map((item) => {
item.server = `${apiUrl.name}|${apiUrl.domain}|${item.titleSlug}|${item.tmdbId || item.id}`;
item.mediaType = apiUrl.url.includes('sonarr') ? 'Series' : 'Movie';
return item;
});
return json;
}
catch (err) {
const element = document.createElement('p');
element.textContent = `Error fetching data from ${apiUrl.url}: ${err.message}`;
if (errors) {
errors.appendChild(element);
}
console.error(err);
}
return [];
}
/**
*
* @param {Array} media Flatten array of media objects
*/
async function printTotalFilesize(media)
{
const totalSizeElement = document.querySelector('#total-filesize');
if (!totalSizeElement) {
return;
}
let totalSizeOnDisk = 0;
for (const item of media)
{
const { path } = item;
// Ignore the 4K Plex server
if (path && (path.includes('/data/echo/MediaServer') || path.includes('/data/media/MediaServer'))) {
continue;
}
if (item.sizeOnDisk) {
totalSizeOnDisk += item.sizeOnDisk;
continue;
}
if (item.statistics && item.statistics.sizeOnDisk) {
totalSizeOnDisk += item.statistics.sizeOnDisk;
}
}
totalSizeElement.innerHTML = `💾 Total media storage size, minus <code>smooth-canyon</code> (4K server): <kbd>${humanFileSize(totalSizeOnDisk)}</kbd>`;
totalSizeElement.classList.remove('hidden');
}
async function initial()
{
media = [];
const refreshBtn = document.querySelector('#refresh-button');
let icon = null;
if (refreshBtn) {
icon = refreshBtn.querySelector('.fas');
refreshBtn.disabled = true;
icon.classList.add('fa-spin');
}
let promises = [];
for (const apiUrl of apiUrls) {
promises.push(apiFetch(apiUrl));
}
media = await Promise.all(promises);
media = media.flat();
printTotalFilesize(media);
media = media.sort((a, b) => {
return a.sortTitle.localeCompare(b.sortTitle);
});
let rows = media.map(mediaToRow);
addDataTable(rows);
const store = {
timestamp: Date.now(),
media,
};
const lastUpdated = document.querySelector('#last-updated');
lastUpdated.textContent = `Last updated: ${new Date(store.timestamp).toLocaleString()}`;
localStorage.setItem('cache', LZString.compress(JSON.stringify(store)));
if (refreshBtn && icon) {
icon.classList.remove('fa-spin');
refreshBtn.disabled = false;
}
}
window.addEventListener('DOMContentLoaded', function() {
const cache = localStorage.getItem('cache');
if (cache) {
const { timestamp, media } = JSON.parse(LZString.decompress(cache));
let rows = media.map(mediaToRow);
addDataTable(rows);
const lastUpdated = document.querySelector('#last-updated');
lastUpdated.textContent = `Last updated: ${new Date(timestamp).toLocaleString()}`;
}
const refreshBtn = document.querySelector('#refresh-button');
if (refreshBtn) {
refreshBtn.addEventListener('click', async function() {
// I don't think this is necessary, but ayyy lmao
if (refreshBtn.disabled) {
return;
}
try {
await initial();
}
catch (err) {
console.error(err);
icon.classList.remove('fa-spin');
refreshBtn.disabled = false;
}
});
}
initial();
});