// ==UserScript==
// @name Collage Extensions for Gazelle Music Trackers
// @version 1.20
// @description Click on collage size = browse through this collage; Alt + click on collage name = remove from this collage
// @author Anakunda
// @license GPL-3.0-or-later
// @copyright 2020, Anakunda (https://openuserjs.org/users/Anakunda)
// @namespace https://greasyfork.org/users/321857-anakunda
// @match https://*/torrents.php?id=*
// @match https://*/collages.php?*id=*
// @match https://*/artist.php?*id=*
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
'use strict';
var auth = document.querySelector('input[name="auth"][value]');
if (auth != null) auth = auth.value; else {
auth = document.querySelector('li#nav_logout > a');
if (auth != null && /\b(?:auth)=(\w+)\b/.test(auth.search)) auth = RegExp.$1; else throw 'Auth not found';
}
let userId = document.querySelector('li#nav_userinfo > a.username');
if (userId != null) {
userId = new URLSearchParams(userId.search);
userId = parseInt(userId.get('id'));
}
const siteApiTimeframeStorageKey = document.location.hostname + ' API time frame';
const gazelleApiFrame = 10500;
try { var redacted_api_key = GM_getValue('redacted_api_key') } catch(e) { }
function queryAjaxAPI(action, params) {
if (!action) return Promise.reject('Action missing');
let retryCount = 0;
return new Promise(function(resolve, reject) {
params = new URLSearchParams(params || undefined);
params.set('action', action);
let url = '/ajax.php?' + params, xhr = new XMLHttpRequest;
queryInternal();
function queryInternal() {
let now = Date.now();
try { var apiTimeFrame = JSON.parse(window.localStorage[siteApiTimeframeStorageKey]) } catch(e) { apiTimeFrame = {} }
if (!apiTimeFrame.timeStamp || now > apiTimeFrame.timeStamp + gazelleApiFrame) {
apiTimeFrame.timeStamp = now;
apiTimeFrame.requestCounter = 1;
} else ++apiTimeFrame.requestCounter;
window.localStorage[siteApiTimeframeStorageKey] = JSON.stringify(apiTimeFrame);
if (apiTimeFrame.requestCounter <= 5) {
xhr.open('GET', url, true);
xhr.setRequestHeader('Accept', 'application/json');
if (redacted_api_key) xhr.setRequestHeader('Authorization', redacted_api_key);
xhr.responseType = 'json';
//xhr.timeout = 5 * 60 * 1000;
xhr.onload = function() {
if (xhr.status == 404) return reject('not found');
if (xhr.status < 200 || xhr.status >= 400) return reject(defaultErrorHandler(xhr));
if (xhr.response.status == 'success') return resolve(xhr.response.response);
if (xhr.response.error == 'not found') return reject(xhr.response.error);
console.warn('queryAjaxAPI.queryInternal(...) response:', xhr, xhr.response);
if (xhr.response.error == 'rate limit exceeded') {
console.warn('queryAjaxAPI.queryInternal(...) ' + xhr.response.error + ':', apiTimeFrame, now, retryCount);
if (retryCount++ <= 10) return setTimeout(queryInternal, apiTimeFrame.timeStamp + gazelleApiFrame - now);
}
reject('API ' + xhr.response.status + ': ' + xhr.response.error);
};
xhr.onerror = function() { reject(defaultErrorHandler(xhr)) };
xhr.ontimeout = function() { reject(defaultTimeoutHandler(xhr)) };
xhr.send();
} else {
setTimeout(queryInternal, apiTimeFrame.timeStamp + gazelleApiFrame - now);
console.debug('AJAX API request quota exceeded: /ajax.php?action=' +
action + ' (' + apiTimeFrame.requestCounter + ')');
}
}
});
}
function addToTorrentCollage(collageId, torrentGroupId) {
return collageId ? torrentGroupId ? queryAjaxAPI('collage', { id: collageId }).then(
collage => !collage.torrentGroupIDList.map(groupId => parseInt(groupId)).includes(torrentGroupId) ? collageId
: Promise.reject('already in collage')
).then(collageId => new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest, formData = new URLSearchParams({
action: 'add_torrent',
collageid: collageId,
groupid: torrentGroupId,
url: document.location.origin.concat('/torrents.php?id=', torrentGroupId),
auth: auth,
});
xhr.open('POST', '/collages.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState < XMLHttpRequest.HEADERS_RECEIVED) return;
if (xhr.status >= 200 && xhr.status < 400) resolve(collageId); else reject(defaultErrorHandler(xhr));
xhr.abort();
};
xhr.onerror = function() { reject(defaultErrorHandler(xhr)) };
xhr.ontimeout = function() { reject(defaultTimeoutHandler(xhr)) };
xhr.send(formData);
})).then(collageId => queryAjaxAPI('collage', { id: collageId }).then(
collage => collage.torrentGroupIDList.map(groupId => parseInt(groupId)).includes(torrentGroupId) ? collage
: Promise.reject('Error: not added for unknown reason')
)) : Promise.reject('torrent group id not defined') : Promise.reject('collage id not defined');
}
function removeFromTorrentCollage(collageId, torrentGroupId, question) {
if (!confirm(question)) return Promise.reject('Cancelled');
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest, formData = new URLSearchParams({
action: 'manage_handle',
collageid: collageId,
groupid: torrentGroupId,
auth: auth,
submit: 'Remove',
});
xhr.open('POST', '/collages.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState < XMLHttpRequest.HEADERS_RECEIVED) return;
if (xhr.status >= 200 && xhr.status < 400) resolve(xhr.status); else reject(defaultErrorHandler(xhr));
xhr.abort();
};
xhr.onerror = function() { reject(defaultErrorHandler(xhr)) };
xhr.ontimeout = function() { reject(defaultTimeoutHandler(xhr)) };
xhr.send(formData);
});
}
function addToArtistCollage(collageId, artistId) {
return collageId ? artistId ? queryAjaxAPI('collage', { id: collageId }).then(
collage => !collage.artists.map(artist => parseInt(artist.id)).includes(artistId) ? collageId
: Promise.reject('already in collage')
).then(collageId => new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest, formData = new URLSearchParams({
action: 'add_artist',
collageid: collageId,
artistid: artistId,
url: document.location.origin.concat('/artist.php?id=', artistId),
auth: auth,
});
xhr.open('POST', '/collages.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState < XMLHttpRequest.HEADERS_RECEIVED) return;
if (xhr.status >= 200 && xhr.status < 400) resolve(collageId); else reject(defaultErrorHandler(xhr));
xhr.abort();
};
xhr.onerror = function() { reject(defaultErrorHandler(xhr)) };
xhr.ontimeout = function() { reject(defaultTimeoutHandler(xhr)) };
xhr.send(formData);
})).then(collageId => queryAjaxAPI('collage', { id: collageId }).then(
collage => collage.artists.map(artist => parseInt(artist.id)).includes(artistId) ? collage
: Promise.reject('Error: not added for unknown reason')
)) : Promise.reject('artist id not defined') : Promise.reject('collage id not defined');
}
function removeFromArtistCollage(collageId, artistId, question) {
if (!confirm(question)) return Promise.reject('Cancelled');
return new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest, formData = new URLSearchParams({
action: 'manage_artists_handle',
collageid: collageId,
artistid: artistId,
auth: auth,
submit: 'Remove',
});
xhr.open('POST', '/collages.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState < XMLHttpRequest.HEADERS_RECEIVED) return;
if (xhr.status >= 200 && xhr.status < 400) resolve(xhr.status); else reject(defaultErrorHandler(xhr));
xhr.abort();
};
xhr.onerror = function() { reject(defaultErrorHandler(xhr)) };
xhr.ontimeout = function() { reject(defaultTimeoutHandler(xhr)) };
xhr.send(formData);
});
}
function defaultErrorHandler(response) {
console.error('HTTP error:', response);
let e = 'HTTP error ' + response.status;
if (response.statusText) e += ' (' + response.statusText + ')';
if (response.error) e += ' (' + response.error + ')';
return e;
}
function defaultTimeoutHandler(response) {
console.error('HTTP timeout:', response);
const e = 'HTTP timeout';
return e;
}
function addQuickAddForm() {
if (!userId || !torrentGroupId && !artistId) return; // User id missing
let ref = document.querySelector('div.sidebar');
if (ref == null) return; // Sidebar missing
const addSuccess = 'Successfully added to collage.';
const alreadyInCollage = 'Error: This ' +
(torrentGroupId ? 'torrent group' : artistId ? 'artist' : null) + ' is already in this collage';
new Promise(function(resolve, reject) {
try {
var categories = JSON.parse(GM_getValue(document.location.hostname + '-categories'));
if (categories.length > 0) resolve(categories); else throw 'empty list cached';
} catch(e) {
let xhr = new XMLHttpRequest;
xhr.open('GET', '/collages.php', true);
xhr.responseType = 'document';
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 400) {
categories = [ ];
xhr.response.querySelectorAll('tr#categories > td > label').forEach(function(label, index) {
let input = xhr.response.querySelector('tr#categories > td > input#' + label.htmlFor);
categories[input != null && /\[(\d+)\]/.test(input.name) ? parseInt(RegExp.$1) : index] = label.textContent.trim();
});
if (categories.length > 0) {
GM_setValue(document.location.hostname + '-categories', JSON.stringify(categories));
resolve(categories);
} else reject('Site categories could not be extracted');
} else reject(defaultErrorHandler(xhr));
};
xhr.onerror = function() { reject(defaultErrorHandler(xhr)) };
xhr.ontimeout = function() { reject(defaultTimeoutHandler()) };
xhr.send();
}
}).then(function(categories) {
const artistsIndexes = categories
.map((category, index) => /^(?:Artists)$/i.test(category) ? index : -1)
.filter(index => index >= 0);
if (artistId && artistsIndexes.length <= 0) throw 'Artists index not found';
const isCompatibleCategory = categoryId => categoryId >= 0 && categoryId < categories.length
&& (torrentGroupId && !artistsIndexes.includes(categoryId) || artistId && artistsIndexes.includes(categoryId));
document.head.appendChild(document.createElement('style')).innerHTML = `
form#addtocollage optgroup { background-color: slategray; color: white; }
form#addtocollage option { background-color: white; color: black; max-width: 290pt; }
div.box_addtocollage > form { padding: 0px 10px; }
`;
let elem = document.createElement('div');
elem.className = 'box box_addtocollage';
elem.style = 'padding: 0 0 10px;';
elem.innerHTML = `
<div class="head" style="margin-bottom: 5px;"><strong>Add to Collage</strong></div>
<div id="ajax_message" class="hidden center" style="padding: 7px 0px;"></div>
<form id="searchcollages">
<input id="searchforcollage" placeholder="Collage search" type="text" style="max-width: 10em;">
<input id="searchforcollagebutton" value="Search" type="submit" style="max-width: 4em;">
</form>
<form id="addtocollage" class="add_form" name="addtocollage">
<select name="collageid" id="matchedcollages" class="add_to_collage_select" style="width: 96%;">
<input id="opencollage-btn" value="Open collage" type="button">
<input id="addtocollage-btn" value="Add to collage" type="button">
</form>
`;
ref.append(elem);
let ajaxMessage = document.getElementById('ajax_message');
let srchForm = document.getElementById('searchcollages');
if (srchForm == null) throw new Error('#searchcollages missing');
let searchText = document.getElementById('searchforcollage');
if (searchText == null) throw new Error('#searchforcollage missing');
let dropDown = document.getElementById('matchedcollages');
if (dropDown == null) throw new Error('#matchedcollages missing');
let doOpen = document.getElementById('opencollage-btn');
let doAdd = document.getElementById('addtocollage-btn');
if (doAdd == null) throw new Error('#addtocollage-btn missing');
srchForm.onsubmit = searchSubmit;
searchText.ondrop = evt => dataHandler(evt.target, evt.dataTransfer);
searchText.onpaste = evt => dataHandler(evt.target, evt.clipboardData);
if (doOpen != null) doOpen.onclick = openCollage;
doAdd.onclick = addToCollage;
let initTimeCap = GM_getValue('max_preload_time', 0); // max time in ms to preload the dropdown
if (initTimeCap > 0) findCollages({ userid: userId, contrib: 1 }, initTimeCap);
function clearList() {
while (dropDown.childElementCount > 0) dropDown.removeChild(dropDown.firstElementChild);
}
function findCollages(query, maxSearchTime) {
return typeof query == 'object' ? new Promise(function(resolve, reject) {
let start = Date.now();
searchFormEnable(false);
clearList();
elem = document.createElement('option');
elem.text = 'Searching...';
dropDown.add(elem);
dropDown.selectedIndex = 0;
let retryCount = 0, options = [ ];
searchInternal();
function searchInternal(page) {
if (maxSearchTime > 0 && Date.now() - start > maxSearchTime) {
reject('Time limit exceeded');
return;
}
let xhr = new XMLHttpRequest, _query = new URLSearchParams(query);
if (!page) page = 1;
_query.set('page', page);
xhr.open('GET', '/collages.php?' + _query, true);
xhr.responseType = 'document';
xhr.onload = function() {
if (xhr.status < 200 || xhr.status >= 400) throw defaultErrorHandler(xhr);
xhr.response.querySelectorAll('table.collage_table > tbody > tr[class^="row"]').forEach(function(tr, rowNdx) {
if ((ref = tr.querySelector(':scope > td:nth-of-type(1) > a')) == null) {
console.warn('Page parsing error');
return;
}
elem = document.createElement('option');
if ((elem.category = categories.findIndex(category => category.toLowerCase() == ref.textContent.toLowerCase())) < 0
&& /\b(?:cats)\[(\d+)\]/i.test(ref.search)) elem.category = parseInt(RegExp.$1); // unsafe due to site bug
if ((ref = tr.querySelector(':scope > td:nth-of-type(2) > a')) == null || !/\b(?:id)=(\d+)\b/i.test(ref.search)) {
console.warn(`Unknown collage id (${xhr.responseURL}/${rowNdx})`);
return;
}
elem.value = elem.collageId = parseInt(RegExp.$1);
elem.text = elem.title = ref.textContent.trim();
if ((ref = tr.querySelector(':scope > td:nth-of-type(3)')) != null) elem.size = parseInt(ref.textContent);
if ((ref = tr.querySelector(':scope > td:nth-of-type(4)')) != null) elem.subscribers = parseInt(ref.textContent);
if ((ref = tr.querySelector(':scope > td:nth-of-type(6) > a')) != null
&& /\b(?:id)=(\d+)\b/i.test(ref.search)) elem.author = parseInt(RegExp.$1);
if (isCompatibleCategory(elem.category) && (elem.category != 0 || elem.author == userId)) options.push(elem);
});
if (xhr.response.querySelector('div.linkbox > a.pager_next') != null) searchInternal(page + 1); else {
if (!Object.keys(query).includes('order'))
options.sort((a, b) => (b.size || 0) - (a.size || 0)/*a.title.localeCompare(b.title)*/);
resolve(options);
}
};
xhr.onerror = function() {
if (xhr.status == 0 && retryCount++ <= 10) setTimeout(function() { searchInternal(page) }, 200);
else reject(defaultErrorHandler(xhr));
};
xhr.ontimeout = function() { reject(defaultTimeoutHandler()) };
xhr.send();
}
}).then(function(options) {
clearList();
categories.forEach(function(category, ndx) {
let _category = options.filter(option => option.category == ndx);
if (_category.length <= 0) return;
elem = document.createElement('optgroup');
elem.label = category;
elem.append(..._category);
dropDown.add(elem);
});
dropDown.selectedIndex = 0;
searchFormEnable(true);
return options;
}).catch(function(reason) {
clearList();
searchFormEnable(true);
console.warn(reason);
}) : Promise.reject('Invalid parameter');
}
function searchFormEnable(enabled) {
for (let i = 0; i < srchForm.length; ++i) srchForm[i].disabled = !enabled;
}
function searchSubmit(evt) {
let searchTerm = searchText.value.trim();
if (searchTerm.length <= 0) return false;
let query = {
action: 'search',
search: searchTerm,
type: 'c.name',
order: 'Updated',
sort: 'desc',
order_way: 'Descending',
};
categories.map((category, index) => 'cats[' + index + ']')
.filter((category, index) => isCompatibleCategory(index))
.forEach(index => { query[index] = 1 });
findCollages(query);
return false;
}
function addToCollage(evt) {
(function() {
evt.target.disabled = true;
if (ajaxMessage != null) ajaxMessage.classList.add('hidden');
let collageId = parseInt(dropDown.value);
if (!collageId) return Promise.reject('No collage selected');
/*
if (Array.from(document.querySelectorAll('table.collage_table > tbody > tr:not([class="colhead"]) > td > a'))
.map(node => /\b(?:id)=(\d+)\b/i.test(node.search) && parseInt(RegExp.$1)).includes(collageId))
return Promise.reject(alreadyInCollage);
*/
if (torrentGroupId) return addToTorrentCollage(collageId, torrentGroupId);
if (artistId) return addToArtistCollage(collageId, artistId);
return Promise.reject('munknown page class');
})().then(function(collage) {
if (ajaxMessage != null) {
ajaxMessage.innerHTML = '<span style="color: #0A0;">' + addSuccess + '</span>';
ajaxMessage.classList.remove('hidden');
}
evt.target.disabled = false;
let mainColumn = document.querySelector('div.main_column');
if (mainColumn == null) return collage;
let tableName = collage.collageCategoryID != 0 ? 'collages' : 'personal_collages'
let tbody = mainColumn.querySelector('table#' + tableName + ' > tbody');
if (tbody == null) {
tbody = document.createElement('tbody');
tbody.innerHTML = '<tr class="colhead"><td width="85%"><a href="#">↑</a> </td><td># torrents</td></tr>';
elem = document.createElement('table');
elem.id = tableName;
elem.className = 'collage_table';
elem.append(tbody);
mainColumn.insertBefore(elem, [
'table#personal_collages', 'table#vote_matches', 'div.torrent_description',
'div#similar_artist_map', 'div#artist_information',
].reduce((acc, selector) => acc || document.querySelector(selector), null));
}
tableName = '\xA0This ' + (artistsIndexes.includes(collage.collageCategoryID) ? 'artist' : 'album') + ' is in ' +
tbody.childElementCount + ' ' + (collage.collageCategoryID != 0 ? 'collage' : 'personal collage');
if (tbody.childElementCount > 1) tableName += 's';
tbody.firstElementChild.firstElementChild.childNodes[1].data = tableName;
elem = document.createElement('tr');
elem.className = 'collage_rows';
if (tbody.querySelector('tr.collage_rows.hidden') != null) elem.classList.add('hidden');
elem.innerHTML = '<td><a href="/collages.php?id=' + collage.id + '">' + collage.name + '</a></td><td class="number_column">' +
collage[artistsIndexes.includes(collage.collageCategoryID) ? 'artists' : 'torrentgroups'].length + '</td>';
tbody.append(elem);
return collage;
}).catch(function(reason) {
evt.target.disabled = false;
if (ajaxMessage == null) return;
ajaxMessage.innerHTML = '<span style="color: #A00;">' + reason.toString() + '</span>';
ajaxMessage.classList.remove('hidden');
});
}
function openCollage(evt) {
let collageId = parseInt(dropDown.value);
if (collageId <= 0) return false;
let win = window.open('/collages.php?id=' + collageId, '_blank');
win.focus();
}
function dataHandler(target, data) {
var text = data.getData('text/plain');
if (!text) return true;
target.value = text;
srchForm.onsubmit();
return false;
}
});
}
switch (document.location.pathname) {
case '/torrents.php': {
var torrentGroupId = new URLSearchParams(document.location.search).get('id'), collages;
if (torrentGroupId) torrentGroupId = parseInt(torrentGroupId); else break; // Unexpected URL format
const searchforcollage = document.getElementById('searchforcollage');
if (searchforcollage != null) {
if (typeof SearchCollage == 'function') SearchCollage = () => {
const searchTerm = $('#searchforcollage').val(),
personalCollages = $('#personalcollages');
ajax.get(`ajax.php?action=collages&search=${encodeURIComponent(searchTerm)}`, responseText => {
const { response, status } = JSON.parse(responseText);
if (status !== 'success') return;
const categories = response.reduce((accumulator, item) => {
const { collageCategoryName } = item;
accumulator[collageCategoryName] = (accumulator[collageCategoryName] || []).concat(item);
return accumulator;
}, {});
personalCollages.children().remove();
Object.entries(categories).forEach(([category, collages]) => {
console.log(collages);
personalCollages.append(`
<optgroup label="${category}">
${collages.reduce((accumulator, { id, name }) =>
`${accumulator}<option value="${id}">${name}</option>`
,'')}
</optgroup>
`);
});
});
};
function inputHandler(evt, key) {
const data = evt[key].getData('text/plain').trim();
if (!data) return true;
evt.target.value = data;
SearchCollage();
setTimeout(function() {
const add_to_collage_select = document.querySelector('select.add_to_collage_select');
if (add_to_collage_select != null && add_to_collage_select.options.length > 1) {
// TODO: expand
}
}, 3000);
return false;
}
searchforcollage.onpaste = evt => inputHandler(evt, 'clipboardData');
searchforcollage.ondrop = evt => inputHandler(evt, 'dataTransfer');
searchforcollage.onkeypress = evt => { if (evt.key == 'Enter') SearchCollage() };
} else addQuickAddForm();
try { collages = JSON.parse(window.sessionStorage.collages) } catch(e) { collages = { } }
if (!collages[document.domain]) collages[document.domain] = { };
document.querySelectorAll('table[id$="collages"] > tbody > tr > td > a').forEach(function(link) {
if (!link.pathname.startsWith('/collages.php') || !/\b(?:id)=(\d+)\b/.test(link.search)) return;
let collageId = parseInt(RegExp.$1), toggle, navLinks = [],
numberColumn = link.parentNode.parentNode.querySelector('td.number_column');
link.onclick = function(evt) {
return evt.button == 0 && evt.altKey ? removeFromTorrentCollage(collageId, torrentGroupId,
'Are you sure to remove this group from collage "' + link.textContent.trim() + '"?')
.then(status => { link.parentNode.parentNode.remove() }) : true;
};
link.title = 'Use Alt + left click to remove from this collage';
if (numberColumn != null) {
numberColumn.style.cursor = 'pointer';
numberColumn.onclick = loadCollage;
numberColumn.title = collages[document.domain][collageId] ? 'Refresh' : 'Load collage for direct browsing';
}
if (collages[document.domain][collageId]) {
expandSection();
addCollageLinks(collages[document.domain][collageId]);
}
function addCollageLinks(collage) {
var index = collage.torrentgroups.findIndex(group => group.id == torrentGroupId);
if (index < 0) {
console.warn('Assertion failed: torrent', torrentGroupId, 'not found in the collage', collage);
return false;
}
link.style.color = 'white';
link.parentNode.parentNode.style = 'color:white; background-color: darkgoldenrod;';
var stats = document.createElement('span');
stats.textContent = `${index + 1} / ${collage.torrentgroups.length}`;
stats.style = 'font-size: 8pt; color: antiquewhite; font-weight: 100; margin-left: 10px;';
navLinks.push(stats);
link.parentNode.append(stats);
if (collage.torrentgroups[index - 1]) {
var a = document.createElement('a');
a.href = '/torrents.php?id=' + collage.torrentgroups[index - 1].id;
a.textContent = '[\xA0<\xA0]';
a.title = getTitle(index - 1);
a.style = 'color: chartreuse; margin-right: 10px;';
navLinks.push(a);
link.parentNode.prepend(a);
a = document.createElement('a');
a.href = '/torrents.php?id=' + collage.torrentgroups[0].id;
a.textContent = '[\xA0<<\xA0]';
a.title = getTitle(0);
a.style = 'color: chartreuse; margin-right: 5px;';
navLinks.push(a);
link.parentNode.prepend(a);
}
if (collage.torrentgroups[index + 1]) {
a = document.createElement('a');
a.href = '/torrents.php?id=' + collage.torrentgroups[index + 1].id;
a.textContent = '[\xA0>\xA0]';
a.title = getTitle(index + 1);
a.style = 'color: chartreuse; margin-left: 10px;';
navLinks.push(a);
link.parentNode.append(a);
a = document.createElement('a');
a.href = '/torrents.php?id=' + collage.torrentgroups[collage.torrentgroups.length - 1].id;
a.textContent = '[\xA0>>\xA0]';
a.title = getTitle(collage.torrentgroups.length - 1);
a.style = 'color: chartreuse; margin-left: 5px;';
navLinks.push(a);
link.parentNode.append(a);
}
return true;
function getTitle(index) {
if (typeof index != 'number' || index < 0 || index >= collage.torrentgroups.length) return undefined;
let title = collage.torrentgroups[index].musicInfo && Array.isArray(collage.torrentgroups[index].musicInfo.artists) ?
collage.torrentgroups[index].musicInfo.artists.map(artist => artist.name).join(', ') + ' - ' : '';
if (collage.torrentgroups[index].name) title += collage.torrentgroups[index].name;
if (collage.torrentgroups[index].year) title += ' (' + collage.torrentgroups[index].year + ')';
return title;
}
}
function expandSection() {
if (toggle === undefined) toggle = link.parentNode.parentNode.parentNode.querySelector('td > a[href="#"][onclick]');
if (toggle === null || toggle.dataset.expanded) return false;
toggle.dataset.expanded = true;
toggle.click();
return true;
}
function loadCollage(evt) {
evt.target.disabled = true;
navLinks.forEach(a => { a.remove() });
navLinks = [];
var span = document.createElement('span');
span.textContent = '[\xA0loading...\xA0]';
span.style = 'color: red; background-color: white; margin-left: 10px;';
link.parentNode.append(span);
queryAjaxAPI('collage', { id: collageId }).then(function(collage) {
span.remove();
cacheCollage(collage);
addCollageLinks(collage);
evt.target.disabled = false;
}, function(reason) {
span.remove();
evt.target.disabled = false;
});
return false;
}
});
function cacheCollage(collage) {
collages[document.domain][collage.id] = {
id: collage.id,
name: collage.name,
torrentgroups: collage.torrentgroups.map(group => ({
id: group.id,
musicInfo: group.musicInfo ? {
artists: Array.isArray(group.musicInfo.artists) ?
group.musicInfo.artists.map(artist => ({ name: artist.name })) : undefined,
} : undefined,
name: group.name,
year: group.year,
})),
};
window.sessionStorage.collages = JSON.stringify(collages);
}
break;
}
case '/artist.php': {
var artistId = new URLSearchParams(document.location.search).get('id');
if (artistId) artistId = parseInt(artistId); else break; // Unexpected URL format
addQuickAddForm();
break;
}
case '/collages.php': {
var collageId = new URLSearchParams(document.location.search).get('id');
if (collageId) collageId = parseInt(collageId); else break; // Collage id missing
let category = document.querySelector('div.box_category > div.pad > a');
category = category != null ? category.textContent : undefined;
function scanPage() {
if (category != 'Artists') {
var sel = [
'tr.group > td[colspan] > strong > a[href^="torrents.php?id="]',
'ul.collage_images > li > a[href^="torrents.php?id="]',
]
document.querySelectorAll(sel.join(', ')).forEach(function(a) {
a.onclick = function(evt) {
if (evt.button != 0 || !evt.altKey) return true;
let torrentGroupId = new URLSearchParams(a.search);
torrentGroupId = torrentGroupId.get('id');
if (torrentGroupId) torrentGroupId = parseInt(torrentGroupId); else {
console.warn('Assertion failed: no id', a);
throw 'no id';
}
removeFromTorrentCollage(collageId, torrentGroupId, 'Are you sure to remove selected group from this collage?').then(function(status) {
document.querySelectorAll(sel.join(', ')).forEach(function(a) {
if (parseInt(new URLSearchParams(a.search).get('id')) == torrentGroupId) switch (a.parentNode.nodeName) {
case 'STRONG': a.parentNode.parentNode.parentNode.remove(); break;
case 'LI': a.parentNode.remove(); break;
}
});
});
};
});
} else {
sel = [
'table#discog_table > tbody > tr > td > a[href^="artist.php?id="]',
'ul.collage_images > li > a[href^="artist.php?id="]',
];
document.querySelectorAll(sel.join(', ')).forEach(function(a) {
a.onclick = function(evt) {
if (evt.button != 0 || !evt.altKey) return true;
let artistId = new URLSearchParams(a.search);
artistId = artistId.get('id');
if (artistId) artistId = parseInt(artistId); else {
console.warn('Assertion failed: no id', a);
throw 'no id';
}
removeFromArtistCollage(collageId, artistId, 'Are you sure to remove selected artist from this collage?').then(function(status) {
document.querySelectorAll(sel.join(', ')).forEach(function(a) {
if (parseInt(new URLSearchParams(a.search).get('id')) == artistId) switch (a.parentNode.nodeName) {
case 'TD': a.parentNode.parentNode.remove(); break;
case 'LI': a.parentNode.remove(); break;
}
});
});
};
});
let artistLink = document.querySelector('form.add_form[name="artist"] input#artist');
if (artistLink != null) {
let ref = document.querySelector('form.add_form[name="artist"] > div.submit_div');
let searchBtn = document.createElement('input');
searchBtn.value = 'Look up';
searchBtn.type = 'button';
searchBtn.onclick = function(evt) {
let xhr = new XMLHttpRequest;
xhr.open('HEAD', '/artist.php?artistname=' + encodeURIComponent(artistLink.value.trim()), true);
xhr.onreadystatechange = function() {
if (xhr.readyState < XMLHttpRequest.HEADERS_RECEIVED) return;
artistLink.value = xhr.responseURL.includes('/artist.php?id=') ? xhr.responseURL : '';
};
xhr.send();
};
ref.append(searchBtn);
}
}
}
scanPage();
document.querySelectorAll('div#pageslinksdiv > span > a.pageslink')
.forEach(a => { a.addEventListener('click', evt => { setTimeout(scanPage, 1000) }) });
break;
}
}