Greasy Fork

Greasy Fork is available in English.

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.

// ==UserScript==
// @name           Quebra-Paywall BR (12ft.io & Archive.is)
// @namespace      http://tampermonkey.net/
// @version        1.0 // Versão atualizada para 1.0 com mais sites
// @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 {
            right: 20px;
            background-color: #007bff; /* Azul */
        }
        #open-in-12ft-button:hover {
            background-color: #0056b3; /* Azul mais escuro */
        }

        #open-in-archive-button {
            right: 140px; /* Posição à esquerda 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();
    }

})();