Greasy Fork 支持简体中文。

Torrent Collage Extensions for Gazelle

Click on collage size = browse through this collage; Alt + click on collage name = remove from this collage

目前為 2020-11-28 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Torrent Collage Extensions for Gazelle
// @version      1.04
// @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=*
// @grant        GM_getValue
// ==/UserScript==

'use strict';

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=${escape(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.getElementsByTagName('option').length == 1)
// 				AddToCollage();
// 		}, 2000);
		return false;
	}
	searchforcollage.onpaste = evt => inputHandler(evt, 'clipboardData');
	searchforcollage.ondrop = evt => inputHandler(evt, 'dataTransfer');
}

if (!/\b(?:id)=(\d+)\b/i.test(document.location.search)) throw 'Unexpected URL format';
var torrentId = parseInt(RegExp.$1), auth = document.querySelector('input[name="auth"]');
if (auth == null) {
  auth = document.querySelector('li#nav_logout > a.user-info-bar__link');
  if (auth == null || !/\b(?:auth)=(\w+)\b/.test(auth.search)) throw 'Auth not found';
  auth = RegExp.$1;
} else auth = auth.value;
const siteApiTimeframeStorageKey = document.location.hostname + ' API time frame';
const gazelleApiFrame = 10500;
if (typeof GM_getValue == 'function') var redacted_api_key = GM_getValue('redacted_api_key');
try { var 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;
  var collageId = parseInt(RegExp.$1), toggle, navLinks = [],
	  numberColumn = link.parentNode.parentNode.querySelector('td.number_column');
  link.onclick = evt => evt.button == 0 && evt.altKey ? removeFromCollage(collageId, link) : 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 == torrentId);
	if (index < 0) {
	  console.warn('Assertion failed: torrent', torrentId, '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;
	  var 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);
}

function removeFromCollage(collageId, node = null) {
  if (!confirm('Are you sure to remove from collage "' + node.textContent.trim() + '"?')) return false;
  let xhr = new XMLHttpRequest, params = new URLSearchParams({
	action: 'manage_handle',
	collageid: collageId,
	groupid: torrentId,
	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) {
	  if (node instanceof HTMLElement) node.parentNode.parentNode.remove(); else document.location.reload();
	} else errorHandler();
	xhr.abort();
  };
  xhr.onerror = errorHandler;
  xhr.send(params);
  return false;

  function errorHandler() { console.error('Failed to remove', torrentId, 'from collage', collageId, xhr) }
}

function queryAjaxAPI(action, params) {
  if (!action) return Promise.reject('Action missing');
  var retryCount = 0;
  return new Promise(function(resolve, reject) {
	params = new URLSearchParams(params || undefined);
	params.set('action', action);
	var url = '/ajax.php?' + params, xhr = new XMLHttpRequest;
	queryInternal();

	function queryInternal() {
	  var 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 defaultErrorHandler(response) {
  console.error('HTTP error:', response);
  var 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;
}