var basePath = ''; var baseUrl = ''; var indexers = []; var configuredIndexers = []; var unconfiguredIndexers = []; var configuredTags = []; var availableFilters = []; var currentFilter = null; $.fn.inView = function () { if (!this.length) return false; var rect = this.get(0).getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); }; $.fn.focusWithoutScrolling = function () { if (this.inView()) this.focus(); return this; }; $(document).ready(function () { $.ajaxSetup({ cache: false }); Handlebars.registerHelper('if_eq', function (a, b, opts) { if (a == b) return opts.fn(this); else return opts.inverse(this); }); Handlebars.registerHelper('if_in', function (elem, list, opts) { if (list.indexOf(elem) > -1) { return opts.fn(this); } return opts.inverse(this); }); var index = window.location.pathname.indexOf("/UI"); var pathPrefix = window.location.pathname.substr(0, index); api.root = pathPrefix + api.root; const hashArgs = getHashArgs(); if ("indexers" in hashArgs) currentFilter = hashArgs.filter bindUIButtons(); loadJackettSettings(); }); function openSearchIfNecessary() { const hashArgs = getHashArgs(); if ("search" in hashArgs) { showSearch(hashArgs.filter, hashArgs.tracker, hashArgs.search, hashArgs.category); } } function getHashArgs() { return location.hash.substring(1).split('&').reduce((prev, item) => Object.assign({ [item.split('=')[0]]: (item.split('=').length < 2 ? undefined : decodeURIComponent(item.split('=')[1].replace(/\+/g, '%20'))) }, prev), {}); } function insertWordWrap(str) { // insert optional word wrap after punctuation to avoid overflows on long scene titles return str.replace(/([\.\-_\/\\])/g, "$1\u200B"); } function type_filter(indexer) { return indexer.type == this.value; } function tag_filter(indexer) { return indexer.tags.map(t => t.toLowerCase()).indexOf(this.value.toLowerCase()) > -1; } function state_filter(indexer) { return indexer.state == this.value; } function getJackettConfig(callback) { api.getServerConfig(callback).fail(function () { doNotify("Error loading Jackett settings, request to Jackett server failed, is server running ?", "danger", "glyphicon glyphicon-alert"); }); } function loadJackettSettings() { getJackettConfig(function (data) { $("#api-key-input").val(data.api_key); $(".api-key-text").text(data.api_key); $("#app-version").html(data.app_version); $("#jackett-port").val(data.port); $("#jackett-proxy-type").val(data.proxy_type); $("#jackett-proxy-url").val(data.proxy_url); $("#jackett-proxy-port").val(data.proxy_port); $("#jackett-proxy-username").val(data.proxy_username); $("#jackett-proxy-password").val(data.proxy_password); proxyWarning(data.proxy_type); $("#jackett-basepathoverride").val(data.basepathoverride); basePath = data.basepathoverride; if (basePath === null || basePath === undefined) { basePath = ''; } $("#jackett-baseurloverride").val(data.baseurloverride); baseUrl = data.baseurloverride; if (baseUrl === null || baseUrl === undefined) { baseUrl = ''; } api.key = data.api_key; $("#jackett-savedir").val(data.blackholedir); $("#jackett-allowext").attr('checked', data.external); $("#jackett-allowupdate").attr('checked', data.updatedisabled); $("#jackett-prerelease").attr('checked', data.prerelease); $("#jackett-logging").attr('checked', data.logging); $("#jackett-cache-enabled").attr('checked', data.cache_enabled); $("#jackett-cache-ttl").val(data.cache_ttl); $("#jackett-cache-max-results-per-indexer").val(data.cache_max_results_per_indexer); if (!data.cache_enabled) { $("#jackett-show-releases").attr("disabled", true); } $("#jackett-flaresolverrurl").val(data.flaresolverrurl); $("#jackett-flaresolverr-maxtimeout").val(data.flaresolverr_maxtimeout); $("#jackett-omdbkey").val(data.omdbkey); $("#jackett-omdburl").val(data.omdburl); var password = data.password; $("#jackett-adminpwd").val(password); if (password != null && password != '') { $("#logoutBtn").show(); } if (data.can_run_netcore != null && data.can_run_netcore === true) { $("#can-upgrade-from-mono").show(); } $.each(data.notices, function (index, value) { console.log(value); doNotify(value, "danger", "glyphicon glyphicon-alert", false); }); reloadIndexers(); }); } function reloadIndexers() { $('#filters').hide(); $('#indexers').hide(); api.getAllIndexers(function (data) { indexers = data; configuredIndexers = []; unconfiguredIndexers = []; configuredTags = []; availableFilters = []; for (var i = 0; i < data.length; i++) { var item = data[i]; item.rss_host = resolveUrl(baseUrl, basePath + "/api/v2.0/indexers/" + item.id + "/results/torznab/api?apikey=" + api.key + "&t=search&cat=&q="); item.torznab_host = resolveUrl(baseUrl, basePath + "/api/v2.0/indexers/" + item.id + "/results/torznab/"); item.potato_host = resolveUrl(baseUrl, basePath + "/api/v2.0/indexers/" + item.id + "/results/potato/"); if (item.last_error) item.state = "error"; else item.state = "success"; if (item.type == "public") { item.type_label = "success"; } else if (item.type == "private") { item.type_label = "danger"; } else if (item.type == "semi-private") { item.type_label = "warning"; } else { item.type_label = "default"; } var main_cats_list = item.caps.filter(function (c) { return c.ID < 100000; }).map(function (c) { return c.Name.split("/")[0]; }); item.mains_cats = $.unique(main_cats_list).join(", "); if (item.configured) configuredIndexers.push(item); else unconfiguredIndexers.push(item); } configuredTags = configuredIndexers.map(i => i.tags).reduce((a, g) => a.concat(g), []).filter((v, i, a) => a.indexOf(v) === i); configureFilters(configuredIndexers); displayFilteredIndexersList(configuredIndexers, currentFilter); $('#indexers div.dataTables_filter input').focusWithoutScrolling(); openSearchIfNecessary(); }).fail(function () { doNotify("Error loading indexers, request to Jackett server failed, is server running ?", "danger", "glyphicon glyphicon-alert"); }); } function configureFilters(indexers) { function add(f) { if (availableFilters.find(x => x.id == f.id)) return; if (!indexers.every(f.apply, f) && indexers.some(f.apply, f)) availableFilters.push(f); } availableFilters.push({id: "test:passed", apply: state_filter, value: "success" }); availableFilters.push({id: "test:failed", apply: state_filter, value: "error" }); ["public", "private", "semi-private"] .map(t => { return { id: "type:" + t, apply: type_filter, value: t } }) .forEach(add); configuredTags.sort() .map(t => { return { id: "tag:" + t.toLowerCase(), apply: tag_filter, value: t }}) .forEach(add); } function displayFilteredIndexersList(indexers, filter) { var active = availableFilters.find(x => x.id == filter); if (availableFilters.length > 0) { var filtersTemplate = Handlebars.compile($("#jackett-filters").html()); var filters = $(filtersTemplate({ filters: availableFilters, active: active ? active.id : null })); $("li a", filters).on('click', function(){ displayFilteredIndexersList(configuredIndexers, $(this).data("id")); }); $('#filters').empty(); $('#filters').append(filters); $('#filters').fadeIn(); } if (active) { indexers = indexers.filter(active.apply, active); currentFilter = active.id; } else { currentFilter = null; } displayConfiguredIndexersList(indexers) } function displayConfiguredIndexersList(indexers) { var indexersTemplate = Handlebars.compile($("#configured-indexer-table").html()); var indexersTable = $(indexersTemplate({ indexers: indexers, total_configured_indexers: indexers.length })); prepareTestButtons(indexersTable); prepareSearchButtons(indexersTable); prepareSetupButtons(indexersTable); prepareDeleteButtons(indexersTable); prepareCopyButtons(indexersTable); indexersTable.find("table").dataTable({ "stateSave": true, "stateDuration": 0, "pageLength": -1, "lengthMenu": [ [10, 20, 50, 100, 250, 500, -1], [10, 20, 50, 100, 250, 500, "All"] ], "order": [ [0, "asc"] ], "columnDefs": [{ "targets": 0, "visible": true, "searchable": true, "orderable": true }, { "targets": 1, "visible": true, "searchable": true, "orderable": true }, { "targets": 2, "visible": false, "searchable": true, "orderable": false } ] }); $('#indexers').empty(); $('#indexers').append(indexersTable); $('#indexers').fadeIn(); } function displayUnconfiguredIndexersList() { var UnconfiguredIndexersDialog = $($("#select-indexer").html()); var indexersTemplate = Handlebars.compile($("#unconfigured-indexer-table").html()); var indexersTable = $(indexersTemplate({ indexers: unconfiguredIndexers, total_unconfigured_indexers: unconfiguredIndexers.length })); indexersTable.find('.indexer-setup').each(function (i, btn) { var indexer = unconfiguredIndexers[i]; $(btn).click(function () { $('#select-indexer-modal').modal('hide').on('hidden.bs.modal', function (e) { displayIndexerSetup(indexer.id, indexer.name, indexer.caps, indexer.link, indexer.alternativesitelinks, indexer.description); }); }); }); indexersTable.find('.indexer-add').each(function (i, btn) { $(btn).click(function () { $('#select-indexer-modal').modal('hide').on('hidden.bs.modal', function (e) { var indexerId = $(btn).attr("data-id"); addIndexer(indexerId, true); }); }); }); indexersTable.find("table").DataTable({ initComplete: function () { var currentTable = this; this.api().columns().every(function (index, i, j) { var column = this; var headerText = column.header().innerText; if (headerText == 'Type') { var select = createDropDownHtml(column, true); var columnData = currentTable.api().columns(index + 1).data(); var distinctValues = [...new Set(columnData[0])]; distinctValues.forEach(function (distinctVal) { select.append('') }); } else if (headerText == 'Categories') { var select = createDropDownHtml(column, false); var columnData = []; column.data().unique().each(function (d, j) { d.split(',').forEach(function (val) { columnData.push(val.trim()); }); }); var distinctValues = [...new Set(columnData)]; distinctValues.sort().forEach(function (distinctVal) { select.append('') }); } else if (headerText == 'Language') { var select = createDropDownHtml(column, true); column.data().unique().sort().each(function (d, j) { select.append('') }); } else { $(column.footer()).empty(); } }); }, "drawCallback": function (settings) { addCheckOnCellClick(); }, "stateSave": true, "stateDuration": 0, "fnStateSaveParams": function (oSettings, sValue) { sValue.search.search = ""; // don't save the search filter content return sValue; }, "bAutoWidth": false, "pageLength": -1, "lengthMenu": [ [10, 20, 50, 100, 250, 500, -1], [10, 20, 50, 100, 250, 500, "All"] ], "select": { style: 'os', selector: 'td:first-child' }, "order": [ [1, "asc"] ], "columnDefs": [{ "name": "select", "targets": 0, "visible": true, "searchable": false, "orderable": false }, { "name": "name", "targets": 1, "visible": true, "searchable": true, "orderable": true }, { "name": "description", "targets": 2, "visible": true, "searchable": true, "orderable": true }, { "name": "type", "targets": 3, "visible": true, "searchable": true, "orderable": true }, { "name": "type_string", "targets": 4, "visible": false, "searchable": false, "orderable": false, }, { "name": "language", "targets": 5, "visible": true, "searchable": true, "orderable": true }, { "name": "buttons", "targets": 6, "visible": true, "searchable": false, "orderable": false }, { "name": "url", "targets": 7, "visible": false, "searchable": true, "orderable": false } ] }); var undefindexers = UnconfiguredIndexersDialog.find('#unconfigured-indexers'); undefindexers.append(indexersTable); UnconfiguredIndexersDialog.on('shown.bs.modal', function () { $(this).find('div.dataTables_filter input').focusWithoutScrolling(); }); UnconfiguredIndexersDialog.on('hidden.bs.modal', function (e) { $('#indexers div.dataTables_filter input').focusWithoutScrolling(); }); $("#modals").append(UnconfiguredIndexersDialog); $('#add-selected-indexers').click(function () { var selectedIndexers = $('#unconfigured-indexer-datatable').DataTable().$('input[type="checkbox"]'); var hasSelectedIndexers = selectedIndexers.is(':checked'); if (hasSelectedIndexers) { doNotify("Adding selected Indexers, please wait...", "info", "glyphicon glyphicon-transfer", false); $('#select-indexer-modal button').attr('disabled', true); addIndexers(selectedIndexers, addSelectedIndexersSuccess, addSelectedIndexersError); } else { doNotify("Error: You must select more than one indexer", "danger", "glyphicon glyphicon-alert"); } }); UnconfiguredIndexersDialog.modal("show"); } function addSelectedIndexersSuccess() { $.notifyClose(); $('#select-indexer-modal').modal('hide'); doNotify("Selected indexers successfully added.", "success", "glyphicon glyphicon-ok"); $('#select-indexer-modal button').attr('disabled', false); } function addSelectedIndexersError(e, xhr, options, err) { doNotify("Configuration failed", "danger", "glyphicon glyphicon-alert"); } function addCheckOnCellClick() { $('td.checkboxColumn') .off('click') .on('click', (function (event) { if (!$(event.target).is('input')) { $('input:checkbox', this).prop('checked', function (i, value) { return !value; }); } })); } function addIndexers(selectedIndexerList, successCallback, errorCallback) { $(document).ajaxStop(function () { if (successCallback == addSelectedIndexersSuccess) { $(document).ajaxStop().unbind(); // Keep future AJAX events from effecting this successCallback(); } }).ajaxError(function (e, xhr, options, err) { errorCallback(e, xhr, options, err); }); selectedIndexerList.each(function () { if (this.checked) { addIndexer($(this).data('id'), false); } }) } function createDropDownHtml(column, exactMatch) { var select = $('') .appendTo($(column.footer()).empty()) .on('change', function () { var val = $.fn.dataTable.util.escapeRegex( $(this).val() ); if (exactMatch) { column .search(val ? '^' + val + '$' : '', true, false) .draw(); } else { column .search(val ? val : '', true, false) .draw(); } }); return select; } function addIndexer(indexerId, displayNotification) { api.getIndexerConfig(indexerId, function (data) { if (data.result !== undefined && data.result == "error") { doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); return; } api.updateIndexerConfig(indexerId, data, function (data) { if (data == undefined) { reloadIndexers(); if (displayNotification) { doNotify("Successfully configured " + indexerId, "success", "glyphicon glyphicon-ok"); } } else if (data.result == "error") { if (data.config) { populateConfigItems(configForm, data.config); } doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert"); } }).fail(function (data) { doErrorNotify(indexerId, data.responseJSON.error, "configuring"); }); }); } function copyToClipboard(text) { // create hidden text element, if it doesn't already exist var targetId = "_hiddenCopyText_"; // must use a temporary form element for the selection and copy target = document.getElementById(targetId); if (!target) { var target = document.createElement("textarea"); target.style.position = "fixed"; target.style.left = "-9999px"; target.style.top = "0"; target.id = targetId; document.body.appendChild(target); } target.textContent = text; // select the content var currentFocus = document.activeElement; target.focus(); target.setSelectionRange(0, target.value.length); // copy the selection var succeed; try { succeed = document.execCommand("copy"); doNotify("Copied to clipboard!", "success", "glyphicon glyphicon-ok"); } catch (e) { succeed = false; } // restore original focus if (currentFocus && typeof currentFocus.focus === "function") { $(currentFocus).focusWithoutScrolling(); } target.textContent = ""; return succeed; } function prepareCopyButtons(element) { element.find(".indexer-button-copy").each(function (i, btn) { var $btn = $(btn); var title = $btn[0].title; $btn.click(function () { copyToClipboard(title); return false; }); }); } function prepareDeleteButtons(element) { element.find(".indexer-button-delete").each(function (i, btn) { var $btn = $(btn); var id = $btn.data("id"); $btn.click(function () { api.deleteIndexer(id, function (data) { if (data == undefined) { doNotify("Deleted " + id, "success", "glyphicon glyphicon-ok"); } else if (data.result == "error") { doNotify("Delete error for " + id + "\n" + data.error, "danger", "glyphicon glyphicon-alert"); } }).fail(function () { doNotify("Error deleting indexer, request to Jackett server error", "danger", "glyphicon glyphicon-alert"); }).always(function () { reloadIndexers(); }); }); }); } function prepareSearchButtons(element) { element.find('.indexer-button-search').each(function (i, btn) { var $btn = $(btn); var id = $btn.data("id"); $btn.click(function () { window.location.hash = "search&tracker=" + id + (currentFilter ? "&filter=" + currentFilter : ""); showSearch(currentFilter, id); }); }); } function prepareSetupButtons(element) { element.find('.indexer-setup').each(function (i, btn) { var $btn = $(btn); var id = $btn.data("id"); var indexer = configuredIndexers.find(i => i.id === id); if (indexer) $btn.click(function () { displayIndexerSetup(indexer.id, indexer.name, indexer.caps, indexer.link, indexer.alternativesitelinks, indexer.description); }); }); } function updateTestState(id, state, message, parent) { var btn = parent.find(".indexer-button-test[data-id=" + id + "]"); var sortmsg = message; if (!sortmsg || state == "success") sortmsg = ""; var td = btn.closest("td"); td.attr("data-sort", sortmsg); td.attr("data-filter", sortmsg); if (message) { btn.tooltip("hide"); btn.attr("title", message); btn.data('bs.tooltip', false).tooltip({ title: message }); } var icon = btn.find("span"); icon.removeClass("glyphicon-ok test-success glyphicon-alert test-error glyphicon-refresh spinner test-inprogres"); if (state == "success") { icon.addClass("glyphicon-ok test-success"); } else if (state == "error") { icon.addClass("glyphicon-alert test-error"); } else if (state == "inprogres") { icon.addClass("glyphicon-refresh test-inprogres spinner"); } var dt = $.fn.dataTable.tables({ visible: true, api: true }).rows().invalidate('dom'); if (state != "inprogres") dt.draw(); var indexer = configuredIndexers.find(x => x.id == id); if (indexer) indexer.state = state; } function testIndexer(id, notifyResult) { var indexers = $('#indexers'); updateTestState(id, "inprogres", null, indexers); if (notifyResult) doNotify("Test started for " + id, "info", "glyphicon glyphicon-transfer"); api.testIndexer(id, function (data) { if (data == undefined) { updateTestState(id, "success", "Test successful", indexers); if (notifyResult) doNotify("Test successful for " + id, "success", "glyphicon glyphicon-ok"); } else if (data.result == "error") { updateTestState(id, "error", data.error, indexers); if (notifyResult) doNotify("Test failed for " + id + ": \n" + data.error, "danger", "glyphicon glyphicon-alert"); } }).fail(function (data) { updateTestState(id, "error", data.error, indexers); doErrorNotify(id, data.responseJSON.error, "testing"); }); } function prepareTestButtons(element) { element.find(".indexer-button-test").each(function (i, btn) { var $btn = $(btn); var id = $btn.data("id"); var state = $btn.data("state"); var title = $btn.attr("title"); $btn.tooltip(); updateTestState(id, state, title, element); $btn.click(function () { testIndexer(id, true); }); }); } function displayIndexerSetup(id, name, caps, link, alternativesitelinks, description) { api.getIndexerConfig(id, function (data) { if (data.result !== undefined && data.result == "error") { doNotify("Error: " + data.error, "danger", "glyphicon glyphicon-alert"); return; } populateSetupForm(id, name, data, caps, link, alternativesitelinks, description); }).fail(function () { doNotify("Request to Jackett server failed", "danger", "glyphicon glyphicon-alert"); }); $("#select-indexer-modal").modal("hide"); } function populateConfigItems(configForm, config) { // Set flag so we show fields named password as a password input for (var i = 0; i < config.length; i++) { config[i].ispassword = config[i].id.toLowerCase() === 'password'; } var $formItemContainer = configForm.find(".config-setup-form"); $formItemContainer.empty(); var setupItemTemplate = Handlebars.compile($("#setup-item").html()); for (var i = 0; i < config.length; i++) { var item = config[i]; var setupValueTemplate = Handlebars.compile($("#setup-item-" + item.type).html()); item.value_element = setupValueTemplate(item); var template = $(setupItemTemplate(item)); $formItemContainer.append(template); setupConfigItem(template, item); } } function setupConfigItem(configItem, item) { switch (item.type) { case "inputtags": { configItem.find("input").tagify({ dropdown: { enabled: 0, position: "text" }, separator: item.separator || ",", whitelist: item.whitelist || [], blacklist: item.blacklist || [], pattern: item.pattern || null, delimiters: item.delimiters || item.separator || ",", originalInputValueFormat: function (values) { return values.map(item => item.value.toLowerCase()).join(this.separator); } }); } break; } } function newConfigModal(title, config, caps, link, alternativesitelinks, description) { var configTemplate = Handlebars.compile($("#jackett-config-setup-modal").html()); var configForm = $(configTemplate({ title: title, caps: caps, link: link, description: description })); $("#modals").append(configForm); populateConfigItems(configForm, config); if (alternativesitelinks.length >= 1) { var AlternativeSiteLinksTemplate = Handlebars.compile($("#setup-item-alternativesitelinks").html()); var template = $(AlternativeSiteLinksTemplate({ "alternativesitelinks": alternativesitelinks })); configForm.find("div[data-id='sitelink']").after(template); template.find("a.alternativesitelink").click(function (a) { $("div[data-id='sitelink'] input").val(this.href); return false; }); } $("div[data-id='tags'] input", configForm).data("tagify").settings.whitelist = configuredTags; return configForm; } function getConfigModalJson(configForm) { var configJson = []; configForm.find(".config-setup-form").children().each(function (i, el) { $el = $(el); var type = $el.data("type"); var id = $el.data("id"); var itemEntry = { id: id }; switch (type) { case "hiddendata": itemEntry.value = $el.find(".setup-item-hiddendata input").val(); break; case "inputstring": itemEntry.value = $el.find(".setup-item-inputstring input").val(); break; case "password": itemEntry.value = $el.find(".setup-item-password input").val(); break; case "inputbool": itemEntry.value = $el.find(".setup-item-inputbool input").is(":checked"); break; case "inputcheckbox": itemEntry.values = []; $el.find(".setup-item-inputcheckbox input:checked").each(function () { itemEntry.values.push($(this).val()); }); break; case "inputselect": itemEntry.value = $el.find(".setup-item-inputselect select").val(); break; case "inputtags": itemEntry.value = $el.find(".setup-item-inputtags input").val(); break; } configJson.push(itemEntry) }); return configJson; } function populateSetupForm(indexerId, name, config, caps, link, alternativesitelinks, description) { var configForm = newConfigModal(name, config, caps, link, alternativesitelinks, description); var $goButton = configForm.find(".setup-indexer-go"); $goButton.click(function () { var data = getConfigModalJson(configForm); var originalBtnText = $goButton.html(); $goButton.prop('disabled', true); $goButton.html($('#spinner').html()); api.updateIndexerConfig(indexerId, data, function (data) { if (data == undefined) { configForm.modal("hide"); reloadIndexers(); doNotify("Successfully configured " + name, "success", "glyphicon glyphicon-ok"); } else if (data.result == "error") { if (data.config) { populateConfigItems(configForm, data.config); } doNotify("Configuration failed: " + data.error, "danger", "glyphicon glyphicon-alert"); } }).fail(function (data) { doErrorNotify(indexerId, data.responseJSON.error, "updating"); }).always(function () { $goButton.html(originalBtnText); $goButton.prop('disabled', false); }); }); configForm.on('hidden.bs.modal', function (e) { $('#indexers div.dataTables_filter input').focusWithoutScrolling(); }); configForm.modal("show"); } function resolveUrl(baseUrl, url) { if (baseUrl != '') { url = baseUrl + url; }else{ var a = document.createElement('a'); a.href = url; url = a.href; } return url; } function doErrorNotify(indexerId, errorMessage, errorEvent) { if (errorMessage !== undefined) { var githubRepo = "Jackett/Jackett"; var githubText = "this indexer"; var githubTemplate = "?template=bug_report.yml&" if (errorMessage.includes("FlareSolverr")) { githubRepo = "FlareSolverr/FlareSolverr"; githubText = "FlareSolverr"; githubTemplate = "?" } var githubUrl = "https://github.com/" + githubRepo + "/issues/new" + githubTemplate + "title=[" + indexerId + "] (" + errorEvent + ")"; var indexEnd = 2000 - githubUrl.length; // keep url <= 2k #5104 var htmlEscapedError = $("