Quebra-Paywall BR (12ft.io & Archive.is)

Adiciona botões flutuantes para acessar rapidamente artigos bloqueados por paywall em dezenas de sites de notícias e mídia brasileiros e alguns internacionais, utilizando 12ft.io e Archive.is. Desenvolvido para funcionar de forma robusta em Single Page Applications (SPAs) e com carregamento dinâmico de conteúdo.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name          Quebra-Paywall BR (12ft.io & Archive.is)
// @namespace     http://tampermonkey.net/
// @version       1.2
// @description   Adiciona botões flutuantes para acessar rapidamente artigos bloqueados por paywall em dezenas de sites de notícias e mídia brasileiros e alguns internacionais, utilizando 12ft.io e Archive.is. Desenvolvido para funcionar de forma robusta em Single Page Applications (SPAs) e com carregamento dinâmico de conteúdo.
// @author        Bruno Fortunato & Colaboradores (Comunidade Gemini/IA)
// @homepage      https://github.com/BrunoFortunato/Quebra-Paywall-BR
// @license       MIT
// @match         *://*.folha.uol.com.br/*
// @match         *://*.estadao.com.br/*
// @match         *://*.oglobo.globo.com/*
// @match         *://*.valor.globo.com.br/*
// @match         *://*.gazetadopovo.com.br/*
// @match         *://*.correiobraziliense.com.br/*
// @match         *://*.uol.com.br/*
// @match         *://*.gauchazh.clicrbs.com.br/*
// @match         *://*.nsctotal.com.br/*
// @match         *://*.diariocatarinense.clicrbs.com.br/*
// @match         *://*.jornaldocomercio.com/*
// @match         *://*.jc.ne10.uol.com.br/*
// @match         *://*.em.com.br/*
// @match         *://*.otempo.com.br/*
// @match         *://*.jota.info/*
// @match         *://*.poder360.com.br/*
// @match         *://*.diariodonordeste.verdesmares.com.br/*
// @match         *://*.opovo.com.br/*
// @match         *://*.correio24horas.com.br/*
// @match         *://*.atarde.uol.com.br/*
// @match         *://*.gazetaonline.com.br/*
// @match         *://*.abril.com.br/*
// @match         *://*.veja.abril.com.br/*
// @match         *://*.exame.com/*
// @match         *://*.istoedinheiro.com.br/*
// @match         *://*.cartacapital.com.br/*
// @match         *://*.diariosp.com.br/*
// @match         *://*.jornaldebrasilia.com.br/*
// @match         *://*.folhadelondrina.com.br/*
// @match         *://*.odiario.com/*
// @match         *://*.nexojornal.com.br/*
// @match         *://*.jornalggn.com.br/*
// @match         *://*.observatoriodaimprensa.com.br/*
// @match         *://*.terra.com.br/*
// @match         *://*.infomoney.com.br/*
// @match         *://*.sunoresearch.com.br/*
// @match         *://*.moneytimes.com.br/*
// @match         *://*.seudinheiro.com/*
// @match         *://*.cnnbrasil.com.br/*
// @match         *://*.band.uol.com.br/*
// @match         *://*.r7.com/*
// @match         *://*.metropoles.com/*
// @match         *://*.gazetabrasil.com.br/*
// @match         *://*.brasil247.com/*
// @match         *://*.theintercept.com.br/*
// @match         *://*.esmaelmorais.com.br/*
// @match         *://*.blogdopim.com.br/*
// @match         *://*.blogdosakamoto.blogosfera.uol.com.br/*
// @match         *://*.revistaforum.com.br/*
// @match         *://*.redebrasilatual.com.br/*
// @match         *://*.conversaafiada.com.br/*
// @match         *://*.operamundi.uol.com.br/*
// @match         *://*.brasildefato.com.br/*
// @match         *://*.ihu.unisinos.br/*
// @match         *://*.catracalivre.com.br/*
// @match         *://*.nsja.com.br/*
// @match         *://*.parana-online.com.br/*
// @match         *://*.paranaportal.uol.com.br/*
// @match         *://*.tribunapr.com.br/*
// @match         *://*.correiodopovo.com.br/*
// @match         *://*.jornalnh.com.br/*
// @match         *://*.diariopopular.com.br/*
// @match         *://*.agora.uol.com.br/*
// @match         *://*.sbtnews.com.br/*
// @match         *://*.jovempan.com.br/*
// @match         *://*.bandnewsfm.com.br/*
// @match         *://*.cbn.globoradio.globo.com/*
// @match         *://*.brpolitico.com.br/*
// @match         *://*.correiopopular.com.br/*
// @match         *://*.crusoe.com.br/*
// @match         *://*.diariodaregiao.com.br/*
// @match         *://*.dgabc.com.br/* // Diário do Grande ABC
// @match         *://*.diarinho.com.br/*
// @match         *://*.diariodecanoas.com.br/*
// @match         *://*.epoca.globo.com/* // Revista Época
// @match         *://*.jornalpioneiro.com.br/*
// @match         *://*.jornalvs.com.br/*
// @match         *://*.revistagalileu.globo.com/* // Revista Galileu
// @match         *://*.epocanegocios.globo.com/* // Adicionado: Revista Época Negócios
// @match         *://*.marieclaire.globo.com/* // Adicionado: Revista Marie Claire
// @match         *://*.globorural.globo.com/* // Adicionado: Revista Globo Rural
// @match         *://*.revistapegn.globo.com/* // Adicionado: Pequenas Empresas Grandes Negócios
// @match         *://*.nytimes.com/* // Adicionado: New York Times (Internacional)
// @match         *://*.elpais.com/* // Adicionado: El País (Internacional)
// @match         *://*.economist.com/* // Adicionado: The Economist (Internacional)
// @match         *://*.opopular.com.br/* // Adicionado: O Popular
// @match         *://*.diariodesantamaria.com.br/* // Adicionado: Diário de Santa Maria
// @match         *://*.glamour.globo.com/* // Adicionado: Revista Glamour
// @match         *://*.atribuna.com.br/* // Adicionado: Jornal A Tribuna (Santos)
// @match         *://*.umdoisesportes.com.br/* // Adicionado: Um Dois Esportes
// @match         *://*.gaz.com.br/* // Adicionado: GAZ
// @match         *://*.semprefamilia.com.br/* // Adicionado: Sempre Família
// @match         *://*.jornaldacidadeonline.com.br/* // Sugestão adicional
// @match         *://*.revistacrescer.globo.com/* // Sugestão adicional
// @match         *://*.revistamonet.globo.com/* // Sugestão adicional
// @match         *://*.casavogue.globo.com/* // Sugestão adicional
// @match         *://*.gq.globo.com/* // Sugestão adicional
// @match         *://*.casaclaudia.abril.com.br/* // Sugestão adicional
// @match         *://*.claudia.abril.com.br/* // Sugestão adicional
// @match         *://*.mdemulher.abril.com.br/* // Sugestão adicional
// @match         *://*.viagemeturismo.abril.com.br/* // Sugestão adicional
// @match         *://*.exame.com/* // Para garantir abrangência de subdomínios Exame
// @grant         GM_addStyle
// @grant         window.location
// ==/UserScript==

(function() {
    'use strict';

    let lastUrl = window.location.href; // Variável para armazenar a última URL conhecida
    let checkInterval = null; // Para controlar o setInterval

    // Estilos comuns para os botões flutuantes
    GM_addStyle(`
        .open-paywall-button {
            position: fixed;
            bottom: 20px;
            color: white;
            padding: 10px 15px;
            border: none;
            border-radius: 5px;
            font-size: 14px;
            font-family: Arial, sans-serif;
            cursor: pointer;
            z-index: 2147483647; /* Tenta garantir que fique no topo */
            box-shadow: 0 4px 8px rgba(0,0,0,0.2);
            opacity: 0.85;
            transition: opacity 0.3s ease, background-color 0.3s ease;
        }
        .open-paywall-button:hover {
            opacity: 1;
        }

        #open-in-12ft-button {
            left: 20px; /* Movido para a esquerda */
            background-color: #007bff; /* Azul */
        }
        #open-in-12ft-button:hover {
            background-color: #0056b3; /* Azul mais escuro */
        }

        #open-in-archive-button {
            left: 140px; /* Posição à direita do 12ft.io */
            background-color: #6c757d; /* Cinza para Archive.is */
        }
        #open-in-archive-button:hover {
            background-color: #5a6268; /* Cinza mais escuro */
        }
    `);

    // Função para criar um elemento de botão
    function createButtonElement(id, text, urlPrefix, alertMessage) {
        const button = document.createElement('button');
        button.id = id;
        button.className = 'open-paywall-button';
        button.innerHTML = text;

        button.addEventListener('click', function(event) {
            event.stopPropagation();
            const currentUrl = window.location.href;

            if (!currentUrl) {
                alert('Não foi possível obter a URL atual.');
                return;
            }

            // Verifica se a URL já é do serviço alvo para evitar loops
            if (currentUrl.startsWith(urlPrefix)) {
                alert(alertMessage);
                return;
            }

            const newUrl = urlPrefix + currentUrl;
            console.log('Redirecionando para: ' + newUrl);
            window.location.href = newUrl;
        });
        return button;
    }

    // Função para remover os botões existentes
    function removeExistingButtons() {
        const button12ft = document.getElementById('open-in-12ft-button');
        const buttonArchive = document.getElementById('open-in-archive-button');
        if (button12ft) {
            button12ft.remove();
        }
        if (buttonArchive) {
            buttonArchive.remove();
        }
    }

    // Função principal para adicionar todos os botões
    function addAllButtons() {
        // Se os botões já estão presentes e visíveis, não faz nada
        if (document.getElementById('open-in-12ft-button') && document.getElementById('open-in-archive-button')) {
            // Verifica se eles estão no body (pode ser que o DOM tenha sido manipulado e eles fiquem "soltos")
            if (document.body.contains(document.getElementById('open-in-12ft-button')) &&
                document.body.contains(document.getElementById('open-in-archive-button'))) {
                return;
            }
        }

        // Garante que o body esteja pronto
        if (!document.body) {
            console.log('Document body not ready yet, deferring button addition.');
            return;
        }

        console.log('Attempting to add/re-add buttons...');
        removeExistingButtons(); // Remove quaisquer botões antigos para garantir um estado limpo

        // Adiciona o botão para 12ft.io
        const button12ft = createButtonElement(
            'open-in-12ft-button',
            '🔓 12ft.io',
            'https://12ft.io/',
            'Esta página já está aberta com o 12ft.io.'
        );
        document.body.appendChild(button12ft);

        // Adiciona o botão para Archive.is
        const buttonArchive = createButtonElement(
            'open-in-archive-button',
            '🏛️ Archive.is',
            'https://archive.is/',
            'Esta página já está aberta com o Archive.is.'
        );
        document.body.appendChild(buttonArchive);
        console.log('Buttons successfully processed.');
    }

    // --- Estratégia de Injeção e Monitoramento ---

    // Função de verificação periódica que tenta adicionar os botões
    function periodicCheckAndAdd() {
        const currentUrl = window.location.href;

        // Se a URL mudou, consideramos uma "nova" página e forçamos a recriação
        if (currentUrl !== lastUrl) {
            console.log('URL changed. Forcing re-addition of buttons.');
            lastUrl = currentUrl;
            addAllButtons();
        } else {
            // Se a URL não mudou, mas os botões não estão visíveis no DOM, tenta adicioná-los
            if (!document.getElementById('open-in-12ft-button') || !document.getElementById('open-in-archive-button')) {
                console.log('Buttons missing on same URL. Attempting to add.');
                addAllButtons();
            }
        }
    }

    // Iniciar a verificação periódica um pouco depois do carregamento
    // e limpá-la se o body não existir ou se a página for embora
    function initializePeriodicCheck() {
        if (checkInterval) {
            clearInterval(checkInterval); // Limpa qualquer intervalo anterior
        }
        checkInterval = setInterval(periodicCheckAndAdd, 500); // Tenta a cada 500ms
    }

    // 1. No carregamento inicial da página (DOMContentLoaded)
    // Isso é a primeira tentativa para garantir que os botões sejam adicionados rapidamente.
    document.addEventListener('DOMContentLoaded', function() {
        addAllButtons();
        initializePeriodicCheck(); // Inicia a checagem periódica após o DOM estar pronto
    });

    // 2. Para lidar com o botão de voltar/avançar do navegador (popstate)
    // Este evento pode indicar uma "nova" página no histórico, então tentamos adicionar.
    window.addEventListener('popstate', function() {
        console.log('Popstate event. Checking for buttons.');
        periodicCheckAndAdd(); // Usa a função de verificação para revalidar
    });

    // 3. Monitoramento de URL e reinício do setInterval se necessário
    // Esta é uma "rede de segurança" para garantir que o setInterval esteja sempre ativo
    // e que a lastUrl esteja correta em caso de navegações complexas.
    let initialUrl = window.location.href;
    new MutationObserver(function(mutations) {
        if (window.location.href !== initialUrl) {
            initialUrl = window.location.href;
            console.log('URL changed via MutationObserver, re-initializing periodic check.');
            initializePeriodicCheck(); // Reinicia o intervalo para garantir consistência
            addAllButtons(); // Força a adição imediata também
        }
    }).observe(document, { childList: true, subtree: true, attributes: true });


    // Caso o script seja executado antes do DOMContentLoaded (modo "document-start" do Tampermonkey)
    // E o body já esteja presente, tenta adicionar os botões imediatamente.
    if (document.body) {
        addAllButtons();
        initializePeriodicCheck();
    }

})();