- // ==UserScript==
- // @name Literotica Downloader
- // @description Single page HTML download for Literotica with improved readability
- // @namespace literotica_downloader
- // @include https://tags.literotica.com/*
- // @include https://*.tags.literotica.com/*
- // @include https://www.literotica.com/new/stories*
- // @include https://literotica.com/c/*
- // @include https://**.literotica.com/c/*
- // @include https://www.literotica.com/c/*
- // @include https://www.literotica.com/authors/**/works/stories*
- // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
- // @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
- // @version 5.10
- // @grant GM_info
- // @grant GM_registerMenuCommand
- // @grant GM.registerMenuCommand
- // @grant GM_unregisterMenuCommand
- // @grant GM_openInTab
- // @grant GM_getValue
- // @grant GM.getValue
- // @grant GM_setValue
- // @grant GM.setValue
- // @grant GM_notification
- // @grant GM.notification
- // @author Improved by a random redditor and @nylonmachete, originally by Patrick Kolodziejczyk and fixed by i23234234
- // ==/UserScript==
- /* jshint esversion: 8 */
- // Those valuse can be modifyed by the icon GreaseMonkey > User script commands > Toggle ...
- let GMsetValue, GMgetValue, GMregisterMenuCommand, GMunregisterMenuCommand, GMnotification, GMopenInTab;
- var options= {
- 'isNightMode' : true,
- 'isNumberChapterInFilename': true,
- 'isUsernameInFilename' : true,
- 'isDescriptionInFilename' : false,
- 'isNoteInFilename' : false,
- 'isCategoryInFilename' : false,
- 'isBookmark' : false,
- 'fontsize': "2.2"
- };
-
- // Creating style for a download icon
- var downloadTooltip = 'Download as html';
- var bookmarkTooltip = 'Check as read';
- var bookmarkCheckTooltip = 'Un-check as read';
- var iconDonwload = '<i class="bi bi-arrow-down-circle" style="cursor: pointer;cursor: hand;margin-right:5px; font-size:14px"></i>';
- var iconBookmark = '<i class="bi bi-bookmark" style="cursor: pointer;cursor: hand;margin-right:5px; font-size:14px"></i>';
- var iconBookmarkCheck = '<i class="bi bi-bookmark-check-fill" style="cursor: pointer;cursor: hand;margin-right:5px; font-size:14px"></i>';
- var PREFIX_URL_PAGE = 'https://www.literotica.com';
- /**
- * Style HTML downloaded
- */
- var chapterStyle = '_style="line-height: 1.4em;" ';
- var descriptionStyle = ' style="line-height: 1.2em;" ';
-
- /*
- * Constante for Filename
- */
- var SEPARATOR = '_';
- var PREFIX_NOTE = 'RATING_';
- var POSTFIX_NUMER_CHAPTER ='_Part_Series';
-
- /*
- * Constant For logging
- */
- var PREFIX_LOG='[Literotica Downloader]';
-
- function calculateBodyStyle(){
- var toReturn = " style=\"";
- if(options.isNightMode){
- toReturn +="background-color:#333333; color: #EEEEEE;";
- }
- toReturn +="font-family: Helvetica,Arial,sans-serif; width: 50%; margin: 0 auto; line-height: 1.5em;";
- toReturn +="font-size:"+options.fontsize+"em; padding: 50px 0 50px 0;";
- toReturn +="\"";
- return toReturn;
- }
-
-
- // Create link elements
- const link1 = document.createElement('link');
- link1.href = 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css';
- link1.rel = 'stylesheet';
- link1.type = 'text/css';
-
- const link2 = document.createElement('link');
- link2.href = 'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css';
- link2.rel = 'stylesheet';
-
- const styleElement = document.createElement('style');
-
- // Set the type attribute
- styleElement.type = 'text/css';
-
- // Set the CSS rules
- styleElement.textContent = '.ldText{background-color: lightgray;width: 3em;},.ldChapter,.ldSerie {margin-left: auto;} ._works_wrapper_29o2p_1 ._works_item__series_expanded_parts_29o2p_40{margin-left: 0px !important} .lbIconOnStoryCardComponent {float:left}';
-
- // Append link elements to the head
- document.head.appendChild(link1);
- document.head.appendChild(link2);
- document.head.appendChild(styleElement);
-
- console.log("Literotica Downloader start addEventListener");
-
- window.addEventListener("load", function() {
- 'use strict';
- setTimeout(function() {
- const GMinfo = GM_info;
- const handlerInfo = GMinfo.scriptHandler;
- const isGM = Boolean(handlerInfo.toLowerCase() === 'greasemonkey');
- var panelOptionDownloader= `
- <div id="literoticaDownloaderOption" title="Options Literotica Downloader" >
- Please Configure the options :
- <fieldset>
- <legend>Interface: </legend>
- <div>
- <input type="checkbox" name="isBookmark" id="isBookmark" class="ldCheckbox">
- <span>Bookmark (locale storage)</span>
- </div>
- <div>Please refresh the page for thoses options</div>
- </fieldset>
- <fieldset>
- <legend>Reading: </legend>
- <div>
- <input type="checkbox" name="isNightMode" id="isNightMode" class="ldCheckbox">
- <span>Night Mode</span>
- </div>
- </fieldset>
- <fieldset>
- <legend>Filename: </legend>
- <div>
- <input type="checkbox" name="isNumberChapterInFilename" id="isNumberChapterInFilename" class="ldCheckbox">
- <span>Number of chapters in filename (for series)</span>
- </div>
- <div>
- <input type="checkbox" name="isUsernameInFilename" id="isUsernameInFilename" class="ldCheckbox">
- <span>Username in filename</span>
- </div>
- <div>
- <input type="checkbox" name="isDescriptionInFilename" id="isDescriptionInFilename" class="ldCheckbox">
- <span>Description in filename (Not on series)</span>
- </div>
- <div>
- <input type="checkbox" name="isNoteInFilename" id="isNoteInFilename" class="ldCheckbox">
- <span>Rating in filename (Not on series)</span>
- </div>
- <div>
- <input type="checkbox" name="isCategoryInFilename" id="isCategoryInFilename" class="ldCheckbox">
- <span>Category in filename (Not on series)</span>
- </div>
- <div>
- <span>FontSize (in em with . for decimal)</span>
- <input type="text" name="fontsize" id="fontsize" class="ldText">
- </div>
- </fieldset>
- </div>
- `;
- document.body.insertAdjacentHTML('beforeend',panelOptionDownloader);
- var ldCheckboxes = document.querySelectorAll('.ldCheckbox');
-
- ldCheckboxes.forEach(function(checkbox) {
- checkbox.addEventListener('change', async function() {
- console.log(this.id);
- GMsetValue(this.id, this.checked);
- console.log(PREFIX_LOG+'new value =>' + await GMgetValue(this.id));
- options[this.id] = this.checked;
- });
- });
- var ldInputTexts = document.querySelectorAll('.ldText');
-
- ldInputTexts.forEach(function(checkbox) {
- checkbox.addEventListener('change', async function() {
- console.log(this.id);
- GMsetValue(this.id, this.value);
- console.log('new value =>' + await GMgetValue(this.id));
- options[this.id] = this.value;
- console.log(options);
- });
- });
- console.log(PREFIX_LOG+"Start init dialog option");
- $( "#literoticaDownloaderOption" ).dialog({ autoOpen: false, width: 450 });
- console.log(PREFIX_LOG+"Snd init dialog option");
- if (isGM) {
- GMsetValue = GM.setValue;
- GMgetValue = GM.getValue;
- GMregisterMenuCommand = GM.registerMenuCommand;
- GMunregisterMenuCommand = function() {};
- GMnotification = GM.notification;
- console.log(PREFIX_LOG+"It's GM !");
- } else {
- console.log(PREFIX_LOG+"Other than GM...");
-
- GMsetValue = GM_setValue;
- GMgetValue = GM_getValue;
- GMregisterMenuCommand = GM_registerMenuCommand;
- GMunregisterMenuCommand = GM_unregisterMenuCommand;
- }
- initOptions();
-
- // Open dialog when the menu command is clicked
- GMregisterMenuCommand("Literotica Downloader Options", function() {
- $( "#literoticaDownloaderOption" ).dialog( "open" );
- });
-
- // Dialog setup
- var dialog = document.getElementById('literoticaDownloaderOption');
- dialog.style.display = 'none'; // Initially hide the dialog
-
- /*
- * Download Button for Chapter
- */
-
- addDownloadChapterButtonTo(document);
-
- /*
- * Download Button for Series
- */
- addDownloadSeriesButtonTo(document);
-
- /*
- * Download Button for individual Story
- */
- addDownloadIndividualStoryButtonTo(document);
-
- /*
- * Section download on tags
- */
- document.addEventListener('DOMNodeInserted', function(e) {
- if (e.target!= null && e.target.classList != null && e.target.classList.contains('ai_gJ')) {
- var target = e.target;
-
- var ai_iGElements = target.querySelectorAll('.ai_iG');
- if(ai_iGElements){
- ai_iGElements.forEach(function(element) {
- element.insertAdjacentHTML('afterbegin', '<span class="ldDownloadTag lbIconOnStoryCardComponent">' + iconDonwload + '</span>');
- });
-
- target.querySelectorAll('.ldDownloadTag.lbIconOnStoryCardComponent')
- .forEach(function(tag) {
- tag.addEventListener('click', function() {
- getABookForStoryOnStoryCardComponent(tag.parentNode.parentNode);
- });
- });
- }
- }
- });
- /*
- * Section download on category
- */
- console.log(PREFIX_LOG+"init category");
- document.querySelectorAll('.b-slb-item').forEach(function(element) {
- element.insertAdjacentHTML('afterbegin', '<span class="ldChapter lbIconOnStoryCardComponent">' + iconDonwload + '</span>');
- });
- document.querySelectorAll(".ldChapter.lbIconOnStoryCardComponent").forEach(function(tag) {
- tag.addEventListener('click', function(tag) {
- getABookForStoryItemCategory(tag.target.parentNode.parentNode);
- });
- });
- }, 250); // 1000 milliseconds = 1 second
- });
- function addDownloadSeriesButtonTo(parentElement){
- const titlesSeries = parentElement.querySelectorAll("[class*='_works_item__series_expanded_header_card'] [class*='_works_item__title'] [class*='_item_title']");
- titlesSeries.forEach(element => {
- // Create a new element to prepend
- const newElement = document.createElement('div');
- newElement.textContent = 'Prepended Element'; // Change this to whatever content you want
- var idIcon = Math.floor(Math.random() * 1000);
- // Prepend the new element before the selected element
- element.insertAdjacentHTML('beforebegin', '<span id="' + idIcon + '" class="ldSerie"> ' + iconDonwload + '</span>');
- element.classList.add("lbButtonAdded");
- });
-
- parentElement.querySelectorAll('.ldSerie').forEach(element => {
- element.addEventListener('click', function() {getABookForSerieDiv(element.parentNode.parentNode)});
- });
- }
- function addDownloadChapterButtonTo(parentElement){
- const titlesChapter = parentElement.querySelectorAll("[class*='_series_parts__item_'] [class*='_works_item__title'] [class*='item_title']");
- titlesChapter.forEach(element => {
- // Create a new element to prepend
- const newElement = document.createElement('div');
- newElement.textContent = 'Prepended Element'; // Change this to whatever content you want
- var idIcon = Math.floor(Math.random() * 1000);
- // Prepend the new element before the selected element
- element.insertAdjacentHTML('beforebegin', '<span id="' + idIcon + '" class="ldChapter"> ' + iconDonwload + '</span>');
- element.classList.add("lbButtonAdded");
- });
-
- parentElement.querySelectorAll('.ldChapter').forEach(element => {
- element.addEventListener('click', function() {getABookForStoryDiv(element.parentNode)});
- });
- }
- function addDownloadIndividualStoryButtonTo(parentElement){
- const titlesChapter = parentElement.querySelectorAll("[class*='_works_item__title'] [class*='item_title']:not(.lbButtonAdded)");
- titlesChapter.forEach(element => {
- // Create a new element to prepend
- const newElement = document.createElement('div');
- newElement.textContent = 'Prepended Element'; // Change this to whatever content you want
- var idIcon = Math.floor(Math.random() * 1000);
- // Prepend the new element before the selected element
- element.insertAdjacentHTML('beforebegin', '<span id="' + idIcon + '" class="ldChapter"> ' + iconDonwload + '</span>');
- element.classList.add("lbButtonAdded");
- });
-
- parentElement.querySelectorAll('.ldChapter').forEach(element => {
- element.addEventListener('click', function() {getABookForStoryDiv(element.parentNode)});
- });
- }
-
- async function initOptions(){
- // Init of all options
- for(const element of Object.getOwnPropertyNames(options)){
- await initCheckBox(element);
- await initInputText(element);
- }
- }
- async function initCheckBox(element, index, array) {
- options[element] = await GMgetValue(element,options[element]);
- $("#"+element)[0].checked= options[element];
-
- return true;
- }
- async function initInputText(element, index, array) {
- options[element] = await GMgetValue(element,options[element]);
- $("#"+element)[0].value= options[element];
- console.log(PREFIX_LOG+element+"-> "+options[element]);
- return true;
- }
-
- function getABookForStoryItemCategory(cardComponent) {
- console.log(PREFIX_LOG+"In getABookForStoryItemCategory");
- console.log(cardComponent);
- var link = cardComponent.querySelector('h3 a').getAttribute('href');
- var storyId = extractStoryId(link);
- var jsonStory = getJsonStoryByStoryId(extractStoryId(link));
- var title = jsonStory.submission.title;
- var note = jsonStory.submission.rate_all;
- var chapterTitle = jsonStory.submission.title;
- var description = jsonStory.submission.description;
- var category = jsonStory.submission.category_info.pageUrl;
- var author = jsonStory.submission.author.username;
- getABookForStory(storyId, title, note, chapterTitle, description, category, author);
- }
-
- function getABookForStoryOnStoryCardComponent(cardComponent) {
- console.log(PREFIX_LOG+"In getABookForStoryOnStoryCardComponent");
- console.log(cardComponent);
- var link = cardComponent.querySelector('.ai_ii').getAttribute('href');
- var storyId = extractStoryId(link);
- var jsonStory = getJsonStoryByStoryId(extractStoryId(link));
- var title = jsonStory.submission.title;
- var note = jsonStory.submission.rate_all;
- var chapterTitle = jsonStory.submission.title;
- var description = jsonStory.submission.description;
- var category = jsonStory.submission.category_info.pageUrl;
- var author = jsonStory.submission.author.username;
- getABookForStory(storyId, title, note, chapterTitle, description, category, author);
- }
-
- function openFullViewForDownloadSerie(){
- var moreExist= false;
- document.querySelectorAll("[class*='_show_more']").forEach(element => {
- if(element.innerText.trim().startsWith('View Full')){
- moreExist = true;
- console.log(PREFIX_LOG+"Opening "+element);
- element.click();
- }
- });
- if(moreExist){
- setTimeout(function() {
- openFullViewForDownloadSerie();
- }, 250);
- }
- }
- function extractSerieId(url) {
- // Split the URL by slashes
- const parts = url.split('/');
- // Get the last part of the array
- const lastPart = parts[parts.length - 1];
- return lastPart;
- }
- function getSerieLinkFormDiv(serieDiv){
- return serieDiv.querySelectorAll("[class*='_item_title_']")[0].href;
- }
- function getABookForSerieDiv(serieDiv) {
- console.log(PREFIX_LOG+"Processing series");
- console.log(serieDiv);
- openFullViewForDownloadSerie();
- var link = getSerieLinkFormDiv(serieDiv);
- var serieId = extractSerieId(link);
- var jsonSerie = getJsonSerieBySerieId(serieId).data;
- var title = jsonSerie.title;
- // Get the X Part Series
- var descriptionSeries = ""
- var author = jsonSerie.user.username;
- var numberChapter = jsonSerie.work_count;
- alert("Starting building file for " + title + " of " + author + " with "+numberChapter+".\nPlease wait...");
-
- var book = '<html>\n<head>\n<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">\n';
- book += '<title>' + title + '</title>';
- book += '<meta content="' +author+ '" name="author">';
- book += '</head>\n<body ' + calculateBodyStyle() + ' >';
-
- function addChapter(itemChapter) {
- console.log(PREFIX_LOG+"addChapter"+itemChapter);
- var storyId = itemChapter.url;
- var jsonStory = getJsonStoryByStoryId(storyId);
- var chapterTitle = jsonStory.submission.title;
- var description = jsonStory.submission.description;
- book += '<h1 class=\'chapter\'' + chapterStyle + '>' + chapterTitle + '</h1>';
- book += '<h2 class=\'chapter\'' + descriptionStyle + '>' + description + '</h2>';
- book += getContentOfStory(storyId);
- }
- console.log(serieDiv.nextElementSibling);
- var allChapter = serieDiv.nextElementSibling.querySelectorAll("[class*='_series_parts__item_'][class*='_works_item_']")
- var lastChapter = allChapter[allChapter.length - 1];
- console.log(lastChapter);
- var linkLastChapter = getChapterLinkFormDiv(lastChapter);
- console.log(PREFIX_LOG+"linkLastChapter => "+linkLastChapter);
- var storyId = extractStoryId(linkLastChapter);
- var jsonStory = getJsonStoryByStoryId(storyId);
- if(jsonStory.submission.series.items != null){
- jsonStory.submission.series.items.forEach(element => {
- console.log(element);
- // Your logic to perform on each element
- addChapter(element);
- });
- }else{
- console.log(PREFIX_LOG+"Last Chapter doesn't have series informations");
- console.log(PREFIX_LOG+"Falback on previous one");
- console.log(allChapter);
- lastChapter = allChapter[allChapter.length - 2];
- console.log(lastChapter);
- linkLastChapter = getChapterLinkFormDiv(lastChapter);
- console.log(PREFIX_LOG+"linkLastChapter => "+linkLastChapter);
- storyId = extractStoryId(linkLastChapter);
- jsonStory = getJsonStoryByStoryId(storyId);
- jsonStory.submission.series.items.forEach(element => {
- console.log(element);
- // Your logic to perform on each element
- addChapter(element);
- });
- }
- saveTextAsFile(book, buildFilename(title, author, descriptionSeries, null, null, numberChapter));
- }
- function getChapterLinkFormDiv(storyDiv){
- return storyDiv.querySelectorAll("[class*='_item_title_']")[0].href;
- }
-
- function getABookForStoryDiv(storyDiv){
- console.log(PREFIX_LOG+"Processing getABookForStoryDiv");
- console.log(storyDiv);
- var link = getChapterLinkFormDiv(storyDiv);
- var storyId = extractStoryId(link);
- var jsonStory = getJsonStoryByStoryId(extractStoryId(link));
- var title = jsonStory.submission.title;
- var note = jsonStory.submission.rate_all;
- var chapterTitle = jsonStory.submission.title;
- var description = jsonStory.submission.description;
- var category = jsonStory.submission.category_info.pageUrl;
- var author = jsonStory.submission.author.username;
- getABookForStory(storyId, title, note, chapterTitle, description, category, author);
- }
-
- function getABookForStory(storyId, title, note, chapterTitle, description, category, author) {
- var book = '<html>\n<head>\n<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">\n';
- console.log(PREFIX_LOG+'title' + title);
- book += '<title>' + title + '</title>';
- book += '<meta content="' + author + '" name="author">';
- book += '</head>\n<body ' + calculateBodyStyle() + ' >';
- if(chapterTitle != null){
- book += '<h1 class=\'chapter\'' + chapterStyle + '>' + chapterTitle + '</h1>';
- }
- if(description != null){
- book += '<h2 class=\'chapter\'' + descriptionStyle + '>' + description + '</h2>';
- }
- book += getContentOfStory(storyId);
-
- saveTextAsFile(book, buildFilename(title, author, description, note, category, null));
- }
-
- function extractStoryId(url) {
- // Split the URL by slashes
- const parts = url.split('/');
- // Get the last part of the array
- const lastPart = parts[parts.length - 1];
- return lastPart;
- }
- function getContentOfStoryOfPage(storyId, current_page) {
- var apiURL = "https://literotica.com/api/3/stories/"+storyId+'?params=%7B"contentPage"%3A'+current_page+'%7D';
- var xhr = new XMLHttpRequest();
- var toReturn ="[Page "+current_page+" not Found]";
- xhr.open('GET', apiURL, false); // false makes the request synchronous
- xhr.onload = function() {
- if (xhr.status >= 200 && xhr.status < 300) {
- const response = JSON.parse(xhr.responseText);
- toReturn = response.pageText;
- } else {
- console.error(PREFIX_LOG+'Request failed with status ' + xhr.status);
- }
- };
- xhr.onerror = function() {
- console.error(PREFIX_LOG+'Request failed');
- };
- xhr.send();
- return toReturn;
- }
-
- function getJsonSerieBySerieId(serieId){
- var apiURL = "https://literotica.com/api/3/series/"+serieId;
- console.log(PREFIX_LOG+"Fetching on API -> " + apiURL);
- var toReturn;
- var xhr = new XMLHttpRequest();
- xhr.open('GET', apiURL, false); // false makes the request synchronous
- xhr.onload = function() {
- if (xhr.status >= 200 && xhr.status < 300) {
- toReturn = JSON.parse(xhr.responseText);
- console.log(toReturn);
- } else {
- console.error(PREFIX_LOG+'Request failed with status ' + xhr.status);
- }
- };
- xhr.onerror = function() {
- console.error(PREFIX_LOG+'Request failed');
- };
- xhr.send();
- return toReturn;
- }
- function getJsonStoryByStoryId(storyId){
- var apiURL = "https://literotica.com/api/3/stories/"+storyId;
- console.log(PREFIX_LOG+"Fetching on API -> " + apiURL);
- var toReturn;
- var xhr = new XMLHttpRequest();
- xhr.open('GET', apiURL, false); // false makes the request synchronous
- xhr.onload = function() {
- if (xhr.status >= 200 && xhr.status < 300) {
- toReturn = JSON.parse(xhr.responseText);
- console.log(toReturn);
- } else {
- console.error(PREFIX_LOG+'Request failed with status ' + xhr.status);
- }
- };
- xhr.onerror = function() {
- console.error(PREFIX_LOG+'Request failed');
- };
- xhr.send();
- return toReturn;
- }
-
- function convertTextToHTML(text) {
- // Replace CRLF with <br> tags
- var htmlText = text.replace(/\r\n/g, "<br>");
- // Replace LF with <br> tags
- htmlText = htmlText.replace(/\n/g, "<br>");
- // Replace CR with <br> tags
- htmlText = htmlText.replace(/\r/g, "<br>");
- return htmlText;
- }
-
- // Function parsing all pages to get the storie based
- function getContentOfStory(storyId) {
- console.log(PREFIX_LOG+"Processing -> " + storyId);
- var apiURL = "https://literotica.com/api/3/stories/"+storyId;
- console.log(PREFIX_LOG+"Fetching on API -> " + apiURL);
- var toReturn;
- var xhr = new XMLHttpRequest();
- xhr.open('GET', apiURL, false); // false makes the request synchronous
- xhr.onload = function() {
- if (xhr.status >= 200 && xhr.status < 300) {
- const response = JSON.parse(xhr.responseText);
- toReturn = response.pageText;
- var pageCount = response.meta.pages_count;
- console.log(PREFIX_LOG+"pageCount => "+pageCount);
- if (pageCount > 0) {
- for (let currentPage = 2; currentPage <= pageCount; currentPage++) {
- const result = getContentOfStoryOfPage(storyId, currentPage); // Call the given function
- toReturn += result; // Concatenate the result to toReturn
- }
- }
- } else {
- console.error(PREFIX_LOG+'Request failed with status ' + xhr.status);
- }
- };
- xhr.onerror = function() {
- console.error(PREFIX_LOG+'Request failed');
- };
- xhr.send();
- toReturn = convertTextToHTML(toReturn);
- return toReturn;
- }
- // Function used to return content as a file for the user.
- function saveTextAsFile(textToWrite, fileNameToSaveAs) {
- var textFileAsBlob = new Blob([textToWrite], {
- type: 'text/javascript'
- });
- var downloadLink = document.createElement('a');
- downloadLink.download = fileNameToSaveAs;
- downloadLink.innerHTML = 'Download File';
- // Firefox requires the link to be added to the DOM
- // before it can be clicked.
- downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
- //downloadLink.onclick = destroyClickedElement;
- downloadLink.style.display = 'none';
- document.body.appendChild(downloadLink);
- downloadLink.click();
- }
-
- function buildFilename(title, author, description, note, category, numberChapter) {
- var toReturn = title;
- if (options.isNumberChapterInFilename) {
- toReturn = toReturn + SEPARATOR + numberChapter + POSTFIX_NUMER_CHAPTER;
- }
- if (options.isUsernameInFilename) {
- toReturn = toReturn + SEPARATOR + author;
- }
- if (options.isDescriptionInFilename) {
- if (description != null && description != "") {
- toReturn = toReturn + SEPARATOR + description.replace(/[^\w\s]/gi, '');
- }
- }
- if (options.isNoteInFilename) {
- if (note != null && note != "") {
- toReturn = toReturn + SEPARATOR + PREFIX_NOTE + note;
- }
- }
- if (options.isCategoryInFilename) {
- if (category != null && category != "") {
- toReturn = toReturn + SEPARATOR + category;
- }
- }
- // Add file extension;
- toReturn = toReturn + '.html';
- return toReturn;
- }
-
- function hasNodeClassStartingWith(node, startclass) {
- const classList = node.classList;
-
- for (let i = 0; i < classList.length; i++) {
- if (classList[i].startsWith(startclass)) {
- return true;
- }
- }
-
- return false;
- }
-
- // Callback function to be called when an element is added to the DOM
- function handleElementAdded(mutationsList, observer) {
- for(var mutation of mutationsList) {
- if (mutation.type === 'childList') {
- // Check if nodes were added
- mutation.addedNodes.forEach(function(node) {
- if (node.nodeType === 1 &&
- ( hasNodeClassStartingWith(node,'_series_parts__item')
- || hasNodeClassStartingWith(node,'_series_parts__wrapper')
- || hasNodeClassStartingWith(node,'_works_wrapper_')
- || hasNodeClassStartingWith(node,'_works_item_')
-
- )
- ) {
- //console.log(PREFIX_LOG+'Element added:', node);
- addDownloadChapterButtonTo(node);
- addDownloadSeriesButtonTo(node);
- addDownloadIndividualStoryButtonTo(node);
- // Call your function here or do any other necessary action
- }
- });
- }
- }
- }
-
- // Create a MutationObserver instance
- var observer = new MutationObserver(handleElementAdded);
-
- // Define the options for the observer (in this case, we're observing changes to the child list)
- var observerConfig = {
- childList: true,
- subtree: true // This option allows us to observe changes within the entire subtree of the target node
- };
-
- // Start observing the target node (in this example, we're observing the entire document)
- observer.observe(document, observerConfig);