Скрипты для торрент трекеров

Страницы :   Пред.  1, 2, 3 ... 12, 13, 14
Ответить
 

(@_@-)

Стаж: 3 года

Сообщений: 376

(@_@-) · 20-Авг-24 23:56 (1 год 2 месяца назад)

EL-34 писал(а):
86608942(@_@-)
Никак.
Правда?
Темные темы делают, а светлые нет?
[Профиль]  [ЛС] 

EL-34

Top Bonus 10* 1PB

Стаж: 17 лет 9 месяцев

Сообщений: 8493

EL-34 · 21-Авг-24 07:55 (спустя 7 часов, ред. 21-Авг-24 07:55)

(@_@-)
Это вам не в форумные игры играть.
На многих сайтах эта возможность встроена, как например в одноклассниках и вконтакте.
Светлая, тёмная, как в системе.
[Профиль]  [ЛС] 

(@_@-)

Стаж: 3 года

Сообщений: 376

(@_@-) · 21-Авг-24 11:50 (спустя 3 часа)

EL-34
Где встроено, я в курсе.
На рутрекере нет тем, но можно сторонними расширениями сделать темную тему.
[Профиль]  [ЛС] 

EL-34

Top Bonus 10* 1PB

Стаж: 17 лет 9 месяцев

Сообщений: 8493

EL-34 · 21-Авг-24 11:52 (спустя 2 мин.)

(@_@-) писал(а):
86612008можно сторонними расширениями сделать темную тему.
И даже без расширений можно, например в Опере.
[Профиль]  [ЛС] 

(@_@-)

Стаж: 3 года

Сообщений: 376

(@_@-) · 21-Авг-24 12:00 (спустя 7 мин.)

EL-34
Вот, уже не никак, а можно
А светлую тему опера не делает?
[Профиль]  [ЛС] 

raddyst

Стаж: 11 лет 10 месяцев

Сообщений: 635


raddyst · 21-Авг-24 12:11 (спустя 10 мин.)

(@_@-) писал(а):
86612042А светлую тему опера не делает?
Попробуйте букмарклеты: Light Mode Bookmarlet и Dark Mode - Javascript Bookmarklet
[Профиль]  [ЛС] 

(@_@-)

Стаж: 3 года

Сообщений: 376

(@_@-) · 21-Авг-24 12:18 (спустя 7 мин.)

raddyst
О! Спасибо, попробую.
[Профиль]  [ЛС] 

RewTeyi

Стаж: 3 года 5 месяцев

Сообщений: 543

RewTeyi · 14-Фев-25 00:40 (спустя 5 месяцев 23 дня)

Цитата:
IMDb Scout
Может знает кто, есть ли подобный скрипт для TMDB?
[Профиль]  [ЛС] 

raddyst

Стаж: 11 лет 10 месяцев

Сообщений: 635


raddyst · 14-Фев-25 02:37 (спустя 1 час 56 мин.)

RewTeyi писал(а):
87394356Может знает кто, есть ли подобный скрипт для TMDB?
Локализованных не встречал, разве что torrent-quick-search и исключительно для passthepopcorn
[Профиль]  [ЛС] 

oi

Стаж: 17 лет 7 месяцев

Сообщений: 584

oi · 24-Фев-25 00:56 (спустя 9 дней, ред. 24-Фев-25 00:56)

TeamHD One Click Torrent Downloader
Добавляет кнопку "Download All" на сайт teamhd.org для массовой загрузки .torrent файлов со страницы поиска.
Обычное нажатие загружает только .torrent файлы, с которыми нет активного взаимодействия (раздача/загрузка).
Долгое нажатие (3 секунды) включает режим "Force Download All", позволяя скачать все .torrent файлы, включая те, что в активной закачке/раздаче.
Кнопка отображает количество доступных файлов и показывает прогресс загрузки.
Есть возможность поставить на паузу и возобновить загрузку при повторном нажатии на кнопку.
Загрузка происходит раз в секунду чтобы не напрягать сервер большими выгрузками.
гифка
скриптец
Код:
// ==UserScript==
// @name         TeamHD One Click Torrent Downloader
// @namespace    http://tampermonkey.net/
// @version      0.1.020
// @description  Adds a Download All button to download all .torrent links on teamhd.org with progress indication, pre-download count, pause/resume with status, and long press functionality
// @author       oi
// @match        https://teamhd.org/browse*
// @grant        GM_download
// ==/UserScript==
(function() {
    'use strict';
    let downloadAllButton;
    let longPressTimer;
    let textChangeTimer;
    const longPressDuration = 3000; // 3 seconds
    const textChangeDelay = 500; // 0.5 seconds
    let isPaused = false;
    let isDownloading = false;
    let currentDownloadIndex = 0;
    let currentTorrents = [];
    // Function to download a file using simulated click
    function downloadFile(url, filename) {
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        link.style.display = 'none';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        console.log(`Simulated click for: ${filename}`);
    }
    // Function to gather all .torrent links with interaction check
    function getAllTorrentLinks(includeInteracted = false) {
        const links = document.querySelectorAll('a[href*="download.php?id="]');
        return Array.from(links).map(link => {
            const parentTd = link.closest('td');
            const isInteracted = parentTd.querySelector('.badgget.seeder, .badgget.leecher') !== null;
            return {
                url: link.href.startsWith('http') ? link.href : window.location.origin + '/' + link.getAttribute('href'),
                filename: decodeURIComponent((link.href.split('name=')[1] || 'file.torrent').split('&')[0]),
                interacted: isInteracted
            };
        }).filter(torrent => includeInteracted || !torrent.interacted);
    }
    // Function to update button progress with red progress bar
    function updateButtonProgress(downloaded, total) {
        const percent = Math.round((downloaded / total) * 100);
        downloadAllButton.textContent = `${isPaused ? 'Paused' : 'Downloading'} ${downloaded}/${total} (${percent}%)`;
        downloadAllButton.style.background = `linear-gradient(to right, red ${percent}%, #28a745 ${percent}%)`;
    }
    // Function to display initial count of torrents
    function displayInitialCount() {
        const allTorrents = getAllTorrentLinks(true);
        const nonInteractedTorrents = getAllTorrentLinks(false);
        downloadAllButton.textContent = `Download All (${nonInteractedTorrents.length}/${allTorrents.length})`;
    }
    // Function to download torrents with delay and pause/resume functionality
    async function downloadAllTorrents(event, includeInteracted = false) {
        if (event) {
            event.preventDefault();
            event.stopPropagation();
        }
        if (isDownloading) {
            isPaused = !isPaused;
            updateButtonProgress(currentDownloadIndex, currentTorrents.length);
            return;
        }
        currentTorrents = getAllTorrentLinks(includeInteracted);
        if (currentTorrents.length === 0) {
            console.log('No torrents found to download.');
            downloadAllButton.textContent = 'No Torrents Found';
            downloadAllButton.style.backgroundColor = 'gray';
            setTimeout(() => displayInitialCount(), 2000);
            return false;
        }
        isDownloading = true;
        isPaused = false;
        currentDownloadIndex = 0;
        // Adding a 0.5 second delay before showing progress for force download
        if (includeInteracted) {
            await new Promise(resolve => setTimeout(resolve, 500));
        }
        while (currentDownloadIndex < currentTorrents.length) {
            if (isPaused) {
                await new Promise(resolve => setTimeout(resolve, 500));
                continue;
            }
            const torrent = currentTorrents[currentDownloadIndex];
            console.log(`Downloading: ${torrent.filename}`);
            downloadFile(torrent.url, torrent.filename);
            updateButtonProgress(currentDownloadIndex + 1, currentTorrents.length);
            currentDownloadIndex++;
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
        isDownloading = false;
        downloadAllButton.textContent = 'This Page is Done';
        downloadAllButton.style.backgroundColor = 'red';
        return false;
    }
    // Add Download All button with long press functionality
    function addDownloadAllButton() {
        const searchButton = document.querySelector('input[type="submit"][value="Поиск!"]');
        if (searchButton) {
            downloadAllButton = document.createElement('button');
            downloadAllButton.textContent = 'Download All';
            downloadAllButton.style.marginLeft = '10px';
            downloadAllButton.classList.add('btn', 'btn-success');
            let longPressActivated = false;
            // Short press - download non-interacted torrents or pause/resume
            downloadAllButton.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                if (!longPressActivated) downloadAllTorrents(e, false);
                return false;
            });
            // Long press handling
            downloadAllButton.addEventListener('mousedown', (e) => {
                e.preventDefault();
                e.stopPropagation();
                let progress = 0;
                longPressActivated = false;
                // Delay text change by 0.5s
                textChangeTimer = setTimeout(() => {
                    downloadAllButton.textContent = 'Force Download All';
                    downloadAllButton.style.background = 'linear-gradient(to right, #FFD700 0%, #28a745 0%)';
                    downloadAllButton.style.color = 'black';
                }, textChangeDelay);
                longPressTimer = setInterval(() => {
                    progress += 100 / (longPressDuration / 100);
                    downloadAllButton.style.background = `linear-gradient(to right, #FFD700 ${progress}%, #28a745 ${progress}%)`;
                    if (progress >= 100) {
                        clearInterval(longPressTimer);
                        clearTimeout(textChangeTimer);
                        longPressTimer = null;
                        textChangeTimer = null;
                        longPressActivated = true;
                        downloadAllTorrents(new Event('click'), true);
                    }
                }, 100);
            });
            downloadAllButton.addEventListener('mouseup', (e) => {
                e.preventDefault();
                e.stopPropagation();
                if (longPressTimer) {
                    clearInterval(longPressTimer);
                    longPressTimer = null;
                }
                if (textChangeTimer) {
                    clearTimeout(textChangeTimer);
                    textChangeTimer = null;
                }
                // Only reset button if not downloading or paused
                if (!isDownloading && !isPaused) {
                    displayInitialCount();
                    downloadAllButton.style.background = '';
                    downloadAllButton.style.color = '';
                }
            });
            downloadAllButton.addEventListener('mouseleave', (e) => {
                e.preventDefault();
                e.stopPropagation();
                if (longPressTimer) {
                    clearInterval(longPressTimer);
                    longPressTimer = null;
                }
                if (textChangeTimer) {
                    clearTimeout(textChangeTimer);
                    textChangeTimer = null;
                }
                // Prevent reset on hover if paused or downloading
                if (!isDownloading && !isPaused) {
                    displayInitialCount();
                    downloadAllButton.style.background = '';
                    downloadAllButton.style.color = '';
                }
            });
            searchButton.parentNode.insertBefore(downloadAllButton, searchButton.nextSibling);
            displayInitialCount();
        } else {
            console.error('Download All button could not be added. Search button not found.');
        }
    }
    // Initialize script
    window.addEventListener('load', () => {
        addDownloadAllButton();
        console.log('TeamHD Torrent Downloader script initialized.');
    });
})();
[Профиль]  [ЛС] 

БэстЛайв

Стаж: 15 лет 5 месяцев

Сообщений: 1007

БэстЛайв · 25-Фев-25 15:52 (спустя 1 день 14 часов, ред. 25-Фев-25 15:52)

oi писал(а):
87440502TeamHD One Click Torrent Downloader
Добавляет кнопку "Download All" на сайт teamhd.org для массовой загрузки .torrent файлов со страницы поиска.
Обычное нажатие загружает только .torrent файлы, с которыми нет активного взаимодействия (раздача/загрузка).
Долгое нажатие (3 секунды) включает режим "Force Download All", позволяя скачать все .torrent файлы, включая те, что в активной закачке/раздаче.
Кнопка отображает количество доступных файлов и показывает прогресс загрузки.
Есть возможность поставить на паузу и возобновить загрузку при повторном нажатии на кнопку.
Загрузка происходит раз в секунду чтобы не напрягать сервер большими выгрузками.
гифка
Правила TeamHD
Цитата:
3.6 Запрещено "коллекционирование" торрент-файлов, т.е. скачивание торрент-файлов "про запас" без фактической загрузки раздачи. Используйте систему закладок. Несоблюдение - предупреждение на 2 недели, в особых случаях - бан.
Хороший скрипт, скачал все торренты и в бан
[Профиль]  [ЛС] 

oi

Стаж: 17 лет 7 месяцев

Сообщений: 584

oi · 25-Фев-25 20:53 (спустя 5 часов)

БэстЛайв писал(а):
Хороший скрипт, скачал все торренты и в бан
Для этого скрипт не нужен, на странице поиска ссылки на торрент файлы и так доступны. Есть 100500 екстеншенов которые их смогут выкачать и так.
[Профиль]  [ЛС] 

Кантор-Эль драко

Top Loader 05* 2TB

Стаж: 15 лет 11 месяцев

Сообщений: 1813

Кантор-Эль драко · 25-Фев-25 21:33 (спустя 40 мин.)

Цитата:
скачал все торренты
И выложил в паблик.
[Профиль]  [ЛС] 

exiless

Стаж: 1 год 9 месяцев

Сообщений: 40

exiless · 22-Апр-25 20:22 (спустя 1 месяц 24 дня)

1dNDN писал(а):
85994224https://gist.github.com/1dNDN/15680715cfcc7fd00f50206d19ec24c9
Написал автозаполнение полей в форме создания новой раздачи игры из steam и mobigames
скрипт написал - а разъяснения - нет..
[Профиль]  [ЛС] 

qweqwe876

Стаж: 13 лет 9 месяцев

Сообщений: 6


qweqwe876 · 29-Апр-25 18:42 (спустя 6 дней)

copyMister писал(а):
85726669RuTracker Full-Text Search in Topics v1.4
Картинка
Добавляет возможность искать текст на всех страницах темы.
Мой вариант скрипта Search text in thread a.k.a. Поиск текста по сообщениям темы (№13).
скрытый текст
Чем лучше:
- найденный текст подсвечивается, есть индикатор прогресса
- в результатах работают спойлеры, YouTube-видео, ссылки в цитатах и т.д.
- при повторном поиске без перезагрузки страницы результаты выдаются почти мгновенно (запросы второй раз не отсылаются)
- запросы асинхронные, так что браузер не должен подвисать даже в 100-страничных темах
- добавлена горячая клавиша для быстрого фокуса в поле: Alt+ф (Chrome), Alt+Shift+ф (Firefox) или Ctrl+Opt+ф (Mac)
- добавлены настройки: искать в том числе по никам пользователей, сортировать сообщения по убыванию даты
- код оптимизирован, без jQuery
Установить:
- Greasy Fork
Проверено в Chrome и Firefox с расширениями Tampermonkey и Violentmonkey.
В Greasemonkey не работает, потому что там нет функции GM_addStyle (зачем-то удалили). Нет желания возиться с этим расширением, так что рекомендую два других.
copyMister, подскажите, что я делаю неверно? установил Tampermonkey и скачал скрипт. в расширении отображается что скрипт мол установлен. но никакого поиска я не наблюдаю(
[Профиль]  [ЛС] 

copyMister

Top Bonus 05* 10TB

Стаж: 16 лет

Сообщений: 240

copyMister · 29-Апр-25 18:51 (спустя 9 мин.)

qweqwe876
Возможно, поможет включение "Режима разработчика" в настройках расширений (справа вверху), если браузер - Chrome или подобный.
Если нет, то можно попробовать другое расширение - Violentmonkey. Оно еще и с открытым кодом.
[Профиль]  [ЛС] 

stаlkerok

Стаж: 2 года 8 месяцев

Сообщений: 3071

stаlkerok · 02-Июн-25 16:08 (спустя 1 месяц 2 дня)

Может, кто-нибудь возьмется? https://rutr.life/forum/viewtopic.php?p=87762515#87762515
[Профиль]  [ЛС] 

RewTeyi

Стаж: 3 года 5 месяцев

Сообщений: 543

RewTeyi · 04-Июн-25 00:04 (спустя 1 день 7 часов)

Есть у кого правленная версия FastPic Upload?
Загрузка на new fastpic выдает undefined
[IMG]undefined[/IMG]
[Профиль]  [ЛС] 

x86-64

Moderator senior

Стаж: 7 лет 5 месяцев

Сообщений: 29546

x86-64 · 02-Сен-25 17:25 (спустя 2 месяца 28 дней, ред. 04-Сен-25 13:22)

добавлю парочку скриптов для нашего трекера
кнопка [Код] сразу его копирует
Код:
// ==UserScript==
// @name         Rutracker Code Copy
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Копирует содержимое блока кода по нажатию на кнопку [Код] на Rutracker
// @author       Мосгортранс
// @match        https://rutr.life/forum/viewtopic.php*
// @grant        unsafeWindow
// @run-at       document-end
// ==/UserScript==
(function() {
    'use strict';
    const EXCLUSION_TIMEOUT = 5000;
    const activeRequests = new Set();
    async function copyToClipboard(text) {
        try {
            await navigator.clipboard.writeText(text);
            return true;
        } catch (err) {
            console.error('Ошибка копирования:', err);
            return false;
        }
    }
    function showNotification(message, isError = false) {
        const existing = document.querySelector('.rtcc-notification');
        if (existing) existing.remove();
        const notification = document.createElement('div');
        notification.className = 'rtcc-notification';
        Object.assign(notification.style, {
            position: 'fixed',
            right: '20px',
            top: '20px',
            padding: '12px 20px',
            background: isError ? '#e74c3c' : '#2ecc71',
            color: 'white',
            borderRadius: '4px',
            fontSize: '14px',
            fontWeight: '500',
            boxShadow: '0 3px 6px rgba(0,0,0,0.16)',
            zIndex: 10000,
            transition: 'opacity 0.3s, transform 0.3s',
            opacity: '0',
            transform: 'translateX(120%)'
        });
        notification.textContent = message;
        document.body.appendChild(notification);
        requestAnimationFrame(() => {
            notification.style.transform = 'translateX(0)';
            notification.style.opacity = '1';
        });
        setTimeout(() => {
            notification.style.opacity = '0';
            notification.style.transform = 'translateX(120%)';
            setTimeout(() => notification.remove(), 300);
        }, 3000);
    }
    function handleCodeRequest(postId) {
        if (activeRequests.has(postId)) return;
        activeRequests.add(postId);
        const targetId = `ptx-${postId}`;
        let observer = null;
        let timeout = null;
        function cleanup() {
            activeRequests.delete(postId);
            if (observer) observer.disconnect();
            if (timeout) clearTimeout(timeout);
        }
        const checkExisting = () => {
            const textarea = document.getElementById(targetId);
            if (textarea && textarea.tagName === 'TEXTAREA') {
                copyToClipboard(textarea.value)
                    .then(success => {
                        showNotification(success ? 'Код скопирован' : 'Ошибка копирования', !success);
                        cleanup();
                    });
                return true;
            }
            return false;
        };
        if (checkExisting()) return;
        unsafeWindow.ajax.view_post(postId);
        observer = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.id === targetId || node.querySelector?.('#' + targetId)) {
                        if (checkExisting()) return;
                    }
                }
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        timeout = setTimeout(() => {
            showNotification('Превышено время ожидания', true);
            cleanup();
        }, 8000);
    }
    document.addEventListener('click', event => {
        const target = event.target.closest('.txtb');
        if (target && target.textContent.trim() === '[Код]') {
            const onclick = target.getAttribute('onclick') || '';
            const match = onclick.match(/ajax\.view_post\(['"]?(\d+)['"]?\)/);
            if (match?.[1]) {
                event.preventDefault();
                event.stopPropagation();
                handleCodeRequest(match[1]);
            }
        }
    }, true);
    document.addEventListener('click', event => {
        if (event.target.closest('.txtb[onclick*="view_post"]')) {
            event.stopImmediatePropagation();
        }
    }, true);
})();
кнопка ''открыть непрочитанные'' мгновенно открывает последние страницы всех тем, а не только первые 20 и с долгими паузами (очень удобно)
Код:
// ==UserScript==
// @name         Rutracker Unread Topics Opener
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Делает бесполезную кнопку полезной
// @author       Мосгортранс
// @match        https://rutr.life/forum/search.php?uid=*
// @grant        none
// ==/UserScript==
(function() {
    'use strict';
    function initScript() {
        const targetButton = document.querySelector('li.a-like.med.normal.open-all-unread-topics');
        if (!targetButton) return false;
        if (targetButton.classList.contains('modified-by-script')) return true;
        targetButton.classList.add('modified-by-script');
        const newButton = targetButton.cloneNode(true);
        targetButton.parentNode.replaceChild(newButton, targetButton);
        newButton.onclick = function(e) {
            e.preventDefault();
            e.stopPropagation();
            e.stopImmediatePropagation();
            const tableBody = document.querySelector('tbody[aria-live="polite"][aria-relevant="all"]');
            if (!tableBody) {
                alert('Не удалось найти таблицу с темами');
                return;
            }
            const unreadRows = tableBody.querySelectorAll('tr:has(a.t-is-unread)');
            if (unreadRows.length === 0) {
                alert('Непрочитанных тем не найдено');
                return;
            }
            console.log(`Найдено ${unreadRows.length} непрочитанных тем`);
            unreadRows.forEach(row => {
                const pagination = row.querySelector('.topicPG');
                if (!pagination) return;
                const links = pagination.querySelectorAll('a');
                if (links.length === 0) return;
                const lastPageLink = links[links.length - 1];
                const url = lastPageLink.href;
                window.open(url, '_blank');
            });
        };
        newButton.textContent = 'Открыть непрочитанные';
        newButton.style.color = '#ff9900';
        newButton.style.fontWeight = 'bold';
        newButton.style.cursor = 'pointer';
        newButton.removeAttribute('title');
        return true;
    }
    if (!initScript()) {
        const observer = new MutationObserver(function(mutations) {
            for (let mutation of mutations) {
                if (mutation.addedNodes.length) {
                    if (initScript()) {
                        observer.disconnect();
                        break;
                    }
                }
            }
        });
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        setTimeout(() => {
            initScript();
        }, 3000);
    }
})();
[Профиль]  [ЛС] 

RoxMarty

RG Мультфильмы

Стаж: 18 лет 7 месяцев

Сообщений: 14812

RoxMarty · 01-Ноя-25 23:36 (спустя 1 месяц 29 дней, ред. 01-Ноя-25 23:36)

Очень долго ждал и надеялся на чудо, но так и не произошло.
Помучив нейросети, получилось сделать задуманное. Возможно кому пригодиться. Потестировал - неплохо работает (хоть и не так плавно сшивается как скрипт copyMister Infinite scroll для автоподгрузки следующих страниц вперёд. Если какие идеи будут или что поправить - пишите. Попробуем
Из плюсов: работает вместе с Infinite scroll без проблем
Из минусов: работает только если изначально перейти на какую-либо страницу (например: https://rutr.life/forum/viewtopic.php?t=4717182&start=390), если перейти на какой-нибудь пост напрямую (например предыдущий от моего: https://rutr.life/forum/viewtopic.php?p=88163966#88163966), то не работает (может кто сможет подкрутить), и ещё, почему-то не подгружает самую первую страницу починили, спасибо YarikG!
Скрипт для Tampermonkey:
архив
Подгрузка предыдущих страниц (2025-10-30)
Код:

// ==UserScript==
// @name         RuTracker Подгрузка предыдущих страниц
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Загрузка предыдущих страниц (ручной и автоматический режим)
// @author       RoxMarty & DeepSeek
// @match        https://rutr.life/forum/viewtopic.php*
// @match        https://rutracker.net/forum/viewtopic.php*
// @match        https://rutracker.nl/forum/viewtopic.php*
// @match        https://rutracker.lib/forum/viewtopic.php*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==
(function() {
    'use strict';
    let isLoading = false;
    let loadedPages = new Set();
    let consecutiveFailures = 0;
    const MAX_CONSECUTIVE_FAILURES = 3;
    // Настройки по умолчанию - РУЧНОЙ РЕЖИМ
    const defaultSettings = {
        enabled: true,
        mode: 'button', // 'button' или 'auto'
        showSettings: false,
        maxPagesToLoad: 50
    };
    let settings = { ...defaultSettings };
    // Загрузка настроек
    function loadSettings() {
        try {
            const saved = GM_getValue('rutracker_prev_loader_settings');
            if (saved) {
                settings = { ...defaultSettings, ...saved };
            }
            console.log('RuTracker: Settings loaded:', settings);
        } catch (error) {
            console.error('RuTracker: Error loading settings:', error);
            settings = { ...defaultSettings };
        }
    }
    // Сохранение настроек
    function saveSettings() {
        try {
            GM_setValue('rutracker_prev_loader_settings', settings);
            console.log('RuTracker: Settings saved:', settings);
        } catch (error) {
            console.error('RuTracker: Error saving settings:', error);
        }
    }
    // Функция для поиска ссылки на предыдущую страницу
    function findPreviousPageLink() {
        if (!settings.enabled) return null;
        if (loadedPages.size >= settings.maxPagesToLoad) {
            console.log('RuTracker: Maximum pages limit reached');
            return null;
        }
        if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
            console.log('RuTracker: Too many consecutive failures');
            return null;
        }
        const paginations = document.querySelectorAll('.pagination, .nav');
        let prevLink = null;
        let currentStart = getCurrentStart();
        console.log('RuTracker: Searching pages. Current start:', currentStart);
        paginations.forEach(pagination => {
            const links = pagination.querySelectorAll('a[href*="start="]');
            links.forEach(link => {
                const href = link.href;
                const match = href.match(/start=(\d+)/);
                if (match) {
                    const start = parseInt(match[1]);
                    // Ищем страницы с start МЕНЬШЕ текущего
                    if (start < currentStart && !loadedPages.has(start)) {
                        // Находим самую близкую страницу (максимальный start)
                        if (!prevLink || start > getStartFromUrl(prevLink.href)) {
                            prevLink = link;
                        }
                    }
                }
            });
        });
        console.log('RuTracker: Found previous page:', prevLink ? getStartFromUrl(prevLink.href) : 'none');
        return prevLink;
    }
    // Получаем текущий start параметр
    function getCurrentStart() {
        const urlParams = new URLSearchParams(window.location.search);
        const start = parseInt(urlParams.get('start')) || 0;
        return start;
    }
    // Получаем start из URL
    function getStartFromUrl(url) {
        const match = url.match(/start=(\d+)/);
        return match ? parseInt(match[1]) : 0;
    }
    // Проверяем, есть ли еще страницы для загрузки
    function hasMorePages() {
        const prevLink = findPreviousPageLink();
        return !!prevLink;
    }
    // Загрузка предыдущей страницы
    function loadPreviousPage() {
        if (!settings.enabled || isLoading) {
            return;
        }
        const prevLink = findPreviousPageLink();
        if (!prevLink) {
            console.log('RuTracker: No previous page found');
            updateUI();
            return;
        }
        const prevStart = getStartFromUrl(prevLink.href);
        if (loadedPages.has(prevStart)) {
            return;
        }
        isLoading = true;
        consecutiveFailures = 0;
        updateUI();
        console.log('RuTracker: Loading page with start:', prevStart);
        const scrollYBefore = window.scrollY;
        const markerPost = findMarkerPost();
        GM_xmlhttpRequest({
            method: "GET",
            url: prevLink.href,
            timeout: 30000,
            onload: function(response) {
                if (response.status !== 200) {
                    handleLoadError('HTTP error: ' + response.status);
                    return;
                }
                const parser = new DOMParser();
                const doc = parser.parseFromString(response.responseText, 'text/html');
                if (!doc.querySelector('#topic_main')) {
                    handleLoadError('Invalid page structure');
                    return;
                }
                const posts = doc.querySelectorAll('#topic_main > tbody[id^="post_"]');
                console.log('RuTracker: Found posts:', posts.length);
                if (posts.length === 0) {
                    handleLoadError('No posts found');
                    return;
                }
                const heightBefore = document.documentElement.scrollHeight;
                const topicMain = document.getElementById('topic_main');
                const firstPost = topicMain.querySelector('tbody[id^="post_"]');
                const fragment = document.createDocumentFragment();
                Array.from(posts).forEach((post) => {
                    const clonedPost = post.cloneNode(true);
                    fragment.appendChild(clonedPost);
                });
                if (firstPost) {
                    topicMain.insertBefore(fragment, firstPost);
                } else {
                    topicMain.appendChild(fragment);
                }
                if (window.BB) {
                    Array.from(fragment.children).forEach(post => {
                        const postBody = post.querySelector('.post_body');
                        if (postBody) {
                            window.BB.initPost(postBody);
                        }
                        const signature = post.querySelector('.signature');
                        if (signature) {
                            window.BB.initPost(signature);
                        }
                    });
                }
                updatePagination(doc);
                loadedPages.add(prevStart);
                restoreScrollPosition(heightBefore, markerPost, scrollYBefore);
                showStatus('+1 страница');
                isLoading = false;
                updateUI();
            },
            onerror: function(error) {
                handleLoadError('Network error');
            },
            ontimeout: function() {
                handleLoadError('Request timeout');
            }
        });
    }
    // Обработчик ошибок загрузки
    function handleLoadError(errorMessage) {
        consecutiveFailures++;
        console.error('RuTracker: Load error:', errorMessage);
        showStatus('Ошибка загрузки');
        isLoading = false;
        updateUI();
    }
    // Находим пост, который был вверху экрана до загрузки
    function findMarkerPost() {
        const posts = document.querySelectorAll('#topic_main > tbody[id^="post_"]');
        let bestPost = null;
        let minDistance = Infinity;
        posts.forEach(post => {
            const rect = post.getBoundingClientRect();
            if (rect.top >= 0) {
                const distance = rect.top;
                if (distance < minDistance) {
                    minDistance = distance;
                    bestPost = post;
                }
            }
        });
        return bestPost || posts[0];
    }
    // Восстанавливаем позицию прокрутки
    function restoreScrollPosition(heightBefore, markerPost, scrollYBefore) {
        if (markerPost && document.contains(markerPost)) {
            const newMarkerRect = markerPost.getBoundingClientRect();
            const scrollDelta = newMarkerRect.top;
            window.scrollTo(0, scrollYBefore + scrollDelta);
        } else {
            const heightAfter = document.documentElement.scrollHeight;
            const heightDiff = heightAfter - heightBefore;
            window.scrollTo(0, scrollYBefore + heightDiff);
        }
    }
    // Автоматическая загрузка (работает только в auto-режиме)
    function initAutoLoad() {
        console.log('RuTracker: Auto-load initialized:', settings.mode);
        if (settings.mode !== 'auto') return;
        let checkInProgress = false;
        window.addEventListener('scroll', function() {
            if (!settings.enabled || settings.mode !== 'auto' || isLoading || checkInProgress) {
                return;
            }
            if (!hasMorePages()) {
                return;
            }
            checkInProgress = true;
            setTimeout(() => {
                const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
                // Загружаем когда близко к верху
                if (scrollTop <= 150) {
                    console.log('RuTracker: Auto-load triggered');
                    loadPreviousPage();
                }
                checkInProgress = false;
            }, 100);
        }, { passive: true });
    }
    // Создаем индикатор статуса
    function createStatusIndicator() {
        const oldIndicator = document.getElementById('rutracker-status-indicator');
        if (oldIndicator) {
            oldIndicator.remove();
        }
        const indicator = document.createElement('div');
        indicator.id = 'rutracker-status-indicator';
        indicator.style.cssText = `
            position: fixed;
            top: 8px;
            left: 45px;
            background: #2196F3;
            color: white;
            padding: 4px 8px;
            border-radius: 3px;
            font-size: 12px;
            font-weight: bold;
            z-index: 9997;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
            cursor: pointer;
            white-space: nowrap;
        `;
        indicator.addEventListener('click', function() {
            // Переключаем режим
            settings.mode = settings.mode === 'auto' ? 'button' : 'auto';
            saveSettings();
            updateUI();
            if (settings.mode === 'auto') {
                initAutoLoad();
            }
            // Показываем уведомление о смене режима
            showStatus('Режим: ' + (settings.mode === 'auto' ? 'Авто' : 'Ручной'));
        });
        document.body.appendChild(indicator);
        return indicator;
    }
    // Обновляем индикатор статуса
    function updateStatusIndicator() {
        const indicator = document.getElementById('rutracker-status-indicator');
        if (!indicator) return;
        if (!settings.enabled) {
            indicator.textContent = '❌ Выкл';
            indicator.style.background = '#f44336';
        } else if (!hasMorePages()) {
            indicator.textContent = '✅ ' + loadedPages.size;
            indicator.style.background = '#4CAF50';
        } else if (settings.mode === 'auto') {
            indicator.textContent = '🔁 Автоподгрузка';
            indicator.style.background = '#4CAF50';
        } else {
            indicator.textContent = '🔘 Ручная подгрузка';
            indicator.style.background = '#2196F3';
        }
        indicator.title = 'Загружено: ' + loadedPages.size + ' стр.\nРежим: ' +
                         (settings.mode === 'auto' ? 'Автоподгрузка' : 'Ручная подгрузка') +
                         '\nКлик для смены режима';
    }
    // Обновляем пагинацию
    function updatePagination(newDoc) {
        const newPagination = newDoc.querySelector('.pagination');
        const currentPagination = document.querySelector('.pagination');
        if (newPagination && currentPagination) {
            currentPagination.innerHTML = newPagination.innerHTML;
            updateUI();
        }
    }
    // Обновляем весь UI
    function updateUI() {
        updateLoadButton();
        updateSettingsPanel();
        updateStatusIndicator();
    }
    // Обновляем кнопку загрузки
    function updateLoadButton() {
        const button = document.getElementById('rutracker-prev-load-btn');
        if (!button) return;
        if (!settings.enabled || settings.mode !== 'button') {
            button.style.display = 'none';
            return;
        }
        button.style.display = 'inline-block';
        const prevLink = findPreviousPageLink();
        if (!prevLink) {
            button.textContent = 'Нет предыдущих страниц';
            button.disabled = true;
            button.style.background = '#cccccc';
            button.style.cursor = 'not-allowed';
        } else {
            button.textContent = isLoading ? 'Загрузка...' : '↑ Предыдущая страница (' + (loadedPages.size + 1) + ')';
            button.disabled = isLoading;
            button.style.background = isLoading ? '#cccccc' : '#4CAF50';
            button.style.cursor = isLoading ? 'not-allowed' : 'pointer';
        }
    }
    // Создаем панель настроек
    function createSettingsPanel() {
        const panel = document.createElement('div');
        panel.id = 'rutracker-settings-panel';
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 2px solid #4CAF50;
            border-radius: 6px;
            padding: 15px;
            z-index: 10000;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            min-width: 320px;
            font-family: Arial, sans-serif;
            display: none;
        `;
        panel.innerHTML = `
            <div style="margin-bottom: 12px; border-bottom: 1px solid #eee; padding-bottom: 8px;">
                <h3 style="margin: 0; color: #4CAF50; font-size: 16px;">Настройки загрузки страниц</h3>
            </div>
            <div style="margin-bottom: 12px;">
                <label style="display: flex; align-items: center; margin-bottom: 8px; cursor: pointer; font-size: 13px;">
                    <input type="checkbox" id="settings-enabled" ${settings.enabled ? 'checked' : ''}
                           style="margin-right: 6px;">
                    <span>Включить загрузку предыдущих страниц</span>
                </label>
            </div>
            <div style="margin-bottom: 15px;">
                <div style="margin-bottom: 6px; font-weight: bold; font-size: 13px;">Режим работы:</div>
                <label style="display: block; margin-bottom: 6px; cursor: pointer; font-size: 13px;">
                    <input type="radio" name="mode" value="button" ${settings.mode === 'button' ? 'checked' : ''}
                           style="margin-right: 6px;">
                    Ручная подгрузка
                </label>
                <label style="display: block; margin-bottom: 6px; cursor: pointer; font-size: 13px;">
                    <input type="radio" name="mode" value="auto" ${settings.mode === 'auto' ? 'checked' : ''}
                           style="margin-right: 6px;">
                    Автоподгрузка
                </label>
            </div>
            <div style="margin-bottom: 12px;">
                <label style="display: flex; align-items: center; margin-bottom: 8px; font-size: 13px;">
                    <span style="margin-right: 8px; min-width: 110px;">Максимум страниц:</span>
                    <input type="number" id="settings-max-pages" value="${settings.maxPagesToLoad}"
                           min="1" max="1000" style="width: 70px; padding: 3px; font-size: 12px;">
                </label>
            </div>
            <div style="color: #666; font-size: 11px; margin-bottom: 12px; padding: 8px; background: #f9f9f9; border-radius: 3px;">
                <strong>Статистика:</strong><br>
                • Загружено: <span id="stats-loaded">${loadedPages.size}</span> страниц<br>
                • Текущий start: ${getCurrentStart()}<br>
                • Режим: ${settings.mode === 'auto' ? 'Автоподгрузка' : 'Ручная подгрузка'}<br>
                • Доступно: <span id="stats-available">${hasMorePages() ? 'Да' : 'Нет'}</span>
            </div>
            <div style="display: flex; justify-content: space-between;">
                <button id="settings-save" style="
                    background: #4CAF50; color: white; border: none;
                    padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 12px;">
                    Сохранить
                </button>
                <button id="settings-close" style="
                    background: #f44336; color: white; border: none;
                    padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 12px;">
                    Закрыть
                </button>
            </div>
        `;
        panel.querySelector('#settings-save').addEventListener('click', saveSettingsHandler);
        panel.querySelector('#settings-close').addEventListener('click', closeSettings);
        return panel;
    }
    // Обработчик сохранения настроек
    function saveSettingsHandler() {
        settings.enabled = document.getElementById('settings-enabled').checked;
        settings.mode = document.querySelector('input[name="mode"]:checked').value;
        settings.maxPagesToLoad = parseInt(document.getElementById('settings-max-pages').value) || 50;
        settings.showSettings = false;
        saveSettings();
        closeSettings();
        updateUI();
        if (settings.mode === 'auto') {
            initAutoLoad();
        }
        // Показываем уведомление о сохранении
        showStatus('Настройки сохранены');
    }
    // Закрытие панели настроек
    function closeSettings() {
        settings.showSettings = false;
        const panel = document.getElementById('rutracker-settings-panel');
        if (panel) {
            panel.style.display = 'none';
        }
    }
    // Показ панели настроек
    function showSettings() {
        settings.showSettings = true;
        const panel = document.getElementById('rutracker-settings-panel');
        if (panel) {
            panel.style.display = 'block';
            updateSettingsPanel();
        }
    }
    // Обновление панели настроек
    function updateSettingsPanel() {
        const panel = document.getElementById('rutracker-settings-panel');
        if (panel) {
            panel.querySelector('#settings-enabled').checked = settings.enabled;
            panel.querySelector(`input[name="mode"][value="${settings.mode}"]`).checked = true;
            panel.querySelector('#settings-max-pages').value = settings.maxPagesToLoad;
            panel.querySelector('#stats-loaded').textContent = loadedPages.size;
            panel.querySelector('#stats-available').textContent = hasMorePages() ? 'Да' : 'Нет';
        }
    }
    // Создаем кнопку настроек
    function createSettingsButton() {
        const settingsBtn = document.createElement('button');
        settingsBtn.id = 'rutracker-settings-btn';
        settingsBtn.innerHTML = '⚙️';
        settingsBtn.title = 'Настройки загрузки страниц';
        settingsBtn.style.cssText = `
            position: fixed;
            top: 8px;
            left: 8px;
            width: 28px;
            height: 28px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            z-index: 9999;
            font-size: 14px;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
            display: flex;
            align-items: center;
            justify-content: center;
        `;
        settingsBtn.addEventListener('click', showSettings);
        return settingsBtn;
    }
    // Добавляем кнопку загрузки
    function addManualLoadButton() {
        let oldButton = document.getElementById('rutracker-prev-load-btn');
        if (oldButton) {
            oldButton.remove();
        }
        if (!settings.enabled) {
            return;
        }
        let container = document.querySelector('.pagination');
        if (!container) {
            container = document.querySelector('.nav');
        }
        if (!container) {
            container = document.querySelector('.bottom_info');
        }
        if (!container) {
            container = document.createElement('div');
            container.style.textAlign = 'center';
            container.style.margin = '10px 0';
            container.style.padding = '10px';
            container.style.background = '#f5f5f5';
            container.style.borderRadius = '5px';
            const contentTable = document.querySelector('#page_content .main-content table');
            if (contentTable) {
                contentTable.parentNode.insertBefore(container, contentTable);
            } else {
                document.querySelector('#page_content .main-content').appendChild(container);
            }
        }
        const manualButton = document.createElement('button');
        manualButton.id = 'rutracker-prev-load-btn';
        manualButton.style.cssText = `
            margin: 3px;
            padding: 6px 12px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        `;
        manualButton.addEventListener('click', (e) => {
            e.preventDefault();
            loadPreviousPage();
        });
        container.insertBefore(manualButton, container.firstChild);
        updateLoadButton();
    }
    // Показываем статус (под переключателем режимов)
    function showStatus(message) {
        const statusDiv = document.createElement('div');
        statusDiv.style.cssText = `
            position: fixed;
            top: 42px;
            left: 45px;
            background: #555;
            color: white;
            padding: 4px 8px;
            border-radius: 3px;
            font-size: 11px;
            z-index: 9996;
            box-shadow: 0 1px 3px rgba(0,0,0,0.2);
            opacity: 0.9;
        `;
        statusDiv.textContent = message;
        document.body.appendChild(statusDiv);
        setTimeout(() => {
            if (statusDiv.parentNode) {
                statusDiv.parentNode.removeChild(statusDiv);
            }
        }, 1500);
    }
    // Инициализация
    function init() {
        console.log('RuTracker: Starting initialization...');
        loadSettings();
        if (!document.getElementById('topic_main')) {
            setTimeout(init, 500);
            return;
        }
        loadedPages.clear();
        consecutiveFailures = 0;
        // Добавляем элементы в DOM
        if (!document.getElementById('rutracker-settings-panel')) {
            document.body.appendChild(createSettingsPanel());
        }
        if (!document.getElementById('rutracker-settings-btn')) {
            document.body.appendChild(createSettingsButton());
        }
        if (!document.getElementById('rutracker-status-indicator')) {
            document.body.appendChild(createStatusIndicator());
        }
        addManualLoadButton();
        initAutoLoad();
        updateUI();
        console.log('RuTracker: Script initialized successfully');
        console.log('RuTracker: Mode:', settings.mode);
        console.log('RuTracker: Current start:', getCurrentStart());
    }
    // Запускаем инициализацию
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

Подгрузка предыдущих страниц (2025-11-01)
Код:

// ==UserScript==
// @name         RuTracker Подгрузка предыдущих страниц
// @namespace    http://tampermonkey.net/
// @version      1.2.1
// @description  Загрузка предыдущих страниц (ручной и автоматический режим)
// @author       RoxMarty & DeepSeek feat. YarikG
// @match        https://rutr.life/forum/viewtopic.php*
// @match        https://rutracker.net/forum/viewtopic.php*
// @match        https://rutracker.nl/forum/viewtopic.php*
// @match        https://rutracker.lib/forum/viewtopic.php*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow
// ==/UserScript==
(function() {
    'use strict';
    let isLoading = false;
    let loadedPages = new Set();
    let consecutiveFailures = 0;
    const MAX_CONSECUTIVE_FAILURES = 3;
    // Настройки по умолчанию - РУЧНОЙ РЕЖИМ
    const defaultSettings = {
        enabled: true,
        mode: 'button', // 'button' или 'auto'
        showSettings: false,
        maxPagesToLoad: 50
    };
    let settings = { ...defaultSettings };
    // Загрузка настроек
    function loadSettings() {
        try {
            const saved = GM_getValue('rutracker_prev_loader_settings');
            if (saved) {
                settings = { ...defaultSettings, ...saved };
            }
            console.log('RuTracker: Settings loaded:', settings);
        } catch (error) {
            console.error('RuTracker: Error loading settings:', error);
            settings = { ...defaultSettings };
        }
    }
    // Сохранение настроек
    function saveSettings() {
        try {
            GM_setValue('rutracker_prev_loader_settings', settings);
            console.log('RuTracker: Settings saved:', settings);
        } catch (error) {
            console.error('RuTracker: Error saving settings:', error);
        }
    }
    // Функция для поиска ссылки на предыдущую страницу
    function findPreviousPageLink() {
        if (!settings.enabled) return null;
        if (loadedPages.size >= settings.maxPagesToLoad) {
            console.log('RuTracker: Maximum pages limit reached');
            return null;
        }
        if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
            console.log('RuTracker: Too many consecutive failures');
            return null;
        }
        const paginations = document.querySelectorAll('#pagination, .nav');
        let prevLink = null;
        let currentStart = getCurrentStart();
        console.log('RuTracker: Searching pages. Current start:', currentStart);
        paginations.forEach(pagination => {
            var first_url = new URLSearchParams(document.querySelector("link[rel='canonical']").getAttribute("href").split('?')[1]);
            first_url.set('start', 0);
            var first_link = Object.assign(document.createElement("a"), {href: '?'+first_url.toString()});
            const links = Array.prototype.slice.call(pagination.querySelectorAll('a[href*="start="]'));
            links.push(first_link);
            links.forEach(link => {
                const href = link.href;
                const match = href.match(/start=(\d+)/);
                if (match) {
                    const start = parseInt(match[1]);
                    // Ищем страницы с start МЕНЬШЕ текущего
                    if (start < currentStart && !loadedPages.has(start)) {
                        // Находим самую близкую страницу (максимальный start)
                        if (!prevLink || start > getStartFromUrl(prevLink.href)) {
                            prevLink = link;
                        }
                    }
                }
            });
        });
        console.log('RuTracker: Found previous page:', prevLink ? getStartFromUrl(prevLink.href) : 'none');
        return prevLink;
    }
    // Получаем текущий start параметр
    function getCurrentStart() {
        const urlParams = new URLSearchParams(document.querySelector("link[rel='canonical']").getAttribute("href").split('?')[1]);
        const start = parseInt(urlParams.get('start')) || 0;
        return start;
    }
    // Получаем start из URL
    function getStartFromUrl(url) {
        const match = url.match(/start=(\d+)/);
        return match ? parseInt(match[1]) : 0;
    }
    // Проверяем, есть ли еще страницы для загрузки
    function hasMorePages() {
        const prevLink = findPreviousPageLink();
        return !!prevLink;
    }
    // Загрузка предыдущей страницы
    function loadPreviousPage() {
        if (!settings.enabled || isLoading) {
            return;
        }
        const prevLink = findPreviousPageLink();
        if (!prevLink) {
            console.log('RuTracker: No previous page found');
            updateUI();
            return;
        }
        const prevStart = getStartFromUrl(prevLink.href);
        if (loadedPages.has(prevStart)) {
            return;
        }
        isLoading = true;
        consecutiveFailures = 0;
        updateUI();
        console.log('RuTracker: Loading page with start:', prevStart);
        const scrollYBefore = window.scrollY;
        const markerPost = findMarkerPost();
        GM_xmlhttpRequest({
            method: "GET",
            url: prevLink.href,
            timeout: 30000,
            onload: function(response) {
                if (response.status !== 200) {
                    handleLoadError('HTTP error: ' + response.status);
                    return;
                }
                const parser = new DOMParser();
                const doc = parser.parseFromString(response.responseText, 'text/html');
                if (!doc.querySelector('#topic_main')) {
                    handleLoadError('Invalid page structure');
                    return;
                }
                const posts = doc.querySelectorAll('#topic_main > tbody[id^="post_"]');
                console.log('RuTracker: Found posts:', posts.length);
                if (posts.length === 0) {
                    handleLoadError('No posts found');
                    return;
                }
                const heightBefore = document.documentElement.scrollHeight;
                const topicMain = document.getElementById('topic_main');
                const firstPost = topicMain.querySelector('tbody[id^="post_"]');
                const fragment = document.createDocumentFragment();
                Array.from(posts).forEach((post) => {
                    const clonedPost = post.cloneNode(true);
                    fragment.appendChild(clonedPost);
                });
                if (unsafeWindow.BB) {
                    Array.from(fragment.children).forEach(post => {
                        const postBody = post.querySelector('.post_body');
                        if (postBody) {
                            unsafeWindow.BB.initPost(postBody);
                        }
                        const signature = post.querySelector('.signature');
                        if (signature) {
                            unsafeWindow.BB.initPost(signature);
                        }
                    });
                }
                if (firstPost) {
                    topicMain.insertBefore(fragment, firstPost);
                } else {
                    topicMain.appendChild(fragment);
                }
                updatePagination(doc);
                loadedPages.add(prevStart);
                restoreScrollPosition(heightBefore, markerPost, scrollYBefore);
                showStatus('+1 страница');
                isLoading = false;
                updateUI();
            },
            onerror: function(error) {
                handleLoadError('Network error');
            },
            ontimeout: function() {
                handleLoadError('Request timeout');
            }
        });
    }
    // Обработчик ошибок загрузки
    function handleLoadError(errorMessage) {
        consecutiveFailures++;
        console.error('RuTracker: Load error:', errorMessage);
        showStatus('Ошибка загрузки');
        isLoading = false;
        updateUI();
    }
    // Находим пост, который был вверху экрана до загрузки
    function findMarkerPost() {
        const posts = document.querySelectorAll('#topic_main > tbody[id^="post_"]');
        let bestPost = null;
        let minDistance = Infinity;
        posts.forEach(post => {
            const rect = post.getBoundingClientRect();
            if (rect.top >= 0) {
                const distance = rect.top;
                if (distance < minDistance) {
                    minDistance = distance;
                    bestPost = post;
                }
            }
        });
        return bestPost || posts[0];
    }
    // Восстанавливаем позицию прокрутки
    function restoreScrollPosition(heightBefore, markerPost, scrollYBefore) {
        if (markerPost && document.contains(markerPost)) {
            const newMarkerRect = markerPost.getBoundingClientRect();
            const scrollDelta = newMarkerRect.top;
            window.scrollTo(0, scrollYBefore + scrollDelta);
        } else {
            const heightAfter = document.documentElement.scrollHeight;
            const heightDiff = heightAfter - heightBefore;
            window.scrollTo(0, scrollYBefore + heightDiff);
        }
    }
    // Автоматическая загрузка (работает только в auto-режиме)
    function initAutoLoad() {
        console.log('RuTracker: Auto-load initialized:', settings.mode);
        if (settings.mode !== 'auto') return;
        let checkInProgress = false;
        window.addEventListener('scroll', function() {
            if (!settings.enabled || settings.mode !== 'auto' || isLoading || checkInProgress) {
                return;
            }
            if (!hasMorePages()) {
                return;
            }
            checkInProgress = true;
            setTimeout(() => {
                const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
                // Загружаем когда близко к верху
                if (scrollTop <= 150) {
                    console.log('RuTracker: Auto-load triggered');
                    loadPreviousPage();
                }
                checkInProgress = false;
            }, 100);
        }, { passive: true });
    }
    // Создаем индикатор статуса
    function createStatusIndicator() {
        const oldIndicator = document.getElementById('rutracker-status-indicator');
        if (oldIndicator) {
            oldIndicator.remove();
        }
        const indicator = document.createElement('div');
        indicator.id = 'rutracker-status-indicator';
        indicator.style.cssText = `
            position: fixed;
            top: 8px;
            left: 45px;
            background: #2196F3;
            color: white;
            padding: 4px 8px;
            border-radius: 3px;
            font-size: 12px;
            font-weight: bold;
            z-index: 9997;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
            cursor: pointer;
            white-space: nowrap;
        `;
        indicator.addEventListener('click', function() {
            // Переключаем режим
            settings.mode = settings.mode === 'auto' ? 'button' : 'auto';
            saveSettings();
            updateUI();
            if (settings.mode === 'auto') {
                initAutoLoad();
            }
            // Показываем уведомление о смене режима
            showStatus('Режим: ' + (settings.mode === 'auto' ? 'Авто' : 'Ручной'));
        });
        document.body.appendChild(indicator);
        return indicator;
    }
    // Обновляем индикатор статуса
    function updateStatusIndicator() {
        const indicator = document.getElementById('rutracker-status-indicator');
        if (!indicator) return;
        if (!settings.enabled) {
            indicator.textContent = '❌ Выкл';
            indicator.style.background = '#f44336';
        } else if (!hasMorePages()) {
            indicator.textContent = '✅ ' + loadedPages.size;
            indicator.style.background = '#4CAF50';
        } else if (settings.mode === 'auto') {
            indicator.textContent = '🔁 Автоподгрузка';
            indicator.style.background = '#4CAF50';
        } else {
            indicator.textContent = '🔘 Ручная подгрузка';
            indicator.style.background = '#2196F3';
        }
        indicator.title = 'Загружено: ' + loadedPages.size + ' стр.\nРежим: ' +
                         (settings.mode === 'auto' ? 'Автоподгрузка' : 'Ручная подгрузка') +
                         '\nКлик для смены режима';
    }
    // Обновляем пагинацию
    function updatePagination(newDoc) {
        const newPagination = newDoc.querySelector('#pagination');
        const currentPagination = document.querySelector('#pagination');
        if (newPagination && currentPagination) {
            currentPagination.innerHTML = newPagination.innerHTML;
            updateUI();
        }
        // Обновляем верхнюю пагинацию
        const newTopPagination = newDoc.querySelector('.vBottom b');
        const currentTopPagination = document.querySelector('.vBottom b');
        if (newTopPagination && currentTopPagination) {
            currentTopPagination.innerHTML = newTopPagination.innerHTML;
        }
    }
    // Обновляем весь UI
    function updateUI() {
        updateLoadButton();
        updateSettingsPanel();
        updateStatusIndicator();
    }
    // Обновляем кнопку загрузки
    function updateLoadButton() {
        const button = document.getElementById('rutracker-prev-load-btn');
        if (!button) return;
        if (!settings.enabled || settings.mode !== 'button') {
            button.style.display = 'none';
            return;
        }
        button.style.display = 'inline-block';
        const prevLink = findPreviousPageLink();
        if (!prevLink) {
            button.textContent = 'Нет предыдущих страниц';
            button.disabled = true;
            button.style.background = '#cccccc';
            button.style.cursor = 'not-allowed';
        } else {
            button.textContent = isLoading ? 'Загрузка...' : '↑ Предыдущая страница (' + (loadedPages.size + 1) + ')';
            button.disabled = isLoading;
            button.style.background = isLoading ? '#cccccc' : '#4CAF50';
            button.style.cursor = isLoading ? 'not-allowed' : 'pointer';
        }
    }
    // Создаем панель настроек
    function createSettingsPanel() {
        const panel = document.createElement('div');
        panel.id = 'rutracker-settings-panel';
        panel.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 2px solid #4CAF50;
            border-radius: 6px;
            padding: 15px;
            z-index: 10000;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
            min-width: 320px;
            font-family: Arial, sans-serif;
            display: none;
        `;
        panel.innerHTML = `
            <div style="margin-bottom: 12px; border-bottom: 1px solid #eee; padding-bottom: 8px;">
                <h3 style="margin: 0; color: #4CAF50; font-size: 16px;">Настройки загрузки страниц</h3>
            </div>
            <div style="margin-bottom: 12px;">
                <label style="display: flex; align-items: center; margin-bottom: 8px; cursor: pointer; font-size: 13px;">
                    <input type="checkbox" id="settings-enabled" ${settings.enabled ? 'checked' : ''}
                           style="margin-right: 6px;">
                    <span>Включить загрузку предыдущих страниц</span>
                </label>
            </div>
            <div style="margin-bottom: 15px;">
                <div style="margin-bottom: 6px; font-weight: bold; font-size: 13px;">Режим работы:</div>
                <label style="display: block; margin-bottom: 6px; cursor: pointer; font-size: 13px;">
                    <input type="radio" name="mode" value="button" ${settings.mode === 'button' ? 'checked' : ''}
                           style="margin-right: 6px;">
                    Ручная подгрузка
                </label>
                <label style="display: block; margin-bottom: 6px; cursor: pointer; font-size: 13px;">
                    <input type="radio" name="mode" value="auto" ${settings.mode === 'auto' ? 'checked' : ''}
                           style="margin-right: 6px;">
                    Автоподгрузка
                </label>
            </div>
            <div style="margin-bottom: 12px;">
                <label style="display: flex; align-items: center; margin-bottom: 8px; font-size: 13px;">
                    <span style="margin-right: 8px; min-width: 110px;">Максимум страниц:</span>
                    <input type="number" id="settings-max-pages" value="${settings.maxPagesToLoad}"
                           min="1" max="1000" style="width: 70px; padding: 3px; font-size: 12px;">
                </label>
            </div>
            <div style="color: #666; font-size: 11px; margin-bottom: 12px; padding: 8px; background: #f9f9f9; border-radius: 3px;">
                <strong>Статистика:</strong><br>
                • Загружено: <span id="stats-loaded">${loadedPages.size}</span> страниц<br>
                • Текущий start: ${getCurrentStart()}<br>
                • Режим: ${settings.mode === 'auto' ? 'Автоподгрузка' : 'Ручная подгрузка'}<br>
                • Доступно: <span id="stats-available">${hasMorePages() ? 'Да' : 'Нет'}</span>
            </div>
            <div style="display: flex; justify-content: space-between;">
                <button id="settings-save" style="
                    background: #4CAF50; color: white; border: none;
                    padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 12px;">
                    Сохранить
                </button>
                <button id="settings-close" style="
                    background: #f44336; color: white; border: none;
                    padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 12px;">
                    Закрыть
                </button>
            </div>
        `;
        panel.querySelector('#settings-save').addEventListener('click', saveSettingsHandler);
        panel.querySelector('#settings-close').addEventListener('click', closeSettings);
        return panel;
    }
    // Обработчик сохранения настроек
    function saveSettingsHandler() {
        settings.enabled = document.getElementById('settings-enabled').checked;
        settings.mode = document.querySelector('input[name="mode"]:checked').value;
        settings.maxPagesToLoad = parseInt(document.getElementById('settings-max-pages').value) || 50;
        settings.showSettings = false;
        saveSettings();
        closeSettings();
        updateUI();
        if (settings.mode === 'auto') {
            initAutoLoad();
        }
        // Показываем уведомление о сохранении
        showStatus('Настройки сохранены');
    }
    // Закрытие панели настроек
    function closeSettings() {
        settings.showSettings = false;
        const panel = document.getElementById('rutracker-settings-panel');
        if (panel) {
            panel.style.display = 'none';
        }
    }
    // Показ панели настроек
    function showSettings() {
        settings.showSettings = true;
        const panel = document.getElementById('rutracker-settings-panel');
        if (panel) {
            panel.style.display = 'block';
            updateSettingsPanel();
        }
    }
    // Обновление панели настроек
    function updateSettingsPanel() {
        const panel = document.getElementById('rutracker-settings-panel');
        if (panel) {
            panel.querySelector('#settings-enabled').checked = settings.enabled;
            panel.querySelector(`input[name="mode"][value="${settings.mode}"]`).checked = true;
            panel.querySelector('#settings-max-pages').value = settings.maxPagesToLoad;
            panel.querySelector('#stats-loaded').textContent = loadedPages.size;
            panel.querySelector('#stats-available').textContent = hasMorePages() ? 'Да' : 'Нет';
        }
    }
    // Создаем кнопку настроек
    function createSettingsButton() {
        const settingsBtn = document.createElement('button');
        settingsBtn.id = 'rutracker-settings-btn';
        settingsBtn.innerHTML = '⚙️';
        settingsBtn.title = 'Настройки загрузки страниц';
        settingsBtn.style.cssText = `
            position: fixed;
            top: 8px;
            left: 8px;
            width: 28px;
            height: 28px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            z-index: 9999;
            font-size: 14px;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
            display: flex;
            align-items: center;
            justify-content: center;
        `;
        settingsBtn.addEventListener('click', showSettings);
        return settingsBtn;
    }
    // Добавляем кнопку загрузки
    function addManualLoadButton() {
        let oldButton = document.getElementById('rutracker-prev-load-btn');
        if (oldButton) {
            oldButton.remove();
        }
        if (!settings.enabled) {
            return;
        }
        let container = document.querySelector('.pagination');
        if (!container) {
            container = document.querySelector('.nav');
        }
        if (!container) {
            container = document.querySelector('.bottom_info');
        }
        if (!container) {
            container = document.createElement('div');
            container.style.textAlign = 'center';
            container.style.margin = '10px 0';
            container.style.padding = '10px';
            container.style.background = '#f5f5f5';
            container.style.borderRadius = '5px';
            const contentTable = document.querySelector('#page_content .main-content table');
            if (contentTable) {
                contentTable.parentNode.insertBefore(container, contentTable);
            } else {
                document.querySelector('#page_content .main-content').appendChild(container);
            }
        }
        const manualButton = document.createElement('button');
        manualButton.id = 'rutracker-prev-load-btn';
        manualButton.style.cssText = `
            margin: 3px;
            padding: 6px 12px;
            background: #4CAF50;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        `;
        manualButton.addEventListener('click', (e) => {
            e.preventDefault();
            loadPreviousPage();
        });
        container.insertBefore(manualButton, container.firstChild);
        updateLoadButton();
    }
    // Показываем статус (под переключателем режимов)
    function showStatus(message) {
        const statusDiv = document.createElement('div');
        statusDiv.style.cssText = `
            position: fixed;
            top: 42px;
            left: 45px;
            background: #555;
            color: white;
            padding: 4px 8px;
            border-radius: 3px;
            font-size: 11px;
            z-index: 9996;
            box-shadow: 0 1px 3px rgba(0,0,0,0.2);
            opacity: 0.9;
        `;
        statusDiv.textContent = message;
        document.body.appendChild(statusDiv);
        setTimeout(() => {
            if (statusDiv.parentNode) {
                statusDiv.parentNode.removeChild(statusDiv);
            }
        }, 1500);
    }
    // Инициализация
    function init() {
        console.log('RuTracker: Starting initialization...');
        loadSettings();
        if (!document.getElementById('topic_main')) {
            setTimeout(init, 500);
            return;
        }
        loadedPages.clear();
        consecutiveFailures = 0;
        // Добавляем элементы в DOM
        if (!document.getElementById('rutracker-settings-panel')) {
            document.body.appendChild(createSettingsPanel());
        }
        if (!document.getElementById('rutracker-settings-btn')) {
            document.body.appendChild(createSettingsButton());
        }
        if (!document.getElementById('rutracker-status-indicator')) {
            document.body.appendChild(createStatusIndicator());
        }
        addManualLoadButton();
        initAutoLoad();
        updateUI();
        console.log('RuTracker: Script initialized successfully');
        console.log('RuTracker: Mode:', settings.mode);
        console.log('RuTracker: Current start:', getCurrentStart());
    }
    // Запускаем инициализацию
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
Выглядит так
Описание
RuTracker Подгрузка предыдущих страниц
📋 Назначение
Скрипт автоматически подгружает предыдущие страницы в темах RuTracker, позволяя читать топик с начала без перехода по страницам.
🎯 Основные функции
Режимы работы:
🔘 Ручная подгрузка - загрузка по кнопке "↑ Предыдущая страница"
🔁 Автоподгрузка - автоматическая загрузка при прокрутке к верху
Элементы управления:
⚙️ Кнопка настроек (слева вверху) - открывает панель настроек
Индикатор статуса - показывает режим и количество загруженных страниц
Кнопка загрузки - в пагинации темы (только в ручном режиме)
⚙️ Настройки
• Включение/выключение скрипта
• Выбор режима работы (ручной/авто)
• Лимит загружаемых страниц (1-1000)
• Статистика загрузки
🚀 Как использовать
1. Активация - скрипт работает автоматически при заходе в тему
2. Смена режима - кликните на индикатор статуса для переключения режимов
3. Загрузка - в ручном режиме используйте кнопку, в авто - просто листайте вверх
4. Настройки - откройте панель настроек для тонкой настройки параметров
💡 Особенности
• Сохраняет позицию прокрутки при загрузке новых страниц
• Работает со всеми доменами RuTracker
• Сохраняет настройки между сессиями
• Поддерживает BB-код и подписи
• Автоматически обновляет пагинацию
Индикатор статуса показывает: количество загруженных страниц, текущий режим и доступность дополнительных страниц для загрузки.
Для установки требуется браузерное расширение Tampermonkey!
[Профиль]  [ЛС] 
 
Ответить
Loading...
Error