Greasy Fork is available in English.

Greasyfork Unlisted Scripts Adder

Adds unlisted scripts if missing, styles added scripts dark green, limits to 500 scripts, saves unlisted IDs to localStorage, press G 5 times to open data in new tab, infinite scrolling, unlisted mode

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

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

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         Greasyfork Unlisted Scripts Adder
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Adds unlisted scripts if missing, styles added scripts dark green, limits to 500 scripts, saves unlisted IDs to localStorage, press G 5 times to open data in new tab, infinite scrolling, unlisted mode
// @author       beak2825 / jarivivi / [email protected]
// @match        https://greasyfork.org/*/scripts?*sort=created*
// @match        https://greasyfork.org/*/scripts?*unlisted*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const isUnlistedMode = location.search.includes('unlisted');
    let ol = document.getElementById('browse-script-list');
    if (!ol && isUnlistedMode) {
        ol = document.createElement('ol');
        ol.id = 'browse-script-list';
        ol.className = 'script-list';
        document.body.appendChild(ol);
        const h1 = document.querySelector('h1');
        if (h1) h1.textContent = 'Unlisted Scripts';
    }
    if (!ol) return;

    // Hide pagination
    const pagination = document.querySelector('.pagination');
    if (pagination) pagination.style.display = 'none';

    // Add Unlisted link
    const sortAs = document.querySelectorAll('a[href*="?sort="]');
    if (sortAs.length) {
        const container = sortAs[0].parentNode;
        const unlistedA = document.createElement('a');
        unlistedA.href = '/en/scripts?unlisted';
        unlistedA.textContent = 'Unlisted';
        unlistedA.style.marginLeft = '10px';
        container.appendChild(unlistedA);
    }

    let unlistedIds = JSON.parse(localStorage.getItem('unlistedIds') || '[]');
    const unlistedSet = new Set(unlistedIds);

    let loading = false;
    let currentPage = 1;
    let count = 0;
    let timer;

    document.addEventListener('keydown', e => {
        if (e.key === 'g' || e.key === 'G') {
            count++;
            clearTimeout(timer);
            timer = setTimeout(() => { count = 0; }, 2000);
            if (count === 5) {
                const data = localStorage.getItem('unlistedIds');
                if (data) {
                    const blob = new Blob([data], { type: 'application/json' });
                    const url = URL.createObjectURL(blob);
                    window.open(url);
                }
                count = 0;
            }
        }
    });

    if (isUnlistedMode) {
        handleUnlistedMode();
    } else {
        handleRegularMode();
    }

    function handleRegularMode() {
        const lis = Array.from(ol.querySelectorAll('li'));
        if (lis.length >= 500) return;

        const currentIds = new Set(lis.map(li => parseInt(li.dataset.scriptId)));
        let maxId = Math.max(...currentIds);
        let minId = Math.min(...currentIds);

        // Probe +1 to +20
        const upperProbes = [];
        for (let i = 1; i <= 20; i++) {
            upperProbes.push(maxId + i);
        }

        Promise.all(upperProbes.map(id => fetch(`https://api.greasyfork.org/scripts/${id}.json`).then(res => res.ok ? res.json() : null).catch(() => null)))
            .then(datas => {
                const added = [];
                datas.filter(data => data && !data.deleted).forEach(data => {
                    if (!currentIds.has(data.id)) {
                        const li = createLiFromData(data);
                        li.style.backgroundColor = 'darkgreen';
                        insertByDate(li, data.created_at);
                        unlistedSet.add(data.id);
                        added.push(data.id);
                    }
                });
                if (added.length) {
                    localStorage.setItem('unlistedIds', JSON.stringify(Array.from(unlistedSet)));
                    maxId = Math.max(maxId, ...added);
                }
            });

        // Probe gaps between min and max
        const missing = [];
        for (let id = maxId - 1; id > minId; id--) {
            if (!currentIds.has(id)) missing.push(id);
        }

        Promise.all(missing.map(id => fetch(`https://api.greasyfork.org/scripts/${id}.json`).then(res => res.ok ? res.json() : null).catch(() => null)))
            .then(datas => {
                datas.filter(data => data && !data.deleted).forEach(data => {
                    if (!currentIds.has(data.id)) {
                        const li = createLiFromData(data);
                        li.style.backgroundColor = 'darkgreen';
                        insertByDate(li, data.created_at);
                        unlistedSet.add(data.id);
                    }
                });
                localStorage.setItem('unlistedIds', JSON.stringify(Array.from(unlistedSet)));
            });

        // Infinite scroll for regular
        window.addEventListener('scroll', () => {
            if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500 && !loading) {
                loadNextRegularPage(minId);
            }
        });
    }

    function loadNextRegularPage(currentMinId) {
        loading = true;
        currentPage++;
        fetch(`https://api.greasyfork.org/en/scripts.json?sort=created&page=${currentPage}&filter_locale=0`)
            .then(res => res.json())
            .then(datas => {
                if (!datas.length) {
                    loading = false;
                    return;
                }
                const newIds = datas.map(d => d.id);
                const newMaxId = Math.max(...newIds);
                const newMinId = Math.min(...newIds);
                datas.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
                datas.forEach(data => {
                    const li = createLiFromData(data);
                    ol.appendChild(li);
                });

                // Probe gaps
                const gapStart = currentMinId - 1;
                const gapEnd = newMaxId + 1;
                if (gapStart >= gapEnd) {
                    const gaps = [];
                    for (let id = gapStart; id >= gapEnd; id--) {
                        gaps.push(id);
                    }
                    Promise.all(gaps.map(id => fetch(`https://api.greasyfork.org/scripts/${id}.json`).then(res => res.ok ? res.json() : null).catch(() => null)))
                        .then(gdatas => {
                            gdatas.filter(data => data && !data.deleted).forEach(data => {
                                const li = createLiFromData(data);
                                li.style.backgroundColor = 'darkgreen';
                                insertByDate(li, data.created_at);
                                unlistedSet.add(data.id);
                            });
                            localStorage.setItem('unlistedIds', JSON.stringify(Array.from(unlistedSet)));
                            loading = false;
                        });
                } else {
                    loading = false;
                }
            }).catch(() => loading = false);
    }

    function handleUnlistedMode() {
        ol.innerHTML = '';
        unlistedIds = Array.from(unlistedSet).sort((a, b) => b - a);
        let currentIndex = 0;
        const batchSize = 30;
        let probeMin = unlistedIds.length ? Math.min(...unlistedIds) - 1 : 562961; // fallback to some high ID

        const loadBatch = () => {
            const batch = unlistedIds.slice(currentIndex, currentIndex + batchSize);
            if (!batch.length) return;
            Promise.all(batch.map(id => fetch(`https://api.greasyfork.org/scripts/${id}.json`).then(res => res.ok ? res.json() : null).catch(() => null)))
                .then(datas => {
                    datas.filter(data => data && !data.deleted).sort((a, b) => new Date(b.created_at) - new Date(a.created_at)).forEach(data => {
                        const li = createLiFromData(data);
                        li.style.backgroundColor = 'darkgreen';
                        ol.appendChild(li);
                    });
                    currentIndex += batchSize;
                });
        };

        loadBatch();

        window.addEventListener('scroll', () => {
            if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500 && !loading) {
                loading = true;
                if (currentIndex < unlistedIds.length) {
                    loadBatch();
                    loading = false;
                } else {
                    const probes = [];
                    for (let i = 0; i < 20; i++) {
                        probes.push(probeMin - i);
                    }
                    Promise.all(probes.map(id => fetch(`https://api.greasyfork.org/scripts/${id}.json`).then(res => res.ok ? res.json() : null).catch(() => null)))
                        .then(datas => {
                            const newDatas = datas.filter(data => data && !data.deleted);
                            newDatas.forEach(data => {
                                unlistedSet.add(data.id);
                                unlistedIds.push(data.id);
                            });
                            unlistedIds.sort((a, b) => b - a);
                            localStorage.setItem('unlistedIds', JSON.stringify(unlistedIds));
                            newDatas.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)).forEach(data => {
                                const li = createLiFromData(data);
                                li.style.backgroundColor = 'darkgreen';
                                ol.appendChild(li);
                            });
                            if (newDatas.length) {
                                probeMin = Math.min(...newDatas.map(d => d.id)) - 1;
                            } else {
                                probeMin -= 20;
                            }
                            loading = false;
                        });
                }
            }
        });
    }

    function insertByDate(newLi, createdStr) {
        const newDate = new Date(createdStr);
        let inserted = false;
        Array.from(ol.children).forEach(child => {
            if (inserted) return;
            const childDateStr = child.querySelector('.script-list-created-date relative-time')?.getAttribute('datetime');
            if (childDateStr) {
                const childDate = new Date(childDateStr);
                if (newDate > childDate) {
                    ol.insertBefore(newLi, child);
                    inserted = true;
                }
            }
        });
        if (!inserted) ol.appendChild(newLi);
    }

    function createLiFromData(data) {
        // Same as previous version, no change needed
        const li = document.createElement('li');
        li.dataset.scriptId = data.id;
        li.dataset.scriptName = data.name;
        const authors = {};
        data.users.forEach(u => { authors[u.id] = u.name; });
        li.dataset.scriptAuthors = JSON.stringify(authors);
        li.dataset.scriptDailyInstalls = data.daily_installs;
        li.dataset.scriptTotalInstalls = data.total_installs;
        li.dataset.scriptRatingScore = data.fan_score;
        const createdDate = new Date(data.created_at).toISOString().split('T')[0];
        li.dataset.scriptCreatedDate = createdDate;
        const updatedDate = new Date(data.code_updated_at).toISOString().split('T')[0];
        li.dataset.scriptUpdatedDate = updatedDate;
        li.dataset.scriptType = 'public';
        li.dataset.scriptVersion = data.version;
        li.dataset.sensitive = data.sensitive || 'false';
        li.dataset.scriptLanguage = 'js';
        li.dataset.cssAvailableAsJs = 'false';
        li.dataset.codeUrl = data.code_url;

        const article = document.createElement('article');
        li.appendChild(article);

        const h2 = document.createElement('h2');
        article.appendChild(h2);

        const a = document.createElement('a');
        a.className = 'script-link';
        const slug = data.name.toLowerCase().replace(/[^a-z0-9 -]/g, '').replace(/\s+/g, '-').replace(/^-|-$/g, '');
        a.href = `/en/scripts/${data.id}-${slug}`;
        a.textContent = data.name;
        h2.appendChild(a);

        const badge = document.createElement('span');
        badge.className = 'badge badge-js';
        badge.title = 'User script';
        badge.textContent = 'JS';
        h2.appendChild(badge);

        const sep = document.createElement('span');
        sep.className = 'name-description-separator';
        sep.textContent = ' - ';
        h2.appendChild(sep);

        const desc = document.createElement('span');
        desc.className = 'script-description description';
        desc.textContent = data.description;
        h2.appendChild(desc);

        const meta = document.createElement('div');
        meta.className = 'script-meta-block';
        article.appendChild(meta);

        const dl = document.createElement('dl');
        dl.className = 'inline-script-stats';
        meta.appendChild(dl);

        const dtAuthor = document.createElement('dt');
        dtAuthor.className = 'script-list-author';
        dtAuthor.innerHTML = '<span>Author</span>';
        dl.appendChild(dtAuthor);

        const ddAuthor = document.createElement('dd');
        ddAuthor.className = 'script-list-author';
        const spanAuthor = document.createElement('span');
        data.users.forEach(u => {
            const aUser = document.createElement('a');
            const userSlug = u.name.toLowerCase().replace(/ /g, '-');
            aUser.href = `/en/users/${u.id}-${userSlug}`;
            aUser.textContent = u.name;
            spanAuthor.appendChild(aUser);
        });
        ddAuthor.appendChild(spanAuthor);
        dl.appendChild(ddAuthor);

        const dtDaily = document.createElement('dt');
        dtDaily.className = 'script-list-daily-installs';
        dtDaily.innerHTML = '<span>Daily installs</span>';
        dl.appendChild(dtDaily);

        const ddDaily = document.createElement('dd');
        ddDaily.className = 'script-list-daily-installs';
        ddDaily.innerHTML = `<span>${data.daily_installs}</span>`;
        dl.appendChild(ddDaily);

        const dtTotal = document.createElement('dt');
        dtTotal.className = 'script-list-total-installs';
        dtTotal.innerHTML = '<span>Total installs</span>';
        dl.appendChild(dtTotal);

        const ddTotal = document.createElement('dd');
        ddTotal.className = 'script-list-total-installs';
        ddTotal.innerHTML = `<span>${data.total_installs}</span>`;
        dl.appendChild(ddTotal);

        const dtRatings = document.createElement('dt');
        dtRatings.className = 'script-list-ratings';
        dtRatings.innerHTML = '<span>Ratings</span>';
        dl.appendChild(dtRatings);

        const ddRatings = document.createElement('dd');
        ddRatings.className = 'script-list-ratings';
        ddRatings.dataset.ratingScore = data.fan_score;
        const spanRatings = document.createElement('span');
        spanRatings.innerHTML = `
<span class="good-rating-count" title="Number of people who rated it Good or added it to favorites;">${data.good_ratings}</span>
<span class="ok-rating-count" title="Number of people who rated it OK;">${data.ok_ratings}</span>
<span class="bad-rating-count" title="Number of people who rated it Bad;">${data.bad_ratings}</span>
`;
        ddRatings.appendChild(spanRatings);
        dl.appendChild(ddRatings);

        const dtCreated = document.createElement('dt');
        dtCreated.className = 'script-list-created-date';
        dtCreated.innerHTML = '<span>Created</span>';
        dl.appendChild(dtCreated);

        const ddCreated = document.createElement('dd');
        ddCreated.className = 'script-list-created-date';
        const spanCreated = document.createElement('span');
        const relCreated = document.createElement('relative-time');
        relCreated.datetime = data.created_at;
        relCreated.prefix = '';
        const createdObj = new Date(data.created_at);
        relCreated.title = createdObj.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', timeZoneName: 'short' });
        relCreated.textContent = createdDate;
        spanCreated.appendChild(relCreated);
        ddCreated.appendChild(spanCreated);
        dl.appendChild(ddCreated);

        const dtUpdated = document.createElement('dt');
        dtUpdated.className = 'script-list-updated-date';
        dtUpdated.innerHTML = '<span>Updated</span>';
        dl.appendChild(dtUpdated);

        const ddUpdated = document.createElement('dd');
        ddUpdated.className = 'script-list-updated-date';
        const spanUpdated = document.createElement('span');
        const relUpdated = document.createElement('relative-time');
        relUpdated.datetime = data.code_updated_at;
        relUpdated.prefix = '';
        const updatedObj = new Date(data.code_updated_at);
        relUpdated.title = updatedObj.toLocaleString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', timeZoneName: 'short' });
        relUpdated.textContent = updatedDate;
        spanUpdated.appendChild(relUpdated);
        ddUpdated.appendChild(spanUpdated);
        dl.appendChild(ddUpdated);

        return li;
    }
})();