Pixverse Full Bypass & Credit Restorer (Fixed Image Upload)

NSFW bypass, watermark-free video download, and credit restore with fixed image upload

// ==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);
})();