try to take over the world!
// ==UserScript==
// @name tensor.art profile download
// @namespace http://tampermonkey.net/
// @version 2026-6-29-2
// @description try to take over the world!
// @author You
// @match https://tensor.art/u/*
// @match https://tensorhub.art/en-US/u/*
// @match https://tensorhub.art/u/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=tensor.art
// @grant GM_download
// @grant GM.download
// @grant GM_xmlhttpRequest
// @grant GM.xmlHttpRequest
// @connect *
// @connect api.tensor.art
// @connect api.tensorhub.art
// @connect tensor.art
// @connect tensorhub.art
// ==/UserScript==
//https://api.tensor.art/community-web/v1/post/list
/* eslint-disable */
// eslint-disable-line
// eslint-disable-next-line(function() {
'use strict';
const token = getCookie('ta_token_prod');
let cursor;
const originalUrl = window.location.href;
const currentURL = originalUrl.split('/');
const uIndex = currentURL.indexOf('u');
const userid = currentURL[uIndex + 1];
function doc_keyUp(e) {
cursor = 0;
console.log(e.keyCode);
switch(e.keyCode)
{
case 61:
case 186:
case 187:
console.log('building nonliked HTML index');
buildImageHtml(false, true);
break;
case 220: //\
console.log('starting');
dlimages(false);
break;
case 221: //]
console.log('starting');
dlimages(true);
break;
case 219: // [
console.log('building HTML indexes');
buildImageHtml(false, false);
break;
default:
break;
}
}
document.addEventListener('keyup', doc_keyUp, false);
function getCookie(name) {
const nameEQ = name + "=";
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(nameEQ) === 0) {
return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
}
return null;
}
async function fetchTensorPosts(maxRetries = 5) {
let result;
const data = "{\"cursor\":\""+cursor+"\",\"filter\":{},\"size\":\"40\",\"userId\":\""+ userid +"\",\"sort\":\"NEWEST\",\"visibility\":\"PRIVATE\"}";
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const r = await GM.xmlHttpRequest({
method: "POST",
url: "https://api." + currentURL[2] + "/community-web/v1/post/list",
headers: {
"User-Agent": "yo-momma",
"Accept": "*/*",
"Accept-Language": "en-US,en;q=0.5",
"Content-Type": "application/json",
"Content-Length": data.length,
"Referer": originalUrl,
"X-Request-Sign": "ZjIyN2UxOThkYjMwYzA3MzI5YjI3OTRjYTIxZGMyZDhjMGE5ODU3ZjZlNmE2ZDFmNGU1MzY3OTkyYmQ0OTk2Mg==",
"X-Request-Timestamp": "1764723083451",
"X-Request-Package-Sign-Version": "0.0.1",
"X-Request-Sign-Version": "v1",
"X-Request-Sign-Type": "HMAC_SHA256",
"X-Request-Package-Id": "3000",
"Origin": originalUrl,
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-site",
"x-echoing-env": "",
"Authorization": "Bearer " + token,
"Connection": "keep-alive",
"DNT": 1,
"Priority": "u=4",
"TE": "trailers"
},
data: data
});
if (!r || r.status !== 200 || !r.responseText) {
console.warn(`Request failed attempt ${attempt}`, r);
if (attempt < maxRetries) {
await sleep(1000 * attempt);
continue;
}
return null;
}
result = JSON.parse(r.responseText);
console.log(result);
cursor = result.data.cursor;
return result.data.items;
} catch(e) {
console.error(`Request exception attempt ${attempt}`, e);
if (attempt < maxRetries) {
await sleep(1000 * attempt);
continue;
}
return null;
}
}
return null;
}
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function dlimages(onePage) {
let result;
let images = [];
let items;
do {
try{
items = await fetchTensorPosts();
} catch(e) {
console.error(e, e.stack);
}
if (!items) {
break;
}
items.forEach(post=>{
post.images.forEach(image=>{
images.push(image);
})
});
} while(items.length&&!onePage);
console.log(images);
dlimages(images);
async function dlimages(images) {
let currentDownloads = 0;
while (images.length > 0) {
if (currentDownloads > 5) {
await sleep(200);
continue;
}
var item = images.shift();
(function(_item) {
const split = _item.url.split('.');
const dl = {
url: _item.url,
name: userid+'/'+_item.id+'.'+split[split.length-1],
saveAs: false,
conflictAction: 'overwrite',
onerror: function(error) {
queue.unshift(_item);
currentDownloads--;
},
onload: function() {
const blob = new Blob([JSON.stringify(_item)], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
GM_download({
url: url,
name: userid+'/'+_item.id+'.json',
saveAs: false, // Prompts the user to choose a save location
conflictAction: 'overwrite',
});
currentDownloads--;
}
};
console.log(dl);
GM_download(dl);
currentDownloads++;
})(item);
}
};
}
async function buildImageHtml(onePage, onlyLiked) {
let posts = [];
let unlistedPosts = [];
let items;
function buildRows(postArray) {
return postArray.map(post => {
let thumb = post.url.endsWith(".mp4")
? `https://image.tensorartassets.com/cdn-cgi/image/anim=false,plain=false,f=avif,q=85/cdn-cgi/media/mode=frame,time=1s,width=200/posts/images/${userid}/${post.url}`
: `https://image.tensorartassets.com/cdn-cgi/image/anim=false,plain=false,w=200,f=avif,q=85/posts/images/${userid}/${post.url}`;
thumb = thumb.replace('_compressed', '');
return `
<div class="card">
<a href="https://tensorhub.art/images/${post.imageId}?post_id=${post.postId}" target="_blank">
<img src="${thumb}" loading="lazy">
</a>
<div class="meta">
<div><strong>ID:</strong> ${post.postId}<br>${post.postSize} images</div>
</div>
</div>`;
}).join('\n');
}
do {
try {
items = await fetchTensorPosts();
} catch(e) {
console.error(e, e.stack);
break;
}
if (!items) {
break;
}
items.forEach(post => {
if (!post.images.length) {
return;
}
const image = post.images[0];
const censored = post.images.map(image=>image.contentRating).includes('MATURE');
const newPost = {
postId: post.id,
imageId: image.id,
url: image.url.split('/').pop(),
postSize: post.images.length,
status: post.status,
liked: post.statisticsInfo.myLike != null
};
if (!onlyLiked || !newPost.liked) {
posts.push(newPost);
}
if (!onlyLiked && censored) {
unlistedPosts.push(newPost);
}
});
await sleep(500);
} while (items.length && !onePage);
console.log('finished api calls');
const rows = buildRows(posts);
const unlistedRows = buildRows(unlistedPosts);
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tensor Image Links - ${userid}</title>
<style>
body {
background: #111;
color: #eee;
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
}
.card {
background: #222;
border-radius: 8px;
padding: 10px;
}
img {
width: 100%;
height: auto;
display: block;
}
.meta {
margin-top: 10px;
word-break: break-all;
}
a {
color: #66ccff;
}
</style>
</head>
<body>
<h1>Tensor Post Links</h1>
<p>Total posts: ${posts.length}</p>
${unlistedPosts.length > 0
? '<p><a href="unlisted.html">Unlisted Posts</a></p>'
: ''
}
<div class="grid">
${rows}
</div>
</body>
</html>`;
const blob = new Blob([html], { type: 'text/html' });
console.log(blob);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'index.html';
link.dispatchEvent(new MouseEvent('click'));
window.setTimeout(() => window.URL.revokeObjectURL(url), 1000);
if (unlistedPosts.length > 0) {
const unlistedHtml = html.replace(
'<h1>Tensor Post Links</h1>',
'<h1>Unlisted Tensor Post Links</h1>'
).replace(rows, unlistedRows).replace('<p><a href="unlisted.html">Unlisted Posts</a></p>','<p><a href="index.html">All Posts</a></p>');
const unlistedBlob = new Blob([unlistedHtml], { type: 'text/html' });
const unlistedUrl = URL.createObjectURL(unlistedBlob);
const unlistedLink = document.createElement('a');
unlistedLink.href = unlistedUrl;
unlistedLink.download = 'unlisted.html';
unlistedLink.dispatchEvent(new MouseEvent('click'));
window.setTimeout(() => window.URL.revokeObjectURL(unlistedUrl), 1000);
}
}