Улучшение пользовательского опыта

Страницы:  1

Ответить
Автор
Сообщение

^(V)^


Всем привет!
Можно улучшить пользовательский опыт с помощью скриптов.
Инструкция:
1. Установить расширение к браузеру для запуска скриптов: Violentmonkey (рекомендуется) или Tampermonkey.
2. Создать в нем новый скрипт, вставить вместо него текст выбранного скрипта и сохранить.
3. Все скрипты рассчитаны на https://torlab.net - HTTP не поддерживается.

TorLab User Menu

Код:
// ==UserScript==
// @name         TorLab User Menu
// @namespace    copyMister
// @version      1.4.1
// @description  Adds a dropdown with quick search links for users in forum topics (e.g., messages by author). Note: Requires JavaScript enabled for torlab.net in Tor Browser (via NoScript or security settings).
// @description:ru  Добавляет выпадающее меню со ссылками на сообщения пользователя в теме (и другие ссылки). Требуется включить JavaScript для torlab.net в Tor Browser (через NoScript или настройки безопасности).
// @author       copyMister
// @license      MIT
// @match        https://torlab.net/viewtopic.php*
// @match        https://torlab.net/torrent/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torlab.net
// @run-at       document-body
// @grant        none
// @homepageURL  https://torlab.net/torrent/uluchshenie-polzovatelskogo-opyta.7155
// ==/UserScript==
(function() {
    'use strict';
    $.holdReady(true);
    document.addEventListener('DOMContentLoaded', function() {
        if (!document.querySelector('#quick-search')) {
            console.error('Quick search element not found');
            $.holdReady(false);
            return;
        }
        // Extract topicId from URL (handles both viewtopic.php and /torrent/*)
        let topicId;
        const url = window.location.href;
        if (url.includes('viewtopic.php')) {
            topicId = new URLSearchParams(window.location.search).get('t');
        } else if (url.includes('/torrent/')) {
            const match = url.match(/\.(\d+)$/);
            topicId = match ? match[1] : null;
        }
        if (!topicId) {
            console.error('Could not extract topicId from URL:', url);
            $.holdReady(false);
            return;
        }
        // Extract forumId (assumes same selector as pornolab.net; adjust if needed)
        const forumOption = document.querySelector('option[value^="search.php?f="]');
        const forumId = forumOption ? forumOption.value.split('=')[1] : null;
        if (!forumId) {
            console.error('Could not extract forumId');
            $.holdReady(false);
            return;
        }
        // Process each post to add user menu
        document.querySelectorAll('#topic_main .poster_btn.td3 > div').forEach(function(div) {
            const userLink = div.firstElementChild.href;
            const userIdMatch = userLink.match(/u=(\d+)/);
            const userId = userIdMatch ? userIdMatch[1] : null;
            if (!userId) return;
            const userName = div.closest('tbody').querySelector('.poster_info > .nick')?.textContent.trim();
            if (!userName) {
                console.error('Could not extract userName for userId:', userId);
                return;
            }
            const userMenu = `usermenu-${userId}`;
            // Add dropdown toggle link
            div.insertAdjacentHTML(
                'beforeend',
                `<a class="txtb menu-root without-caret" href="#${userMenu}">▼</a>`
            );
            // Add dropdown menu if not already present
            if (!document.querySelector(`#${userMenu}`)) {
                document.body.insertAdjacentHTML(
                    'beforeend',
                    `<div id="${userMenu}" class="menu-sub"><div class="menu-a bold nowrap">
                     <table style="width: 100%; border-collapse: collapse; border: 0;">
                         <tr><th style="font-size: 12px; padding: 4px 12px;">${userName}</th></tr>
                     </table>
                     <a class="med" href="search.php?uid=${userId}&search_author=1&only_replies">Все ответы</a>
                     <a class="med" href="search.php?uid=${userId}&t=${topicId}&dm=1">Сообщения в этой теме</a>
                     <a class="med" href="search.php?uid=${userId}&f=${forumId}&dm=1">Сообщения в этом разделе</a>
                     <a class="med" href="search.php?uid=${userId}&search_author=1">Сообщения по всему трекеру</a>
                     <a class="med" href="search.php?uid=${userId}&myt=1">Начатые темы</a>
                     <a class="med" href="tracker.php?rid=${userId}">Раздачи</a></div></div>`
                );
            }
        });
        $.holdReady(false);
    });
})();

TorLab Preloaded Preview

Код:
// ==UserScript==
// @name         Torlab Preloaded Preview
// @version      1.7.0
// @description  Прелоадит и отображает превью изображений под ссылками на torlab.net для страниц трекера, поиска и форумов.
// @author       Ace
// @license      MIT
// @match        *://torlab.net/search*
// @match        *://torlab.net/tracker*
// @match        *://torlab.net/viewforum*
// @match        *://torlab.net/torrent/*
// @match        *://torlab.net/forum/*
// @run-at       document-end
// @require      https://cdnjs.cloudflare.com/ajax/libs/core-js/3.25.1/minified.js
// @grant        none
// @namespace    https://torlab.net/torrent/uluchshenie-polzovatelskogo-opyta.7155
// ==/UserScript==
(async function(){
    const previewSize = {width: "auto", height: "19rem"};
    const inProgressLinks = new Set();
    const failedLinks = new Map();
    const MAX_RETRIES = 3;
    const THROTTLE_INTERVAL = 300;
    const MAX_CONCURRENT_FETCHES = 5;
    let concurrentFetchCount = 0;
    try {
        const style = document.createElement("style");
        style.textContent = `.preview-container{max-width:${previewSize.width};height:${previewSize.height};display:flex;justify-content:center;align-items:center;overflow:hidden}.preview-container img{width:100%;height:100%;object-fit:scale-down}`;
        document.head.appendChild(style);
    } catch (error) {}
    const previewCache = new Map();
    async function fetchWithRetry(url, retries = 0) {
        try {
            const response = await fetch(url, { timeout: 10000 });
            if (!response.ok) {
                if (retries < MAX_RETRIES) return fetchWithRetry(url, retries + 1);
                failedLinks.set(url, `Failed after ${MAX_RETRIES} retries`);
                return [];
            }
            const html = await response.text();
            const doc = new DOMParser().parseFromString(html, "text/html");
            const imgElements = doc.querySelectorAll(".postImg, .postimage, img[title], var.postImg img");
            const imgUrls = Array.from(imgElements).map(img => img.title || img.src || img.dataset.src).filter(Boolean);
            if (imgUrls.length === 0) failedLinks.set(url, "No images found");
            return imgUrls;
        } catch (error) {
            if (retries < MAX_RETRIES) return fetchWithRetry(url, retries + 1);
            failedLinks.set(url, `Failed after ${MAX_RETRIES} retries`);
            return [];
        }
    }
    function normalizeUrl(href) {
        const baseUrl = "https://torlab.net";
        if (!href) return null;
        if (href.startsWith("/")) return baseUrl + href;
        if (href.match(/\/(torrent|forum)\/.+/)) {
            const match = href.match(/\.(\d+)$/);
            const type = href.includes("/torrent") ? "torrent" : "forum";
            if (match) {
                const id = match[1];
                const normalized = type === "torrent" ? `${baseUrl}/viewtopic.php?t=${id}` : `${baseUrl}/viewforum.php?f=${id}`;
                return normalized;
            }
            return href;
        }
        const normalized = href.startsWith(baseUrl) ? href : baseUrl + "/" + href;
        return normalized;
    }
    async function getCachedOrFetchPreviewUrls(linkElement) {
        const href = linkElement.getAttribute("href");
        const url = normalizeUrl(href);
        if (!url) return [];
        if (previewCache.has(url)) return Promise.resolve(previewCache.get(url));
        if (inProgressLinks.has(url)) return Promise.resolve([]);
        if (failedLinks.has(url)) return Promise.resolve([]);
        inProgressLinks.add(url);
        const delay = () => new Promise(resolve => setTimeout(resolve, 100));
        while (concurrentFetchCount >= MAX_CONCURRENT_FETCHES) await delay();
        concurrentFetchCount++;
        try {
            const result = await fetchWithRetry(url);
            previewCache.set(url, result);
            return result;
        } finally {
            concurrentFetchCount--;
            inProgressLinks.delete(url);
        }
    }
    function insertPreview(linkElement, imgUrls) {
        try {
            if (!imgUrls || imgUrls.length === 0) return;
            if (linkElement.hasAttribute("data-preview-processed")) return;
            linkElement.setAttribute("data-preview-processed", "true");
            const existingPreview = linkElement.nextElementSibling?.classList.contains("preview-container");
            if (existingPreview) return;
            const previewContainer = document.createElement("div");
            previewContainer.className = "preview-container";
            const img = document.createElement("img");
            img.src = imgUrls[0];
            img.loading = "lazy";
            img.addEventListener("error", function() {
                imgUrls.shift();
                if (imgUrls.length > 0) img.src = imgUrls[0];
                else img.remove();
            });
            img.addEventListener("load", function() {
                if (img.naturalHeight < 201) {
                    imgUrls.shift();
                    if (imgUrls.length > 0) img.src = imgUrls[0];
                    else img.remove();
                }
            });
            previewContainer.appendChild(img);
            linkElement.parentNode.insertBefore(previewContainer, linkElement.nextSibling);
            linkElement.parentNode.style.cssText = "padding:1rem;display:flex;justify-content:space-between";
            linkElement.style.cssText = "width:30%";
        } catch (error) {}
    }
    async function preloadPreviews() {
        try {
            const links = document.querySelectorAll(".tLink, .tt-text, .topictitle");
            const tasks = Array.from(links).map(link => async () => {
                const imgUrls = await getCachedOrFetchPreviewUrls(link);
                insertPreview(link, imgUrls);
            });
            await preloadAll(tasks, 5);
        } catch (error) {}
    }
    async function preloadAll(tasks, limit = 5) {
        const results = [];
        try {
            while (tasks.length) {
                const batch = tasks.splice(0, limit).map(task => task());
                results.push(...(await Promise.allSettled(batch)));
            }
        } catch (error) {}
        return results;
    }
    function throttle(func, limit) {
        let lastFunc;
        let lastRan;
        return function(...args) {
            const context = this;
            if (!lastRan) {
                func.apply(context, args);
                lastRan = Date.now();
            } else {
                clearTimeout(lastFunc);
                lastFunc = setTimeout(() => {
                    if (Date.now() - lastRan >= limit) {
                        func.apply(context, args);
                        lastRan = Date.now();
                    }
                }, limit - (Date.now() - lastRan));
            }
        };
    }
    try {
        const throttledObserverCallback = throttle(entries => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    const link = entry.target;
                    preloadPreviews().then(() => observer.unobserve(link)).catch(() => observer.observe(link));
                }
            });
        }, THROTTLE_INTERVAL);
        const observer = new IntersectionObserver(throttledObserverCallback);
        const observeLinks = () => {
            document.querySelectorAll(".tLink, .tt-text, .topictitle").forEach(link => {
                if (!link.hasAttribute("data-preview-processed")) observer.observe(link);
            });
        };
        observeLinks();
        const mutationObserver = new MutationObserver(() => {
            try {
                observeLinks();
            } catch (error) {}
        });
        mutationObserver.observe(document.body, { childList: true, subtree: true });
    } catch (error) {}
})();
 
Loading...
Error