StashDB x 1337x

This script tries to match the scenes on stash.db to torrents on 1337x.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         StashDB x 1337x
// @license      MIT
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  This script tries to match the scenes on stash.db to torrents on 1337x.
// @match        https://stashdb.org/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=stashdb.org
// @grant        GM_xmlhttpRequest
// ==/UserScript==

function GetResponse(url, type) {
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            url: url,
            responseType: type,
            onload: function(response) {
                resolve(response.response);
            },
            onerror: function(error) {
                reject(error);
            }
        });
    });
}

function GetLinks(doc) {
    const links = [];
    const rows = doc.querySelectorAll('div.table-list-wrap table tbody tr');
    rows.forEach(row => {
        const long = row.querySelector('td:nth-child(1) a:nth-child(2)').textContent;
        const link = `https://1337x.to${row.querySelector('td:nth-child(1) a:nth-child(2)').getAttribute('href')}`;
        const size = row.querySelector('td:nth-child(5)').childNodes[0].textContent;
        links.push([long, link, size]);
    });
    return links;
}

async function GetTags(studio, scene, date, performers) {
    // generate keywords
    const studios = [...new Set([studio, studio.replaceAll(' ', '')])];
    const scenes = [scene];
    const dates = [date, date.substring(2)];
    const keywords = [];
    studios.forEach(a => {
        const pairing = [].concat(scenes, dates, performers);
        pairing.forEach(b => {
            keywords.push(`${a} ${b}`);
        });
    });
    dates.forEach(a => {
        const pairing = [].concat(scenes, performers);
        pairing.forEach(b => {
            keywords.push(`${a} ${b}`);
        });
    });

    // get all links
    const links = [];
    await Promise.all(keywords.map(async keyword => {
        //const url = `https://1337x.to/sort-search/${keyword}/time/desc/1/`;
        const url = `https://1337x.to/search/${keyword}/1/`;
        const doc = await GetResponse(url, 'document');
        links.push(...GetLinks(doc));
    }));
    console.log(keywords, links);

    // sort links
    const countMap = new Map();
    const linkMap = new Map();
    links.forEach(link => {
        const count = countMap.get(link[1]);
        if (count == undefined) {
            countMap.set(link[1], 1);
            linkMap.set(link[1], link);
        } else {
            countMap.set(link[1], count + 1);
        }
    });
    const sortedLinks = [...countMap.entries()].sort((a, b) => {
        const hits = b[1] - a[1];
        function Convert(str) {
            str = str.replace(',', '');
            str = str.replace(' MB', '*1e3');
            str = str.replace(' GB', '*1e6');
            try {
                const value = eval(str);
                return value;
            } catch {
                console.log('Bad Value: ', str);
                return 0;
            }
        }
        const sizes = Convert(linkMap.get(b[0])[2]) - Convert(linkMap.get(a[0])[2]);
        return hits * 1e9 + sizes;
    });
    const result = [];
    function GetShortName(long) {
        return long;
        var shortName = long.replaceAll('.', ' ').toLowerCase();
        const words = [].concat(scenes, studios, performers);
        words.forEach(word => {
            shortName = shortName.replaceAll(word.toLowerCase(), '');
        });
        const YY = date.substring(2, 4);
        const MM = date.substring(5, 7);
        const DD = date.substring(8, 10);
        const wordsToDelete = [`${YY} ${MM} ${DD}`, `20${YY} ${MM} ${DD}`, `${DD} ${MM} 20${YY}`, 'xxx', 'and'];
        wordsToDelete.forEach(word => {
            shortName = shortName.replaceAll(word.toLowerCase(), '');
        });
        return shortName;
    }
    sortedLinks.forEach(entry => {
        const link = linkMap.get(entry[0]);
        result.push([`[${entry[1]} hits] [${link[2]}] ${GetShortName(link[0])}`, link[0], link[1]]);
    });
    return result;
}

function WrapTags(tags) {
    const ul = document.createElement('ul');
    ul.style.overflowY = 'scroll';  // Enable vertical scrolling
    ul.style.maxHeight = '100px';   // Limit the height to 200 pixels
    ul.setAttribute('class', 'scene-tag-list');
    tags.forEach(tag => {
        const a = document.createElement('a');
        a.setAttribute('href', tag[2]);
        a.appendChild(document.createTextNode(tag[0]));
        const abbr = document.createElement('abbr');
        abbr.setAttribute('title', tag[1]);
        abbr.appendChild(a);
        const span = document.createElement('span');
        span.setAttribute('class', 'tag-item badge bg-none');
        span.appendChild(abbr);
        const li = document.createElement('li');
        li.appendChild(span);
        ul.appendChild(li);
    });
    return ul;
}

async function GetPerformers(url) {
    const data = await new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: 'POST',
            responseType: 'json',
            data: `{"operationName":"Scene","variables":{"id":"${url.substring(url.lastIndexOf('/') + 1)}"},"query":"query Scene($id: ID!) {\n  findScene(id: $id) {\n    ...SceneFragment\n    __typename\n  }\n}\n\nfragment URLFragment on URL {\n  url\n  site {\n    id\n    name\n    icon\n    __typename\n  }\n  __typename\n}\n\nfragment ImageFragment on Image {\n  id\n  url\n  width\n  height\n  __typename\n}\n\nfragment ScenePerformerFragment on Performer {\n  id\n  name\n  disambiguation\n  deleted\n  gender\n  aliases\n  __typename\n}\n\nfragment SceneFragment on Scene {\n  id\n  release_date\n  title\n  deleted\n  details\n  director\n  code\n  duration\n  urls {\n    ...URLFragment\n    __typename\n  }\n  images {\n    ...ImageFragment\n    __typename\n  }\n  studio {\n    id\n    name\n    parent {\n      id\n      name\n      __typename\n    }\n    __typename\n  }\n  performers {\n    as\n    performer {\n      ...ScenePerformerFragment\n      __typename\n    }\n    __typename\n  }\n  fingerprints {\n    hash\n    algorithm\n    duration\n    submissions\n    user_submitted\n    created\n    updated\n    __typename\n  }\n  tags {\n    id\n    name\n    description\n    aliases\n    __typename\n  }\n  __typename\n}"}`.replaceAll('\n', ''),
            url: 'https://stashdb.org/graphql',
            headers: { "Content-Type": "application/json" },
            onload: function(response) {
                resolve(response.response);
            },
            onerror: function(error) {
                reject(error);
            }
        });
    });
    const performers = data.data.findScene.performers;
    const result = [];
    performers.forEach(performer => {
        //console.log(performer);
        if (performer.performer.gender != 'MALE') {
            result.push(performer.performer.name);
        }
    });
    return result;
}

function SearchTorrents() {
    const cards = document.querySelectorAll('div.card-footer');
    //const cards = [document.querySelector('div.card-footer')];
    cards.forEach(async card => {
        const studio = card.querySelector('div.text-muted a').textContent;
        const scene = card.querySelector('div.d-flex a h6').textContent;
        const date = card.querySelector('div.text-muted strong').textContent;
        const performers = await GetPerformers(card.querySelector('div.d-flex a').getAttribute('href'));
        const tags = await GetTags(studio, scene, date, performers);
        const ul = WrapTags(tags);
        const existingUl = card.querySelector('ul');
        if (existingUl != undefined) {
            existingUl.remove();
        }
        card.appendChild(ul);
    });
}

function AddSearchTorrentsButton() {
    const button = document.createElement('button');
    button.setAttribute('type', 'button');
    button.setAttribute('class', 'btn btn-primary');
    button.onclick = SearchTorrents;
    button.appendChild(document.createTextNode('⚡️'));
    const a = document.createElement('a');
    a.setAttribute('class', 'ms-auto');
    a.appendChild(button);
    document.querySelector('div.scenes-list div').appendChild(a);
}


// Convenience function to execute your callback only after an element matching readySelector has been added to the page.
// Example: runWhenReady('.search-result', augmentSearchResults);
// Gives up after 1 minute.
function runWhenReady(readySelector, callback) {
    var numAttempts = 0;
    var tryNow = function() {
        var elem = document.querySelector(readySelector);
        if (elem) {
            callback(elem);
        } else {
            numAttempts++;
            if (numAttempts >= 34) {
                console.warn('Giving up after 34 attempts. Could not find: ' + readySelector);
            } else {
                setTimeout(tryNow, 250 * Math.pow(1.1, numAttempts));
            }
        }
    };
    tryNow();
}

(function() {
    'use strict';
    runWhenReady('div.scenes-list', AddSearchTorrentsButton);
})();