Xexle - Link Extractor

Extract normal and premium links with Fetch y XHR.

Terá de instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Terá de instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Terá de instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Terá de instalar uma extensão como Tampermonkey para instalar este script.

Terá de instalar uma extensão de gestão de scripts de utilizador para instalar este script.

(Já tenho um gestor de scripts de utilizador, deixe-me instalá-lo!)

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão como Stylus para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

Terá de instalar uma extensão de gestão de estilos de utilizador para instalar este estilo.

(Já tenho um gestor de estilos de utilizador, deixe-me instalá-lo!)

// ==UserScript==
// @name         Xexle - Link Extractor
// @namespace    http://tampermonkey.net/
// @version      1.0
// @license MIT
// @description  Extract normal and premium links with Fetch y XHR.
// @author       xness
// @match        *://*.xexle.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=xexle.com
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    const videoDataMap = new Map();
    const win = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;

    // fetch interceptor
    const originalFetch = win.fetch;
    win.fetch = async (...args) => {
        const response = await originalFetch(...args);
        if (args[0] && args[0].includes('/api/')) {
            const clone = response.clone();
            clone.json().then(data => processJson(data)).catch(()=>{});
        }
        return response;
    };

    // xhr interceptor if has clasic XMLHttpRequest
    const originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function() {
        this.addEventListener('load', function() {
            if (this.responseURL.includes('/api/')) {
                try {
                    const data = JSON.parse(this.responseText);
                    processJson(data);
                } catch (e) {}
            }
        });
        return originalOpen.apply(this, arguments);
    };

    // process data
    function processJson(json) {
        if (json && json.data && json.data.list) {
            json.data.list.forEach(item => {
                const link = item.filePath || item.downloadPath;
                if (link) {
                    videoDataMap.set(item.id.toString(), link);
                }
            });
        }
    }

    // add button for each video
    function injectUI() {
        // find video containers
        const entries = document.querySelectorAll('a.xexleWatchAFullpage');

        entries.forEach(entry => {
            const id = entry.getAttribute('data-id');
            if (id && videoDataMap.has(id)) {
                if (entry.querySelector('.force-unlock-btn')) return;

                const realUrl = videoDataMap.get(id);
                const btn = document.createElement('div');
                btn.className = 'force-unlock-btn';
                btn.innerHTML = '▶ FULL VIDEO';
                btn.style.cssText = `
                    position: absolute;
                    top: 5px;
                    right: 5px;
                    background: #2ecc71;
                    color: white;
                    padding: 6px 10px;
                    border-radius: 4px;
                    font-weight: bold;
                    font-size: 11px;
                    z-index: 9999;
                    cursor: pointer;
                    box-shadow: 0px 2px 5px rgba(0,0,0,0.5);
                `;

                btn.onclick = (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    window.open(realUrl, '_blank');
                };

                entry.style.position = 'relative';
                entry.appendChild(btn);

                // Opcional: Quitar el blur de la imagen
                const img = entry.querySelector('.list_blur');
                if (img) {
                    img.classList.remove('list_blur');
                    img.style.filter = 'none';
                }
            }
        });
    }

    // executes each 1.5s
    setInterval(injectUI, 1500);

})();