Скачивает аудиокниги с "knigoblud.club"
// ==UserScript==
// @name "knigoblud.club" downloader
// @namespace http://tampermonkey.net/
// @version 2025-05-13
// @description Скачивает аудиокниги с "knigoblud.club"
// @author https://github.com/VladiStep
// @license GPL-3.0-only
// @match https://*.knigoblud.club/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=knigoblud.club
// @grant GM_download
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_notification
// ==/UserScript==
(async function() {
'use strict';
let stopped = false;
async function onStartClick() {
if (window.location.pathname === '/') {
alert('Вы на главной странице, выберите необходимую аудиокнигу.');
return;
}
try {
await startDownloading();
}
catch (e) {
alert(`Ошибка во время попытки загрузки аудиокниги:\n${e.message}`);
}
}
let startMenuID = GM_registerMenuCommand('Начать загрузку аудиокниги', () => onStartClick());
function replaceInvalidChars(folderName, replacementChar = '_') {
return folderName.replace(/[/\\?%*:|"<>]/g, replacementChar);
}
async function startDownloading() {
if (GM_info.downloadMode !== 'browser') {
alert('Не выставлен режим загрузки "Browser API", файлы не будут скачаны в отдельную папку.');
}
let bookTitleSafe = document.querySelector('div.PageTitle > h1')?.innerText;
if (!bookTitleSafe) {
throw new Error('Не найдено название книги.');
}
bookTitleSafe = replaceInvalidChars(bookTitleSafe);
const scripts = document.querySelectorAll('script:not([src])');
if (scripts.length === 0) {
throw new Error('Не найден ни один встроенный (не загружаемый с другого адреса) скрипт.');
}
const playlistScript = Array.prototype.find.call(scripts, x => x.innerHTML.includes('"playlist":'));
if (!playlistScript) {
throw new Error('Не найден скрипт, который инициализирует плейлист.');
}
const playlistJSON = (/^\s+?KB\.playerInit\(({.+?})\);/gm).exec(playlistScript.textContent)?.[1];
if (!playlistJSON) {
throw new Error('Не найдена функция (и JSON) инициализации плейлиста.');
}
let playlist = null;
try {
playlist = JSON.parse(playlistJSON);
if ((playlist.playlist?.length ?? 0) === 0) {
throw new Error('Не найден `playlist` внутри объекта плейлиста (или он пуст).');
}
}
catch (e) {
throw new Error(`Ошибка при чтении JSON плейлиста - ${e}.`);
}
const firstFileURI = playlist.playlist[0].src;
if (firstFileURI.includes('litres')) {
throw new Error('Доступна только пробная глава, переключитесь на мобильную версию (в "Инструментах разработчика") и обновите страницу.')
}
const fileExt = (/.+(\.[^\/]+?)($|#|\?)/g).exec(firstFileURI)?.[1];
if (!fileExt) {
throw new Error(`Не удалось извлечь расширение файла из "${firstFileURI}".`);
}
const origTitle = document.title;
let stopMenu = GM_registerMenuCommand('Прервать загрузку аудиокниги', () => { stopped = true });
GM_unregisterMenuCommand(startMenuID);
let hasError = false;
for (let i = 0; i < playlist.playlist.length; i++) {
if (stopped) {
GM_notification({text: 'Загрузка аудиокниги прервана.'});
break;
}
const downloaded = await new Promise((resolve) => {
GM_download({
url: playlist.playlist[i].src,
name: `${bookTitleSafe}/${String(i).padStart(2, '0')}${fileExt}`,
saveAs: false,
onload: () => resolve(true),
onerror: () => resolve(false)
});
});
if (!downloaded) {
throw new Error('Ошибка при скачивании.');
}
document.title = `Скачано ${i}/${playlist.playlist.length}.`;
}
document.title = origTitle;
if (!stopped) {
GM_notification({text: 'Загрузка аудиокниги завершена.'});
}
else {
stopped = false;
}
startMenuID = GM_registerMenuCommand('Начать загрузку аудиокниги', () => onStartClick());
GM_unregisterMenuCommand(stopMenu);
}
})();