// ==UserScript==
// @name Pixverse Full Bypass & Credit Restorer (Fixed Image Upload)
// @version 4.1
// @description NSFW bypass, watermark-free video download, and credit restore with fixed image upload
// @author GPT
// @license MIT
// @match https://app.pixverse.ai/*
// @run-at document-start
// @namespace https://gf.zukizuki.org/users/1458338
// ==/UserScript==
(function () {
'use strict';
let savedImagePath = null;
// Función mejorada para capturar la ruta de la imagen (tomada del Script 1)
function captureImagePath(body) {
try {
const data = body ? JSON.parse(body) : {};
if (data?.images?.[0]?.path) {
savedImagePath = data.images[0].path;
console.log('[Upload] Captured batch upload path:', savedImagePath);
} else if (data?.path) {
savedImagePath = data.path;
console.log('[Upload] Captured single upload path:', savedImagePath);
}
} catch (e) {
console.error('[Upload] Error parsing upload body:', e);
}
}
// Modify /user/credits to restore credits
function tryModifyCredits(data) {
if (data?.Resp?.credits !== undefined) {
console.log('[Bypass] Restoring credits to 100');
data.Resp.credits = 100;
}
return data;
}
// Función mejorada para modificar respuestas de subida (combinación de ambos scripts)
function modifyUploadResponse(data, url) {
if (url.includes('/media/batch_upload_media') && savedImagePath) {
if ([400, 403, 401].includes(data?.ErrCode)) {
console.log('[Bypass] Patching batch_upload_media response with path:', savedImagePath);
const name = savedImagePath.split('/').pop();
return {
ErrCode: 0,
ErrMsg: "success",
Resp: {
result: [{
id: Date.now(),
name: name,
path: savedImagePath,
url: `https://media.pixverse.ai/${savedImagePath}`,
category: 0,
size: 0,
err_msg: ""
}]
}
};
}
}
if (url.includes('/media/upload') && savedImagePath) {
if ([400040, 403, 401].includes(data?.ErrCode)) {
console.log('[Bypass] Patching single upload response with path:', savedImagePath);
return {
ErrCode: 0,
ErrMsg: "success",
Resp: {
path: savedImagePath,
url: `https://media.pixverse.ai/${savedImagePath}`
}
};
}
}
return data;
}
// Modify NSFW/Video list responses
function modifyVideoList(data) {
if (data?.Resp?.data) {
data.Resp.data = data.Resp.data.map(item => {
return {
...item,
video_status: item.video_status === 7 ? 1 : item.video_status,
first_frame: item.extended === 1
? item.customer_paths?.customer_video_last_frame_url
: item.customer_paths?.customer_img_url,
url: 'https://media.pixverse.ai/' + item.video_path
};
});
}
return data;
}
// Función principal para modificar respuestas
function modifyResponse(url, data) {
if (url.includes('/user/credits')) {
return tryModifyCredits(data);
}
if (url.includes('/video/list/personal')) {
return modifyVideoList(data);
}
return modifyUploadResponse(data, url);
}
// Intercept XMLHttpRequest (mejorado con la lógica del Script 1)
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this._url = url;
return originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
const xhr = this;
// Capturar la ruta de la imagen al enviar (como en el Script 1)
if (xhr._url?.includes('/media/batch_upload_media') || xhr._url?.includes('/media/upload')) {
captureImagePath(body);
}
xhr.addEventListener('load', function () {
try {
const data = JSON.parse(xhr.responseText);
const patched = modifyResponse(xhr._url, data);
const text = JSON.stringify(patched);
Object.defineProperty(xhr, 'responseText', { get: () => text });
Object.defineProperty(xhr, 'response', { get: () => patched });
} catch (e) {
console.error('[XHR] Error processing response:', e);
}
});
return originalSend.apply(this, arguments);
};
// Intercept fetch (mejorado con manejo de errores)
const originalFetch = window.fetch;
window.fetch = async function (...args) {
const url = typeof args[0] === 'string' ? args[0] : args[0].url;
const res = await originalFetch(...args);
// Capturar la ruta de la imagen en solicitudes de subida
if (url?.includes('/media/') && args[1]?.body) {
try {
const bodyText = await args[1].body.getReader().read().then(r => new TextDecoder().decode(r.value));
captureImagePath(bodyText);
} catch (e) {
console.error('[Fetch] Error parsing upload body:', e);
}
}
if (!res.clone || !res.headers.get("Content-Type")?.includes("application/json")) {
return res;
}
try {
const clone = res.clone();
const data = await clone.json();
const patched = modifyResponse(url, data);
return new Response(JSON.stringify(patched), {
status: res.status,
statusText: res.statusText,
headers: res.headers
});
} catch (e) {
console.error('[Fetch] Error processing response:', e);
return res;
}
};
// Watermark-free button replacer (usando MutationObserver como en el Script 2 original)
function setupWatermarkButton() {
const observer = new MutationObserver(() => {
const watermark = Array.from(document.querySelectorAll('div'))
.find(el => el.textContent.trim() === 'Watermark-free');
if (watermark) {
const btn = document.createElement('button');
btn.textContent = 'Watermark-free';
btn.style.cssText = getComputedStyle(watermark).cssText;
btn.onclick = () => {
const video = document.querySelector(".component-video > video");
if (video?.src) {
const a = document.createElement('a');
a.href = video.src;
a.download = video.src.split('/').pop();
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
console.log('[Watermark-free] Download triggered.');
} else {
alert('No video found.');
}
};
watermark.parentNode.replaceChild(btn, watermark);
observer.disconnect();
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
document.addEventListener('DOMContentLoaded', setupWatermarkButton);
})();