tsadult-dl

tsadult post saver

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         tsadult-dl
// @namespace    http://tampermonkey.net/
// @version      0.3.2
// @description  tsadult post saver
// @author       You
// @match        https://*.tsadult.net/*/res/*.html
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tsadult.net
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.8.0/jszip.min.js
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==


const CSS = `
  #dl-btn {
    position: fixed;
    bottom: 64px;
    right: 32px;
    font-size: 16pt;
    text-align: right;
  }
  #dl-msg {
   font-size: 12pt;
   color: black;
  }
`

function addDownloadButton() {
  var count = 0;
  const post = getPost();
  const imageTask = post.filter(it => it.image != null);

  const container = document.createElement('div');
  container.id = 'dl-btn';

  const message = document.createElement('div');
  message.id = 'dl-msg';
  const printMessage = (msg) => { message.innerText = msg };
  printMessage(`image: (0/${imageTask.length})`);

  const button = document.createElement('button');
  button.innerText = '💾';
  button.onclick = () => {
    if (downloading) {
      return;
    }
    var downloading = true;
    console.log('get post', post);

    var zip = new JSZip();
    var tasks = imageTask.map(it => {
        const name = it.image.name;
        const url = it.image.url;
        console.log('download image', it);
        return fetch(url)
          .then(resp => resp.blob())
          .then(blob => {
            zip.file(name, blob);
            count++;
            printMessage(`image: (${count}/${imageTask.length})`);
            return Promise.resolve();
          })
      });

    const article = post.map(it => it.content)
      .filter(it => it!=null && it.length > 0)
      .join('\n\r---\n\r');
    Promise.all(tasks)
      .then(() => zip.file('article.md', article))
      .then(() => zip.generateAsync({ type: 'blob' }))
      .then(blob => {
        const download = document.createElement('a');
        document.body.appendChild(download);
        download.style.display = 'none';

        const postId = location.pathname.split('/').slice(-1)[0].replace('.html','') ?? '';
        const fileUrl = window.URL.createObjectURL(blob);
        download.href = fileUrl;
        download.download = 'tsadult_' + postId + '_' + new Date().getTime();
        download.click();
        window.URL.revokeObjectURL(fileUrl);
        download.remove();
        downloading = false;
      })
  }
  container.appendChild(message);
  container.appendChild(button);
  return container;
}

const parser = {
  old: function () {
    const convertTo = function (fragment) {
      const message = fragment.querySelector('.message');
      var content = message?.innerText?.replace(/^#/gm, '> ');

      var image = null;
      const img = fragment.querySelector('img');
      if (img) {
        const url = new URL(img.parentElement.href, location.href).href.toString();
        const suffixIndex = url.lastIndexOf('/');
        image = { url, name: url.slice(suffixIndex + 1) }
        content = `![](${image.name})\n\n` + content;
      }

      return { image, content }
    };
    const post = document.querySelector('.op');
    const tables = document.querySelectorAll('#posts > table');
    const contents = Array.from(tables).map(it => convertTo(it));
    return [convertTo(post), ...contents]
  },
  v2024: function () {
    const convertTo = function (fragment) {
      const imagesEle = fragment.querySelectorAll('.files .fileinfo > a');

      const images = Array.from(imagesEle)
        .map(e => {
          const url = new URL(e.href, location.href).href.toString();
          const name = e.innerText;

          const image = { url, name };
          const content = `![](${name})\n\n`;
          return { image, content }
        });

      const text = fragment.querySelector('.body')?.innerText?.replace(/^#/gm, '> ') ?? '';
      return [...images, { image: null, content: text }]
    };

    const imagesEle = document.querySelectorAll('.thread > .files .fileinfo > a');
    const images = Array.from(imagesEle)
      .map(e => {
        const url = new URL(e.href, location.href).href.toString();
        const name = e.innerText;

        const image = { url, name };
        const content = `![](${name})`;
        return { image, content }
      });
    const posts = Array.from(document.querySelectorAll('.thread > .post'))
      .map(e => convertTo(e))
      .flat();

    return [...images, ...posts]
  }
};


function getPost() {
  const url = location.host;
  if (url.includes('2021')) {
    return parser.old()
  } else {
    return parser.v2024()
  }
}

(function () {
  'use strict';
  GM_addStyle(CSS);
  const button = addDownloadButton();
  document.body.appendChild(button);
})();