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.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
    }

})();