PopUpOFF

Remove popups, overlays e elementos fixos de sites. Baseado na extensão PopUpOFF de RomanistHere. Modos: Agressivo, Moderado, Delicado.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         PopUpOFF
// @name:en      PopUpOFF
// @name:pt-BR   PopUpOFF
// @namespace    https://greasyfork.org/en/users/1301195-luciano-inf
// @version      1.0.1
// @description  Removes popups, overlays, and fixed elements from websites. Based on PopUpOFF browser extension by RomanistHere. Modes: Aggressive, Moderate, Delicate.
// @description:pt-BR  Remove popups, overlays e elementos fixos de sites. Baseado na extensão PopUpOFF de RomanistHere. Modos: Agressivo, Moderado, Delicado.
// @author       Luciano.Oliveirals
// @license      Apache-2.0
// @run-at       document-end
// @grant        GM_setValue
// @grant        GM_getValue
// @match        *://*/*
// @icon         https://icons.iconarchive.com/icons/paomedia/small-n-flat/64/shield-warning-icon.png
// @supportURL   https://greasyfork.org/scripts/587461-popupoff
// @homepageURL  https://github.com/LucianoSkx/popupoff
// ==/UserScript==

;(function () {
    'use strict'

    if (window !== window.parent) return

    const defaultSettings = {
        mode: 'moderate',
        restoreCont: false,
        staticSubMode: 'relative',
    }

    const whitelist = [
        'a.goodtime.io', 'about.google', 'app.hubspot.com', 'calendar.google.com',
        'discord.com', 'docs.google.com', 'drive.google.com', 'mail.google.com',
        'music.youtube.com', 'open.spotify.com', 'trello.com', 'twitter.com',
        'vk.com', 'web.telegram.org', 'www.amazon.com', 'www.facebook.com',
        'www.google.com', 'www.instagram.com', 'www.linkedin.com', 'www.netflix.com',
        'www.reddit.com', 'www.spotify.com', 'www.youtube.com', 'zoom.us',
        'store.steampowered.com', 'www.twitch.tv', 'www.apple.com', 'www.microsoft.com',
    ]

    const forbWordsEasy = [
        'cookie', 'privacy', 'adblock', 'ad block', 'blocker', 'ever miss',
        't miss', 'our privacy', 'theguardian', 'bloqueador de anuncios',
        'to continue us', 'mited acces', 'lusive acces', 'left this mon',
        'be the fir', 'ble notif', 's the time', 'ur newslet', 'gister for fre',
        'nload free', 'nload your free', 'gn to youtube', 'ble deal',
        'started for fre', "it's free", 'free trial', 'tart fre',
        'advertisement', '//consent.', 'ign in to youtub',
    ]

    const forbWords = [...forbWordsEasy, 'policy', 'subscri', 'sale', 'updates',
        'member', 'value', 'advertis', 'install',
    ]

    const allowedWords = [
        'sign in', 'language', 'basket', 'delivery', 'price', 'google meet',
        'resume', 'apply', 'drive',
    ]

    const hiddenClasses = [
        '.hide', '.height_0', '.not_scroll', '.excerpt-cropped',
        '.paragraph--faded', '.paragraph--reduced', '.paragraph--dynamic',
        '.article-teaser-overflow', '.editor-description__wrapper--cropped',
    ]

    let domObserver = null
    let isCSSAppended = false
    let infiniteLoopPreventCounter = 0
    let myTimer = 0
    let wasNotStopped = true

    const getStyle = (elem, prop) => window.getComputedStyle(elem, null).getPropertyValue(prop)
    const setPropImp = (elem, prop, val) => elem.style.setProperty(prop, val, 'important')
    const getPureURL = () => location.hostname + (location.port ? ':' + location.port : '')

    const isDecentElem = e => !['SCRIPT', 'HEAD', 'BODY', 'HTML', 'STYLE'].includes(e.nodeName)

    const contentEasyCheck = el => forbWordsEasy.some(v => el.innerHTML.toLowerCase().includes(v))
    const contentCheck = el => forbWords.some(v => el.innerHTML.toLowerCase().includes(v))
    const contentUnlockCheck = el => !allowedWords.some(v => el.innerHTML.toLowerCase().includes(v))

    const videoCheck = element => {
        const nn = element.nodeName
        if (nn === 'APP-DRAWER') return false
        if (nn === 'VIDEO' || nn === 'IMG' || nn === 'FORM' || nn === 'BUTTON' || nn === 'INPUT' || nn === 'IFRAME') return false
        if (element.shadowRoot) return videoCheck(element.shadowRoot)
        if (element.contentDocument) return videoCheck(element.contentDocument)
        for (let i = 0; i < element.childNodes.length; i++) {
            if (element.childNodes[i].nodeType === 1 && !videoCheck(element.childNodes[i])) return false
        }
        return true
    }

    const positionCheck = (element, windowArea) => {
        if (element.offsetHeight === 0 || element.offsetWidth === 0) {
            return { shouldRemove: true, shouldMemo: !!contentEasyCheck(element) }
        }
        const layoutArea = element.offsetHeight * element.offsetWidth
        const screenValue = Math.round((layoutArea / windowArea) * 100) / 100
        const offsetBot = window.innerHeight - (element.offsetTop + element.offsetHeight)

        if (screenValue >= 0.98) {
            return { shouldRemove: contentEasyCheck(element) || videoCheck(element), shouldMemo: true }
        }
        if (element.offsetTop <= 100 && element.offsetHeight <= 250 && element.offsetWidth > 640) {
            if (element.id === 'onesignal-slidedown-container') return { shouldRemove: true, shouldMemo: true }
            return { shouldRemove: false, shouldMemo: true }
        }
        if (element.offsetLeft <= 0 && element.offsetWidth <= 360 && screenValue >= 0.1) {
            return { shouldRemove: false, shouldMemo: true }
        }
        if (screenValue < 0.98 && screenValue >= 0.1) {
            return { shouldRemove: contentEasyCheck(element), shouldMemo: true }
        }
        if (screenValue <= 0.02 && element.offsetTop > 100) {
            return { shouldRemove: false, shouldMemo: true }
        }
        if (offsetBot <= 212) {
            return { shouldRemove: contentEasyCheck(element), shouldMemo: true }
        }
        if (screenValue <= 0.1 && element.offsetTop > 100) {
            return { shouldRemove: false, shouldMemo: true }
        }
        return { shouldRemove: false, shouldMemo: false }
    }

    const removeOverflow = () => {
        const doc = document.documentElement
        const body = document.body
        const overDoc = getStyle(doc, 'overflow-y')
        const overBody = getStyle(body, 'overflow-y')
        const docPos = getStyle(doc, 'position')
        const bodyPos = getStyle(body, 'position')

        if (overDoc !== 'visible' && overDoc !== 'unset' && overDoc !== 'auto') setPropImp(doc, 'overflow-y', 'unset')
        if (overBody !== 'visible' && overBody !== 'unset' && overBody !== 'auto') setPropImp(body, 'overflow-y', 'unset')
        if (docPos === 'fixed' || docPos === 'absolute') { setPropImp(doc, 'min-height', '100vh'); setPropImp(doc, 'position', 'relative') }
        if (bodyPos === 'fixed' || bodyPos === 'absolute') { setPropImp(body, 'position', 'relative'); setPropImp(body, 'min-height', '100vh') }
    }

    const findHidden = () => {
        hiddenClasses.forEach(cls => {
            document.querySelectorAll(cls).forEach(el => {
                if (el.innerHTML.length > 5) {
                    el.classList.remove(...hiddenClasses.map(c => c.replace('.', '')))
                }
            })
        })
        try { document.querySelector('#sign-in-gate').remove() } catch (e) { }
    }

    const detectGrad = element => {
        const bg = getStyle(element, 'background-image')
        if (bg.includes('linear-gradient')) setPropImp(element, 'background-image', 'unset')

        const before = getComputedStyle(element, '::before').getPropertyValue('background-image')
        const after = getComputedStyle(element, '::after').getPropertyValue('background-image')

        if (before.includes('linear-gradient') || after.includes('linear-gradient')) {
            if (!isCSSAppended) {
                document.head.insertAdjacentHTML('beforeend',
                    '<style>.PopUpOFF-no_grad::after,.PopUpOFF-no_grad::before{background-image:unset!important}</style>')
                isCSSAppended = true
            }
            element.classList.add('PopUpOFF-no_grad')
        }
    }

    const additionalChecks = (element, checkElem) => {
        const f = getStyle(element, 'filter')
        const wf = getStyle(element, '-webkit-filter')
        if (f !== 'none' || wf !== 'none') { setPropImp(element, 'filter', 'none'); setPropImp(element, '-webkit-filter', 'none') }

        const settings = getSettings()
        if (settings.restoreCont) detectGrad(element)
        if (element.shadowRoot) checkElemWithSibl(element.shadowRoot, checkElem)
    }

    const checkToConvertToStatic = elem => {
        if (getStyle(elem, 'overflow') === 'hidden') {
            const { width, height, top, left } = elem.getBoundingClientRect()
            if (width > 0 && height > 0 && top === 0 && left === 0) {
                setPropImp(elem, 'position', 'static')
                elem.setAttribute('data-popupoff', 'st')
                return true
            }
        }
        return false
    }

    const checkElemWithSibl = (element, checkElem) => {
        if (element instanceof HTMLElement) {
            checkElem(element)
            if (wasNotStopped) { const e = element.querySelectorAll('*'); e.forEach(checkElem) }
        } else if (element instanceof ShadowRoot) { element.querySelectorAll('*').forEach(checkElem) }
    }

    const disconnectObserver = () => {
        if (domObserver) { domObserver.disconnect(); domObserver = null }
    }

    const removeDomWatcher = (body, action) => {
        disconnectObserver()
        if (wasNotStopped) { setTimeout(() => { action(body.getElementsByTagName('*')) }, 2000) }
        wasNotStopped = false
    }

    const prevLoop = () => {
        if (infiniteLoopPreventCounter > 1200) return true
        infiniteLoopPreventCounter++
        if (myTimer === 0) { myTimer = setTimeout(() => { infiniteLoopPreventCounter = 0; clearTimeout(myTimer); myTimer = 0 }, 1000) }
        return false
    }

    const watchMutations = (mutations, checkElem, body, action) => {
        for (let i = 0; i < mutations.length; i++) {
            if (prevLoop()) { removeDomWatcher(body, action); break }
            const m = mutations[i]
            if (m.attributeName === 'data-popupoff') continue
            if (['SCRIPT', 'HEAD', 'STYLE'].includes(m.target.nodeName)) continue

            checkElemWithSibl(m.target, checkElem)
            m.addedNodes.forEach(el => {
                if (!['#text', '#comment', 'SCRIPT', 'HEAD', 'STYLE'].includes(el.nodeName))
                    checkElemWithSibl(el, checkElem)
            })
            removeOverflow()
        }
    }

    const aggressiveMode = () => {
        const doc = document.documentElement
        const body = document.body
        const settings = getSettings()
        const checkElem = element => {
            if (!isDecentElem(element)) return
            const pos = getStyle(element, 'position')
            if (pos === 'fixed' || pos === 'sticky') {
                if (checkToConvertToStatic(element)) return
                if (element.getAttribute('data-popupoff') === 'notification') return
                if (getStyle(element, 'display') !== 'none') element.setAttribute('data-popupoff', 'bl')
                setPropImp(element, 'display', 'none')
            }
            additionalChecks(element, checkElem)
        }
        const action = elems => {
            removeOverflow()
            ;[...elems].forEach(checkElem)
            if (settings.restoreCont) findHidden()
            domObserver = new MutationObserver(mutations => watchMutations(mutations, checkElem, body, action))
            domObserver.observe(doc, { childList: true, subtree: true, attributes: true })
        }
        action(body.getElementsByTagName('*'))
    }

    const moderateMode = () => {
        const doc = document.documentElement
        const body = document.body
        const memoize = new WeakMap()
        const settings = getSettings()
        const wArea = parseFloat(window.innerHeight * window.innerWidth)

        const checkElem = element => {
            if (!isDecentElem(element)) return
            const pos = getStyle(element, 'position')
            if (pos === 'fixed' || pos === 'sticky') {
                if (checkToConvertToStatic(element)) return
                if (element.getAttribute('data-popupoff') === 'notification') return

                const memoized = memoize.has(element)
                const { shouldRemove } = memoized
                    ? { shouldRemove: memoize.get(element) }
                    : positionCheck(element, wArea)

                if (shouldRemove) {
                    if (getStyle(element, 'display') !== 'none') element.setAttribute('data-popupoff', 'bl')
                    setPropImp(element, 'display', 'none')
                }
                if (!memoized) memoize.set(element, shouldRemove)
            }
            additionalChecks(element, checkElem)
        }

        const action = elems => {
            removeOverflow()
            ;[...elems].forEach(checkElem)
            if (settings.restoreCont) findHidden()
            domObserver = new MutationObserver(mutations => watchMutations(mutations, checkElem, body, action))
            domObserver.observe(doc, { childList: true, subtree: true, attributes: true })
        }
        action(body.getElementsByTagName('*'))
    }

    const delicateMode = () => {
        const doc = document.documentElement
        const body = document.body
        const settings = getSettings()
        const subMode = settings.staticSubMode || 'relative'

        const checkElem = element => {
            if (!isDecentElem(element)) return
            const pos = getStyle(element, 'position')
            if (pos === 'fixed' || pos === 'sticky') {
                if (element.getAttribute('data-popupoff') === 'notification') return
                if (getStyle(element, 'display') !== 'none') element.setAttribute('data-popupoff', 'st')
                setPropImp(element, 'position', subMode)
            }
        }

        const action = elems => {
            removeOverflow()
            ;[...elems].forEach(checkElem)
            if (settings.restoreCont) findHidden()
            domObserver = new MutationObserver(mutations => watchMutations(mutations, checkElem, body, action))
            domObserver.observe(doc, { childList: true, subtree: true, attributes: true })
        }
        action(body.getElementsByTagName('*'))
    }

    const restoreFixedElems = () => {
        document.querySelectorAll('[data-popupoff]').forEach(el => {
            const attr = el.getAttribute('data-popupoff')
            if (attr === 'notification') return
            if (attr === 'bl') el.style.display = null
            else if (attr === 'st') el.style.setProperty('position', 'absolute')
            el.removeAttribute('data-popupoff')
        })
    }

    const getSettings = () => {
        try { return JSON.parse(GM_getValue('settings', '{}')) || { ...defaultSettings } }
        catch (e) { return { ...defaultSettings } }
    }

    const saveSettings = s => GM_setValue('settings', JSON.stringify(s))

    const modes = {
        aggressive: aggressiveMode,
        moderate: moderateMode,
        delicate: delicateMode,
        whitelist: () => { },
    }

    const startMode = modeName => {
        disconnectObserver()
        restoreFixedElems()
        if (modes[modeName]) modes[modeName]()
    }

    const modeNames = {
        aggressive: { en: 'Aggressive', pt: 'Agressivo' },
        moderate: { en: 'Moderate', pt: 'Moderado' },
        delicate: { en: 'Delicate', pt: 'Delicado' },
        whitelist: { en: 'OFF', pt: 'OFF' },
    }

    const modesList = ['moderate', 'aggressive', 'delicate', 'whitelist']

    let widget = null

    const updateWidget = mode => {
        if (widget) widget.textContent = modeNames[mode]?.pt || mode
    }

    const createWidget = curMode => {
        const existing = document.getElementById('popupoff-widget')
        if (existing) existing.remove()

        widget = document.createElement('div')
        widget.id = 'popupoff-widget'
        widget.setAttribute('data-popupoff', 'notification')

        widget.textContent = modeNames[curMode]?.pt || curMode

        widget.style.cssText =
            'position:fixed;bottom:20px;right:20px;z-index:999999;' +
            'background:#1a1a2e;color:#fff;padding:8px 16px;border-radius:20px;' +
            'font:13px/1.4 sans-serif;cursor:pointer;box-shadow:0 2px 12px rgba(0,0,0,.35);' +
            'user-select:none'

        widget.addEventListener('click', cycleMode)
        document.body.appendChild(widget)
    }

    const cycleMode = () => {
        const settings = getSettings()
        const idx = modesList.indexOf(settings.mode)
        const nextMode = modesList[(idx + 1) % modesList.length]
        settings.mode = nextMode
        saveSettings(settings)
        startMode(nextMode)
        updateWidget(nextMode)

        const n = document.createElement('div')
        n.textContent = 'PopUpOFF: ' + (modeNames[nextMode]?.pt || nextMode)
        n.style.cssText =
            'position:fixed;top:20px;left:50%;transform:translateX(-50%);' +
            'background:#1a1a2e;color:#fff;padding:10px 24px;border-radius:20px;' +
            'z-index:999999;font:14px sans-serif;box-shadow:0 2px 12px rgba(0,0,0,.3);' +
            'animation:popupoff-fade 2.5s forwards'
        document.body.appendChild(n)
        setTimeout(() => n.remove(), 2500)
    }

    const styleSheet = document.createElement('style')
    styleSheet.textContent =
        '@keyframes popupoff-fade{0%{opacity:1;transform:translateX(-50%) translateY(0)}' +
        '70%{opacity:1}100%{opacity:0;transform:translateX(-50%) translateY(-20px)}}'
    document.head.appendChild(styleSheet)

    const init = () => {
        const url = getPureURL()
        if (whitelist.includes(url)) return

        const settings = getSettings()
        startMode(settings.mode)
        createWidget(settings.mode)
    }

    document.addEventListener('keydown', e => {
        if (e.altKey && e.key.toLowerCase() === 'x') {
            e.preventDefault()
            cycleMode()
        }
    })

    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init)
    else init()


})()