Niii7
Опытный
SVOERODNOE25
// ==UserScript==
// @name Litres universal filter + prune empty pagination pages
// @namespace http://tampermonkey.net/
// @version 1.8
// @description Filter books and hide pagination pages without visible books on litres.ru SPA
// @match *://litres.ru/*
// @match *://*.litres.ru/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const SHOW_LABEL = 'Взять себе';
const HIDE_LABELS = ['Читать', 'Слушать'];
// Cache for checked pages: page number => true if has visible books, false if none
const pageCheckCache = {};
// Selectors
const BOOK_SELECTOR = 'div[role="group"][data-testid="art__wrapper"]';
const PAGINATOR_LINK_SELECTOR = 'div.PaginatedContent-module__7njuza__pages a[href*="?page="]';
// Utility: Check if element or any child contains exact label
function containsLabel(el, label) {
if (!el) return false;
if (el.textContent && el.textContent.trim() === label) return true;
for (const child of el.children) {
if (containsLabel(child, label)) return true;
}
return false;
}
// Find label text inside a book element
function findLabelInBook(bookEl) {
const selectors = ['button', '[role="button"]', '[data-testid*="button"]'];
for (const sel of selectors) {
const candidates = bookEl.querySelectorAll(sel);
for (const candidate of candidates) {
const text = candidate.textContent.trim();
if (text === SHOW_LABEL) return SHOW_LABEL;
if (HIDE_LABELS.includes(text)) return text;
if (containsLabel(candidate, SHOW_LABEL)) return SHOW_LABEL;
if (HIDE_LABELS.some(lbl => containsLabel(candidate, lbl))) {
return HIDE_LABELS.find(lbl => containsLabel(candidate, lbl));
}
}
}
return null;
}
// Filter books on current page and return count of visible books
function filterBooks() {
const books = document.querySelectorAll(BOOK_SELECTOR);
let visibleCount = 0;
books.forEach(book => {
const label = findLabelInBook(book);
if (label === SHOW_LABEL) {
book.style.display = '';
visibleCount++;
} else {
book.style.display = 'none';
}
});
return visibleCount;
}
// Check an external page by URL if it contains any visible books
// Loads page in hidden iframe, filters, counts visible, returns Promise<boolean>
function checkPageHasVisibleBooks(url) {
return new Promise((resolve) => {
const pageNum = (new URL(url, location.href)).searchParams.get('page') || '1';
if (pageCheckCache[pageNum] !== undefined) {
resolve(pageCheckCache[pageNum]);
return;
}
// Create hidden iframe
const iframe = document.createElement('iframe');
iframe.style.position = 'fixed';
iframe.style.left = '0';
iframe.style.top = '0';
iframe.style.width = '0';
iframe.style.height = '0';
iframe.style.border = 'none';
iframe.style.visibility = 'hidden';
iframe.src = url;
document.body.appendChild(iframe);
iframe.onload = () => {
try {
const doc = iframe.contentDocument || iframe.contentWindow.document;
const bookEls = doc.querySelectorAll(BOOK_SELECTOR);
let hasVisible = false;
bookEls.forEach(book => {
const label = findLabelInBook(book);
// Only consider visible books as ones labeled SHOW_LABEL
if (label === SHOW_LABEL) {
hasVisible = true;
}
});
pageCheckCache[pageNum] = hasVisible;
document.body.removeChild(iframe);
resolve(hasVisible);
} catch(e) {
// Cross-origin or other errors, keep page visible
document.body.removeChild(iframe);
resolve(true);
}
};
// In case iframe loading fails after 10s, assume visible
setTimeout(() => {
if (document.body.contains(iframe)) {
document.body.removeChild(iframe);
resolve(true);
}
}, 10000);
});
}
// Prune pagination links that lead to pages with no visible books
async function prunePagination() {
const paginator = document.querySelector('div.PaginatedContent-module__7njuza__pages, div[data-testid="paginator--wrapper"]');
if (!paginator) return;
const links = paginator.querySelectorAll(PAGINATOR_LINK_SELECTOR);
if (!links.length) return;
for (let link of links) {
const href = link.getAttribute('href');
if (!href) continue;
const pageNumMatch = href.match(/[?&]page=(\d+)/i);
if (!pageNumMatch) continue;
const pageNum = pageNumMatch[1];
// Skip current page link or active page as removing it would confuse user
if (link.closest('li')?.classList.contains('Paginator-module__g51rda__page_active')) continue;
try {
const visible = await checkPageHasVisibleBooks(href);
if (!visible) {
// Remove the entire list item if possible to keep UI consistent
const li = link.closest('li');
if (li) {
li.remove();
} else {
link.remove();
}
console.log(`Removed pagination page ${pageNum} (empty after filtering)`);
}
} catch (e) {
// On error keep link visible
console.warn('Error checking pagination page', href, e);
}
}
}
// Main filtering + pagination prune orchestrator
async function mainFilterCycle() {
filterBooks();
await prunePagination();
}
// Debounce helper
let debounceTimeout;
function debouncedMainFilter(delay=300) {
if (debounceTimeout) clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(mainFilterCycle, delay);
}
// Setup mutation observer to watch for content and pagination changes
function setupObservers() {
const container = document.querySelector('div[data-testid="collection__content--wrapper"], div.PaginatedContent-module__7njuza__wrapper');
if (!container) return;
const observer = new MutationObserver(() => {
debouncedMainFilter();
});
observer.observe(container, {childList: true, subtree: true});
}
// Patch SPA navigation events (history API)
function patchHistoryAndSetup() {
const origPush = history.pushState;
history.pushState = function(...args) {
origPush.apply(this, args);
window.dispatchEvent(new Event('locationchange'));
};
const origReplace = history.replaceState;
history.replaceState = function(...args) {
origReplace.apply(this, args);
window.dispatchEvent(new Event('locationchange'));
};
window.addEventListener('popstate', () => window.dispatchEvent(new Event('locationchange')));
window.addEventListener('locationchange', () => {
// Slight delay for SPA content to render
setTimeout(() => {
debouncedMainFilter();
setupObservers();
}, 500);
});
}
// Initial run on page load
window.addEventListener('load', () => {
patchHistoryAndSetup();
setupObservers();
debouncedMainFilter();
});
// Run ASAP if script loaded late
if (document.readyState === 'complete') {
patchHistoryAndSetup();
setupObservers();
debouncedMainFilter();
}
})();
Не активирует указанную вами. Активирует отсутствующую:
Обновилась подборка https://www.litres.ru/collections/podborka-knig-ot-doshirak-h-litres/ (промокоды, скорее всего, одноразовые), лендинг - https://www.litres.ru/landing/doshirakmarth25
Мегаакция «КЭШБУМ!»«Правила стимулирующего мероприятия «КЭШБУМ!»: https://www.promodoshirak.ru/upload/rules.pdfГлавная
www.promodoshirak.ru
Условное наименование Призов: Гарантированный подарок №1
Описание Призов: 1 (один) промо-код на скачивание электронной книги из представленной подборки на сайте www.litres.ru (далее по тексту - промо-код Литрес)
Общее количество призов за Мероприятие, шт.: 2 500 000 (два миллиона пятьсот тысяч)
Условное наименование: Регистрация верного кода
Ограничение по количеству призов для одного участника, шт.: не ограничено.
3. Сроки проведения Мероприятия
3.1. Общий срок проведения Мероприятия: с 01 марта 2025 года по 31 декабря 2025 года.
3.2. Срок, указанный в пункте 3.1 настоящих Правил, включает в себя:
3.2.1. направление и прием заявок на участие в Мероприятии: в период с 00 часов 00 минут 00 секунд по московскому времени 01 марта 2025 года по 23 часа 59 минут 59 секунд по московскому времени 31 августа 2025 года.
Подборка книг от Доширак х Литрес! – подборка книг – Литрес
«Анатомия силовых упражнений с использованием в качестве отягощения собственного веса», «Деньги. Мастер игры», «Воспоминания биржевого спекулянта» и другие книги скачать в FB2, TXT, PDF, EPUB, DOC.www.litres.ru
Не активирует указанную вами. Активирует отсутствующую:
Заберите подарок от Литрес и фабрики детской мебели "Друг Кузя"! – подборка книг – Литрес
«Моя Лола. Записки мать-и-мачехи», «Дикий робот», «Эмоциональный интеллект ребенка» и другие книги скачать в FB2, TXT, PDF, EPUB, DOC.www.litres.ru
Вы правы. Были обновления. Код в названии. Также ввел 2 кода от приветМИР, Выдают код на номер телефона, карту МИР создал в Юмани, возможно и без карты дадут код, если её пропустить при регистрации. https://www.litres.ru/collections/223119/Вроде как обновилась подборка https://www.litres.ru/collections/wikireading2025/ и ещё 2 смежные похоже, ибо когда забрал оттуда 3 книги у меня было 21 акция (подборка) доступна, а стало 18.
Такое же, как лендингах, количество книг указано и в описании акций на сайтах:Для подборки https://www.litres.ru/collections/fotostrana-sezon-dach-i-piknikov/ есть два промокода:
GILMON_DACHA - 1 книга (если верить лендингу https://www.litres.ru/landing/GILMON_DACHA )
promokodus_dacha - 2 книги (если верить лендингу https://www.litres.ru/landing/promokodus_dacha )
Адрес акции - в программе "Бонус" от "Ростелекома", а если вы про лендинг, то он истек: https://www.litres.ru/?rostel2024q1Подборка от Ростелекома (адрес акции мне неизвестен): https://www.litres.ru/collections/novaya-podborka-knig-i-skidka-20-ot-rostelekoma-i-litres/
Написали бы сразу fotostrana_dacha3 книги
KMOILAS76D
До 21 июля.
По адресной строке подборки нетрудно догадаться, что есть и третий, который элементарно находится по несложной логике.
Адрес акции - в программе "Бонус" от "Ростелекома", а если вы про лендинг, то он истек: https://www.litres.ru/?rostel2024q1
Посмотреть вложение 931900
А удовольствие самому решить элементарную задачку (даже если лично вы его не получили)?Написали бы сразу fotostrana_dacha
halva_may - подборка https://www.litres.ru/collections/fotostrana-may-vremya-novyh-znakomstv/
promokodus_june - подборка https://www.litres.ru/collections/fotostrana-may-vremya-novyh-znakomstv/ (промокод promokodus_may к данной подборке уже не работает)
Есть парочка. Срок активации промокода: по 16.07.2025 г. Если кому нужны, пишите в личку.По 4 книги за каждый персональный промокод:
https://vamprivet.ru/promo/razvlecheniya/knizhnye-miry-v-vashem-smartfone-1/ - подборка https://www.litres.ru/collections/d...orki-ot-litres-i-programmy-loyalnosti-privet/ (лендинг https://www.litres.ru/landing/privetnspk )
privrqxqhmПо 4 книги за каждый персональный промокод:
https://vamprivet.ru/promo/razvlecheniya/knizhnye-miry-v-vashem-smartfone-1/ - подборка https://www.litres.ru/collections/d...orki-ot-litres-i-programmy-loyalnosti-privet/ (лендинг https://www.litres.ru/landing/privetnspk )
Система дает активировать 4 промокода на Литрес Подписку, потом пишет "Вы уже активировали данный промокод".https://get4click.ru/ext/MNFAYNA8 - подборка https://www.litres.ru/collections/darim-3-knigi-iz-podborki/
По данной ссылке из поста выше выбил +3 месяца подписки (3 промика по 1 месяцу) помимо самих книг
Флоктори только раз или два даёт 2 месяца подписки, но теперь требует подтвердить телефонСистема дает активировать 4 промокода на Литрес Подписку, потом пишет "Вы уже активировали данный промокод".
У меня не получилось активировать. Купон там только для новых подписчиков...Флоктори только раз или два даёт 2 месяца подписки, но теперь требует подтвердить телефонне могу проверить.
Витрина подарков — скидки и лучшие предложения
flocktory.com