// ==UserScript== // @name [RED] Cover Inspector // @namespace https://greasyfork.org/users/321857-anakunda // @version 1.11.8 // @run-at document-end // @description Adds cover sticker if needs updating for unsupported host / big size / small resolution // @author Anakunda // @copyright 2020, Anakunda (https://greasyfork.org/users/321857-anakunda) // @license GPL-3.0-or-later // @iconURL https://i.ibb.co/mh2prQR/clouseau.png // @match https://redacted.ch/torrents.php?id=* // @match https://redacted.ch/torrents.php // @match https://redacted.ch/torrents.php?action=advanced // @match https://redacted.ch/torrents.php?action=advanced&* // @match https://redacted.ch/torrents.php?*&action=advanced // @match https://redacted.ch/torrents.php?*&action=advanced&* // @match https://redacted.ch/torrents.php?action=basic // @match https://redacted.ch/torrents.php?action=basic&* // @match https://redacted.ch/torrents.php?*&action=basic // @match https://redacted.ch/torrents.php?*&action=basic&* // @match https://redacted.ch/torrents.php?page=* // @match https://redacted.ch/torrents.php?action=notify // @match https://redacted.ch/torrents.php?action=notify&* // @match https://redacted.ch/torrents.php?type=* // @match https://redacted.ch/artist.php?id=* // @connect * // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_openInTab // @require https://openuserjs.org/src/libs/Anakunda/libLocks.min.js // @require https://openuserjs.org/src/libs/Anakunda/gazelleApiLib.min.js // @downloadURL none // ==/UserScript== 'use strict'; const siteApiTimeframeStorageKey = 'AJAX time frame'; let gazelleApiFrameReserve = 100; // reserve that amount of ms for service operations let gazelleApiMutex = createMutex(), ajaxApiKey, ajaxApiLogger; if (typeof GM_getValue == 'function') switch (document.domain) { case 'redacted.ch': ajaxApiKey = GM_getValue('redacted_api_key'); break; } for (let key of ['ajaxApiKey', 'ajax_api_key', 'api_key']) if (!ajaxApiKey && key in window.localStorage && (ajaxApiKey = window.localStorage[key])) GM_setValue('redacted_api_key', ajaxApiKey); function setAjaxApiLogger(callback) { ajaxApiLogger = typeof callback == 'function' ? callback : undefined } function queryAjaxAPI(action, params, postData) { return action ? new Promise(function(resolve, reject) { let xhr = new XMLHttpRequest, retryCount = 0; params = new URLSearchParams(Object.assign({ action: action }, params || undefined)); const url = '/ajax.php?' + params.toString(); if (postData && !(postData instanceof URLSearchParams)) switch (typeof postData) { case 'object': postData = new URLSearchParams(postData); break; case 'string': try { postData = new URLSearchParams(JSON.parse(postData)) } catch(e) { } break; } postData = postData instanceof URLSearchParams ? postData.toString() : undefined; (function attempt() { gazelleApiMutex.lock(function() { let timeStamp = Date.now(), apiTimeFrame; if (siteApiTimeframeStorageKey in window.localStorage) try { apiTimeFrame = JSON.parse(window.localStorage[siteApiTimeframeStorageKey]); } catch(e) { apiTimeFrame = { } } else apiTimeFrame = { }; if (!apiTimeFrame.timeLock || timeStamp > apiTimeFrame.timeLock) { apiTimeFrame.timeLock = timeStamp + 10000 + gazelleApiFrameReserve; apiTimeFrame.requestCounter = 1; } else ++apiTimeFrame.requestCounter; window.localStorage[siteApiTimeframeStorageKey] = JSON.stringify(apiTimeFrame); gazelleApiMutex.unlock(); if (apiTimeFrame.requestCounter <= 5) { xhr.open(postData ? 'POST' : 'GET', url, true); xhr.setRequestHeader('Accept', 'application/json'); if (postData) xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8'); if (ajaxApiKey) xhr.setRequestHeader('Authorization', ajaxApiKey); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.responseType = 'json'; 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 == 'rate limit exceeded') { console.debug(xhr.response.error + ':', 'action=' + action, apiTimeFrame, timeStamp, retryCount); if (retryCount++ <= 10) return setTimeout(attempt, apiTimeFrame.timeLock - timeStamp); } else console.warn('queryAjaxAPI.attempt(…):', xhr.response.status, xhr.response.error); reject(xhr.response.status + ': ' + xhr.response.error); }; xhr.onerror = () => { reject(defaultErrorHandler(xhr)) }; xhr.ontimeout = () => { reject(defaultTimeoutHandler(xhr)) }; xhr.timeout = 20000; xhr.send(postData); } else { setTimeout(attempt, apiTimeFrame.timeLock - timeStamp); if (typeof ajaxApiLogger == 'function') ajaxApiLogger(action, apiTimeFrame, timeStamp); console.debug('AJAX API request quota exceeded: action=' + action, apiTimeFrame, timeStamp, retryCount); } }); })(); }) : Promise.reject('Action missing'); } const ajaxGetArtist = artistName => queryAjaxAPI('artist', { artistname: artistName }); const ajaxGetRequest = id => queryAjaxAPI('request', { id: id }); 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; }