tsadult post saver
// ==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 = `\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 = `\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 = ``;
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);
})();