Literotica Downloader

Single page HTML download for Literotica with improved readability

La data de 25-04-2021. Vezi ultima versiune.

  1. // ==UserScript==
  2. // @name Literotica Downloader
  3. // @description Single page HTML download for Literotica with improved readability
  4. // @namespace literotica_downloader
  5. // @include https://www.literotica.com/stories/memberpage.php*
  6. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
  7. // @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
  8. // @version 2.9
  9. // @grant GM_info
  10. // @grant GM_registerMenuCommand
  11. // @grant GM.registerMenuCommand
  12. // @grant GM_unregisterMenuCommand
  13. // @grant GM_openInTab
  14. // @grant GM_getValue
  15. // @grant GM.getValue
  16. // @grant GM_setValue
  17. // @grant GM.setValue
  18. // @grant GM_notification
  19. // @grant GM.notification
  20. // @author Improved by a random redditor and @nylonmachete, originally by Patrick Kolodziejczyk
  21. // ==/UserScript==
  22.  
  23. // Those valuse can be modifyed by the icon GreaseMonkey > User script commands > Toggle ...
  24. var options= {
  25. 'isNightMode' : true,
  26. 'isUsernameInFilename' : true,
  27. 'isDescriptionInFilename' : false,
  28. 'isNoteInFilename' : false,
  29. 'isCategoryInFilename' : false
  30. }
  31.  
  32. // @grant
  33. var SEPARATOR = '_';
  34. var PREFIX_NOTE = 'RATING_';
  35. var bodyStyleNight = ' style="background-color:#333333; color: #EEEEEE; font-family: Helvetica,Arial,sans-serif; width: 50%; margin: 0 auto; line-height: 1.5em; font-size:2.2em; padding: 50px 0 50px 0;" ';
  36. var bodyStyle = ' style="font-family: Helvetica,Arial,sans-serif; width: 50%; margin: 0 auto; line-height: 1.5em; font-size:2.2em; padding: 50px 0 50px 0;" ';
  37. var chapterStyle = '_style="line-height: 1.4em;" ';
  38. var descriptionStyle = ' style="line-height: 1.2em;" ';
  39. // Creating style for a download icon
  40. var iconDonwload = ' style=\'background-image: url("https://marcoceppi.github.io/bootstrap-glyphicons/img/glyphicons-halflings.png"); background-position: -120px -24px;cursor: pointer;cursor: hand; background-repeat: no-repeat; display: inline-block; height: 14px; line-height: 14px; vertical-align: text-bottom; width: 14px;}\'';
  41. var messagePleaseRefreshPage= 'For the modification to be taken into account.\nPlease refresh the page.';
  42. function buildFilename(title, author, description, note, category) {
  43. var toReturn = title;
  44. if (options.isUsernameInFilename) {
  45. toReturn = toReturn + SEPARATOR + author;
  46. }
  47. if (options.isDescriptionInFilename) {
  48. if (description != null && description != "") {
  49. toReturn = toReturn + SEPARATOR + description.replace(/[^\w\s]/gi, '');
  50. }
  51. }
  52. if (options.isNoteInFilename) {
  53. if (note != null && note != "") {
  54. toReturn = toReturn + SEPARATOR + PREFIX_NOTE + note;
  55. }
  56. }
  57. if (options.isCategoryInFilename) {
  58. if (category != null && category != "") {
  59. toReturn = toReturn + SEPARATOR + category;
  60. }
  61. }
  62. // Add file extension;
  63. toReturn = toReturn + '.html';
  64. return toReturn;
  65. }
  66. $("head").append (
  67. // loading CSS JqueryUI for options menu
  68. '<link href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet" type="text/css">'
  69. );
  70. $(document).ready(function() {
  71. 'use strict';
  72. const isdebug = false;
  73. const debug = isdebug ? console.log.bind(console) : () => {};
  74.  
  75. /* Perfectly Compatible For Greasemonkey4.0+, TamperMonkey, ViolentMonkey * F9y4ng * 20210209 */
  76.  
  77. let GMsetValue, GMgetValue, GMregisterMenuCommand, GMunregisterMenuCommand, GMnotification, GMopenInTab;
  78. const GMinfo = GM_info;
  79. const handlerInfo = GMinfo.scriptHandler;
  80. const isGM = Boolean(handlerInfo.toLowerCase() === 'greasemonkey');
  81.  
  82. debug(`//-> CheckGM: ${isGM} >> ${handlerInfo}`);
  83. var panelOptionDownloader= `
  84. <div id="literoticaDownloaderOption" title="Options Literotica Downloader" >
  85. Please Configure the options :
  86. <fieldset>
  87. <legend>Reading: </legend>
  88. <div>
  89. <input type="checkbox" name="isNightMode" id="isNightMode" class="ldCheckbox">
  90. <span>Night Mode</span>
  91. </div>
  92. </fieldset>
  93. <fieldset>
  94. <legend>Filename: </legend>
  95. <div>
  96. <input type="checkbox" name="isUsernameInFilename" id="isUsernameInFilename" class="ldCheckbox">
  97. <span>Username in filename</span>
  98. </div>
  99. <div>
  100. <input type="checkbox" name="isDescriptionInFilename" id="isDescriptionInFilename" class="ldCheckbox">
  101. <span>Description in filename (Not on series)</span>
  102. </div>
  103. <div>
  104. <input type="checkbox" name="isNoteInFilename" id="isNoteInFilename" class="ldCheckbox">
  105. <span>Rating in filename (Not on series)</span>
  106. </div>
  107. <div>
  108. <input type="checkbox" name="isCategoryInFilename" id="isCategoryInFilename" class="ldCheckbox">
  109. <span>Category in filename (Not on series)</span>
  110. </div>
  111. </fieldset>
  112. </div>
  113. `;
  114. $('body').append(panelOptionDownloader);
  115. // Handle of change of value by user.
  116. $('.ldCheckbox').change(function onChangeCheckBox(checkbox){
  117. GMsetValue(this.id, this.checked);
  118. options[this.id] = this.checked;
  119. });
  120. $( "#literoticaDownloaderOption" ).dialog({ autoOpen: false, width: 350 });
  121.  
  122. if (isGM) {
  123. GMsetValue = GM.setValue;
  124. GMgetValue = GM.getValue;
  125. GMregisterMenuCommand = GM.registerMenuCommand;
  126. GMunregisterMenuCommand = () => {};
  127. GMnotification = GM.notification;
  128. console.log("It's GM !");
  129. } else {
  130. console.log("Other")
  131. GMsetValue = GM_setValue;
  132. GMgetValue = GM_getValue;
  133. GMregisterMenuCommand = GM_registerMenuCommand;
  134. GMunregisterMenuCommand = GM_unregisterMenuCommand;
  135. }
  136. // Globale registry of the options menu.
  137. GMregisterMenuCommand("Literotica Downloader Options", () => {
  138. $( "#literoticaDownloaderOption" ).dialog( "open" );
  139. });
  140. function initCheckBox(element, index, array) {
  141. if (isGM) {
  142. GMgetValue(element, options[element]).then((myData) => {
  143. options[element] = myData;
  144. $("#"+element)[0].checked= options[element];
  145. });
  146. }else {
  147. options[element] = GMgetValue(element, options[element]);
  148. $("#"+element)[0].checked= options[element];
  149. }
  150.  
  151. return true;
  152. }
  153. // Init of all options
  154. Object.getOwnPropertyNames(options).every(initCheckBox)
  155.  
  156. // Function used to return content as a file for the user.
  157. function saveTextAsFile(textToWrite, fileNameToSaveAs) {
  158. var textFileAsBlob = new Blob([textToWrite], {
  159. type: 'text/javascript'
  160. });
  161. var downloadLink = document.createElement('a');
  162. downloadLink.download = fileNameToSaveAs;
  163. downloadLink.innerHTML = 'Download File';
  164. // Firefox requires the link to be added to the DOM
  165. // before it can be clicked.
  166. downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
  167. //downloadLink.onclick = destroyClickedElement;
  168. downloadLink.style.display = 'none';
  169. document.body.appendChild(downloadLink);
  170. downloadLink.click();
  171. }
  172. // Function parsing all pages to get the storie based
  173. function getContentOfStoie(baseURL) {
  174. console.log("Fetching " + baseURL);
  175. var remote;
  176. $.ajax({
  177. url: baseURL,
  178. type: 'GET',
  179. async: false,
  180. crossDomain: true,
  181. success: function(data) {
  182. if ($(data).find('a.l_bJ.l_bL').size() > 0) {
  183. console.log($(data).find('a.l_bJ.l_bL').size());
  184. remote = $(data).find('.panel.article.aa_eQ .aa_ht').html() + getContentOfStoie($(data).find('a.l_bJ.l_bL')[0].href);
  185. } else {
  186. remote = $(data).find('.panel.article.aa_eQ .aa_ht').html();
  187. }
  188. }
  189. });
  190. return remote;
  191. }
  192.  
  193. function getABookForSerieDiv(myDiv) {
  194. var title = $.trim(myDiv.text().split(':')[0]);
  195. // Get the X Part Series
  196. var descriptionSeries = $.trim(myDiv.text().split(':')[1]);
  197. var author = $('.contactheader').text();
  198. alert("Starting building file for " + title + " of " + author + ".\nPlease wait...");
  199. var book = '<html>\n<head>\n<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">\n';
  200. book += '<title>' + title + '</title>';
  201. book += '<meta content="' + author + '" name="author">';
  202. if(options.isNightMode){
  203. book += '</head>\n<body ' + bodyStyleNight + ' >';
  204. }else {
  205. book += '</head>\n<body ' + bodyStyle + ' >';
  206. }
  207.  
  208. function addChapter(element, index, array) {
  209. if ($(this).find('a').size() > 0) {
  210. var chapeterTitle = $($(this).find('td a')[0]).text();
  211. var description = $($(this).find('td')[1]).text();
  212. book += '<h1 class=\'chapter\'' + chapterStyle + '>' + chapeterTitle + '</h1>';
  213. book += '<h2 class=\'chapter\'' + descriptionStyle + '>' + description + '</h2>';
  214. var link = $($(this).find('a')[0]);
  215. book += getContentOfStoie(link.attr('href'));
  216. }
  217. }
  218. myDiv.nextUntil('.ser-ttl,.root-story').each(addChapter);
  219. saveTextAsFile(book, buildFilename(title, author, descriptionSeries, ));
  220. }
  221.  
  222. function getABookForStoryDiv(myDiv) {
  223. var title = $.trim($($(myDiv)).text().split('(')[0]);
  224. var note = $.trim($($(myDiv)).text().split('(')[1]).replace(")", "");
  225. if ($(myDiv).find('a').size() >= 0) {
  226. var chapterTitle = $($(myDiv.parent()).find('td a')[0]).text();
  227. var description = $($(myDiv.parent()).find('td')[1]).text();
  228. var category = $($(myDiv.parent()).find('td')[2]).text();
  229. }
  230. var author = $('.contactheader').text();
  231. var book = '<html>\n<head>\n<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">\n';
  232. console.log('title' + title);
  233. book += '<title>' + title + '</title>';
  234. book += '<meta content="' + author + '" name="author">';
  235. book += '<meta content="' + author + '" name="author">';
  236. if(options.isNightMode){
  237. book += '</head>\n<body ' + bodyStyleNight + ' >';
  238. }else {
  239. book += '</head>\n<body ' + bodyStyle + ' >';
  240. }
  241. book += '<h1 class=\'chapter\'' + chapterStyle + '>' + chapterTitle + '</h1>';
  242. book += '<h2 class=\'chapter\'' + descriptionStyle + '>' + description + '</h2>';
  243. var link = $($(myDiv).find('a')[0]);
  244. book += getContentOfStoie(link.attr('href'));
  245.  
  246. saveTextAsFile(book, buildFilename(title, author, description, note, category));
  247. }
  248. $('.ser-ttl td:nth-child(1)').prepend('<span ' + iconDonwload + '></span>');
  249. $('.ser-ttl td:nth-child(1) span').click(function() {
  250. getABookForSerieDiv($(this).parent().parent());
  251. });
  252. var idIcon = Math.floor(Math.random() * 100);
  253. $('.root-story td:nth-child(1), .sl td:nth-child(1)').prepend('<span ' + iconDonwload + ' id="' + idIcon + '"></span>');
  254. $('.root-story td:nth-child(1), .sl td:nth-child(1) span').click(function() {
  255. getABookForStoryDiv($(this).parent());
  256. });
  257. });