tsadult-dl

tsadult post saver

  1. // ==UserScript==
  2. // @name tsadult-dl
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.3.1
  5. // @description tsadult post saver
  6. // @author You
  7. // @match https://*.tsadult.net/*/res/*.html
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=tsadult.net
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.8.0/jszip.min.js
  10. // @grant GM_addStyle
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14.  
  15. const CSS = `
  16. #dl-btn {
  17. position: fixed;
  18. bottom: 64px;
  19. right: 32px;
  20. font-size: 16pt;
  21. text-align: right;
  22. }
  23. #dl-msg {
  24. font-size: 12pt;
  25. color: black;
  26. }
  27. `
  28.  
  29. function addDownloadButton() {
  30. var count = 0;
  31. const post = getPost();
  32. const imageTask = post.filter(it => it.image != null);
  33.  
  34. const container = document.createElement('div');
  35. container.id = 'dl-btn';
  36.  
  37. const message = document.createElement('div');
  38. message.id = 'dl-msg';
  39. const printMessage = (msg) => { message.innerText = msg };
  40. printMessage(`image: (0/${imageTask.length})`);
  41.  
  42. const button = document.createElement('button');
  43. button.innerText = '💾';
  44. button.onclick = () => {
  45. if (downloading) {
  46. return;
  47. }
  48. var downloading = true;
  49. console.log('get post', post);
  50.  
  51. var zip = new JSZip();
  52. var tasks = imageTask.map(it => {
  53. const name = it.image.name;
  54. const url = it.image.url;
  55. console.log('download image', it);
  56. return fetch(url)
  57. .then(resp => resp.blob())
  58. .then(blob => {
  59. zip.file(name, blob);
  60. count++;
  61. printMessage(`image: (${count}/${imageTask.length})`);
  62. return Promise.resolve();
  63. })
  64. });
  65.  
  66. const article = post.map(it => it.content)
  67. .filter(it => it!=null && it.length > 0)
  68. .join('\n\r---\n\r');
  69. Promise.all(tasks)
  70. .then(() => zip.file('article.md', article))
  71. .then(() => zip.generateAsync({ type: 'blob' }))
  72. .then(blob => {
  73. const download = document.createElement('a');
  74. document.body.appendChild(download);
  75. download.style.display = 'none';
  76.  
  77. const fileUrl = window.URL.createObjectURL(blob);
  78. download.href = fileUrl;
  79. download.download = 'tsadult_' + new Date().getTime();
  80. download.click();
  81. window.URL.revokeObjectURL(fileUrl);
  82. download.remove();
  83. downloading = false;
  84. })
  85. }
  86. container.appendChild(message);
  87. container.appendChild(button);
  88. return container;
  89. }
  90.  
  91. const parser = {
  92. old: function () {
  93. const convertTo = function (fragment) {
  94. const message = fragment.querySelector('.message');
  95. var content = message?.innerText?.replace(/^#/gm, '> ');
  96.  
  97. var image = null;
  98. const img = fragment.querySelector('img');
  99. if (img) {
  100. const url = new URL(img.parentElement.href, location.href).href.toString();
  101. const suffixIndex = url.lastIndexOf('/');
  102. image = { url, name: url.slice(suffixIndex + 1) }
  103. content = `![](${image.name})\n\n` + content;
  104. }
  105.  
  106. return { image, content }
  107. };
  108. const post = document.querySelector('.op');
  109. const tables = document.querySelectorAll('#posts > table');
  110. const contents = Array.from(tables).map(it => convertTo(it));
  111. return [convertTo(post), ...contents]
  112. },
  113. v2024: function () {
  114. const convertTo = function (fragment) {
  115. const imagesEle = fragment.querySelectorAll('.files .fileinfo > a');
  116.  
  117. const images = Array.from(imagesEle)
  118. .map(e => {
  119. const url = new URL(e.href, location.href).href.toString();
  120. const name = e.innerText;
  121.  
  122. const image = { url, name };
  123. const content = `![](${name})\n\n`;
  124. return { image, content }
  125. });
  126.  
  127. const text = fragment.querySelector('.body')?.innerText?.replace(/^#/gm, '> ') ?? '';
  128. return [...images, { image: null, content: text }]
  129. };
  130.  
  131. const imagesEle = document.querySelectorAll('.thread > .files .fileinfo > a');
  132. const images = Array.from(imagesEle)
  133. .map(e => {
  134. const url = new URL(e.href, location.href).href.toString();
  135. const name = e.innerText;
  136.  
  137. const image = { url, name };
  138. const content = `![](${name})`;
  139. return { image, content }
  140. });
  141. const posts = Array.from(document.querySelectorAll('.thread > .post'))
  142. .map(e => convertTo(e))
  143. .flat();
  144.  
  145. return [...images, ...posts]
  146. }
  147. };
  148.  
  149.  
  150. function getPost() {
  151. const url = location.host;
  152. if (url.includes('2021')) {
  153. return parser.old()
  154. } else {
  155. return parser.v2024()
  156. }
  157. }
  158.  
  159. (function () {
  160. 'use strict';
  161. GM_addStyle(CSS);
  162. const button = addDownloadButton();
  163. document.body.appendChild(button);
  164. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址