// ==UserScript== // @name AB - Mark Sneedex Releases // @description Tags the best releases on animebytes according to https://sneedex.moe/ // @namespace TalkingJello@animebytes.tv // @match *://animebytes.tv/* // @grant GM_addElement // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @version 1.1.1 // @author TalkingJello // @icon http://animebytes.tv/favicon.ico // @connect sneedex.moe // @license MIT // @downloadURL none // ==/UserScript== // Thanks to garret who made the nyaa script https://tilde.club/~garret/userscripts/nyaablue.user.js // which I stole sneedex related code from const DEX = "https://sneedex.moe" const CACHE_TIME = 1000*60*60*2; // 2 hours function log(...rest) { console.log("[Mark Sneedex Releases]", ...rest) } function gmFetchJson(opts, timeout = 10000) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ ...opts, timeout, ontimeout: function() { reject(new Error(`Request timed out after ${timeout}ms`)); }, onerror: function(err) { reject(err ? err : new Error('Failed to fetch')) }, onload: function(response) { console.log('onload', response) resolve(JSON.parse(response.responseText)); } }) }); } async function fetchSneedex(route) { // cache check const lastUpdate = GM_getValue(`cache_last_update_${route}`) if (typeof lastUpdate === "number" && Date.now() < lastUpdate + CACHE_TIME) { const cached = GM_getValue(`cache_map_${route}`) if (typeof cached === "object") { log(`cache hit for route ${route}`); return cached } } // fetch api log(`fetching sneedex api for route ${route}`); const res = await gmFetchJson({ headers: { "Accept": "application/json", "User-Agent": "ab-mark-sneedex-releases.user.js" }, method: "GET", url: DEX + route }); const linkMap = {}; res.forEach(entry => { entry.permLinks.forEach(l => { linkMap[l] = {id: entry.entryID} }); }) GM_setValue(`cache_map_${route}`, linkMap) GM_setValue(`cache_last_update_${route}`, Date.now()) return linkMap } // Thanks to https://github.com/momentary0/AB-Userscripts/blob/master/torrent-highlighter/src/tfm_torrent_highlighter.user.js#L470 // for the handy selectors function torrentsOnPage() { const torrentPageTorrents = [...document.querySelectorAll( '.group_torrent>td>a[href*="&torrentid="]' )].map(a => ({ a, seperator: a.href.includes('torrents.php') ? ' | ' : ' / ' })); const searchResultTorrents = [...document.querySelectorAll( '.torrent_properties>a[href*="&torrentid="]' )].map(a => ({ a, seperator: ' | ' })); /*const bbcodeTorrents = [...document.querySelectorAll( ':not(.group_torrent)>:not(.torrent_properties)>a[href*="/torrent/"]:not([title])', )].map(a => ({ a, seperator: a.href.includes('torrents.php') ? ' | ' : ' / ' }));*/ return [...torrentPageTorrents, ...searchResultTorrents] } function insertTag(parent, {title, src, alt, onclick}) { const img = GM_addElement(parent, 'img', { src, alt, title }); img.addEventListener('click', onclick) } (async function () { try { const linkMap = await fetchSneedex("/api/public/ab") const torrents = torrentsOnPage(); torrents.forEach(t => { const entry = linkMap[t.a.href]; if (!entry) { return; } t.a.append(t.seperator); let parent = t.a; if (t.a.classList.contains('userscript-highlight')) { // highlight already ran parent = document.createElement('span'); parent.className = "userscript-highlight torrent-field"; parent.dataset.sneedex = "Sneedex"; parent.dataset.field = "Sneedex"; t.a.append(parent); } insertTag(parent, { dataAttr: "data-sneedex", title: "This release is sneedex approved", src: "https://ptpimg.me/n40di4.png", alt: "Sneedex Choice!", onclick: (e) => { e.preventDefault() e.stopImmediatePropagation() window.open(`${DEX}/?${entry.id}`, '_blank').focus() } }); }); } catch (err) { alert(`Failed to fetch sneedex for best releases - ${err.message ? err.message : err}`); } })();