|
|
|
(@_@-)
 Стаж: 3 года Сообщений: 376
|
(@_@-) ·
20-Авг-24 23:56
(1 год 2 месяца назад)
EL-34 писал(а):
86608942(@_@-)
Никак.
Правда?
Темные темы делают, а светлые нет?
|
|
|
|
EL-34
  Стаж: 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
  Стаж: 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 дня)
Может знает кто, есть ли подобный скрипт для 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 екстеншенов которые их смогут выкачать и так.
|
|
|
|
Кантор-Эль драко
  Стаж: 15 лет 11 месяцев Сообщений: 1813
|
Кантор-Эль драко ·
25-Фев-25 21:33
(спустя 40 мин.)
Цитата:
скачал все торренты
И выложил в паблик.
|
|
|
|
exiless
 Стаж: 1 год 9 месяцев Сообщений: 40
|
exiless ·
22-Апр-25 20:22
(спустя 1 месяц 24 дня)
скрипт написал - а разъяснения - нет..
|
|
|
|
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
  Стаж: 16 лет Сообщений: 240
|
copyMister ·
29-Апр-25 18:51
(спустя 9 мин.)
qweqwe876
Возможно, поможет включение "Режима разработчика" в настройках расширений (справа вверху), если браузер - Chrome или подобный.
Если нет, то можно попробовать другое расширение - Violentmonkey. Оно еще и с открытым кодом.
|
|
|
|
stаlkerok
 Стаж: 2 года 8 месяцев Сообщений: 3071
|
stаlkerok ·
02-Июн-25 16:08
(спустя 1 месяц 2 дня)
|
|
|
|
RewTeyi
 Стаж: 3 года 5 месяцев Сообщений: 543
|
RewTeyi ·
04-Июн-25 00:04
(спустя 1 день 7 часов)
Есть у кого правленная версия FastPic Upload?
Загрузка на new fastpic выдает undefined
[IMG]undefined[/IMG]
|
|
|
|
x86-64
  Стаж: 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
  Стаж: 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!
|
|
|
|