PaywallPassPlus

redirect to archive.is

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name            PaywallPassPlus
// @name:de         PaywallPassPlus
// @version         0.2
// @description     redirect to archive.is
// @description:de  Weiterleitung zu archive.is
// @icon            https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Hybrid_Access_logo_alternative.svg/250px-Hybrid_Access_logo_alternative.svg.png
// @author          magotar
// @license         GPL-3.0-or-later

// @match           https://www.bazonline.ch/*
// @match           https://www.nzz.ch/*


// @match           https://www.abendblatt.de/*
// @match           https://ga.de/*
// @match           https://www.heise.de/*
// @match           https://www.bild.de/*
// @match           https://www.badische-zeitung.de/*
// @match           https://www.spiegel.de/*
// @match           https://www.waz.de/*
// @match           https://www.zeit.de/*
// @match           https://www.berliner-zeitung.de/*
// @match           https://www.welt.de/*
// @match           https://www.freitag.de/*
// @match           https://www.faz.net/*
// @match           https://m.faz.net/*
// @match           https://www.sueddeutsche.de/*
// @match           https://sz-magazin.sueddeutsche.de/*
// @match           https://www.tagesspiegel.de/*

// @match           https://www.zerohedge.com/*
// @match           https://nytimes.com/*
// @match           https://www.nytimes.com/*


// @match           https://archive.today/*
// @match           https://archive.ph/*
// @match           https://archive.is/*
// @match           https://archive.fo/*
// @match           https://archive.li/*
// @match           https://archive.md/*
// @match           https://archive.vn/*
// @grant           GM.registerMenuCommand
// @grant           GM.xmlHttpRequest
// @connect         archive.today
// @connect         archive.ph
// @connect         archive.is
// @connect         archive.fo
// @connect         archive.li
// @connect         archive.md
// @connect         archive.vn
// @namespace https://greasyfork.org/users/1580381
// ==/UserScript==

/* global GM */
/* jshint asi: true, esversion: 8 */

(async function () {
  'use strict'

  const scriptName = 'PaywallPassPlus'

  const hostnames = [
    'archive.is',
    'archive.ph',
    'archive.today',
    'archive.fo',
    'archive.li',
    'archive.md',
    'archive.vn'
  ]
  const archiveHostCacheKey = 'paywall_bypass_archive_host'
  const archiveHostCacheTtlMs = 10 * 60 * 1000

  function sleep (t) {
    return new Promise(resolve => setTimeout(resolve, t))
  }

  function getCachedArchiveHostname () {
    try {
      const raw = window.sessionStorage.getItem(archiveHostCacheKey)
      if (!raw) return null

      const cache = JSON.parse(raw)
      if (!cache || typeof cache.hostname !== 'string' || typeof cache.ts !== 'number') {
        return null
      }

      const isFresh = Date.now() - cache.ts < archiveHostCacheTtlMs
      const isKnownHost = hostnames.includes(cache.hostname)

      if (isFresh && isKnownHost) {
        return cache.hostname
      }
    } catch (err) {
      console.debug('No usable archive host cache', err)
    }

    return null
  }

  function setCachedArchiveHostname (hostname) {
    try {
      window.sessionStorage.setItem(archiveHostCacheKey, JSON.stringify({
        hostname,
        ts: Date.now()
      }))
    } catch (err) {
      console.debug('Cannot persist archive host cache', err)
    }
  }

  function clearCachedArchiveHostname () {
    try {
      window.sessionStorage.removeItem(archiveHostCacheKey)
    } catch (err) {
      console.debug('Cannot clear archive host cache', err)
    }
  }

  function checkAvailability (hostname) {
    return new Promise(function (resolve, reject) {
      const onResponse = function (response) {
        const status = response && typeof response.status === 'number' ? response.status : 0
        if ((status >= 200 && status <= 400) || status === 429) {
          resolve(response)
        } else {
          reject(new Error('HOST_UNAVAILABLE'))
        }
      }
      GM.xmlHttpRequest({
        url: `https://${hostname}/`,
        method: 'GET',
        timeout: 5000,
        headers: {
          Range: 'bytes=0-63'
        },
        onload: onResponse,
        ontimeout: onResponse,
        onerror: onResponse
      })
    })
  }

  function showSpinner (msg) {
    let style = document.getElementById('check_host_style')
    if (!style) {
      style = document.head.appendChild(document.createElement('style'))
      style.setAttribute('id', 'check_host_style')
      style.textContent = `
        #check_host_spinner {
          position: fixed;
          background: #fff;
          height: 2.2em;
          top: 1em;
          left: 50%;
          transform: translate(-50%, 0);
          z-index: 1000;
          border-radius: 5px;
          border: 1px solid black;
          color: black;
          min-width: 7em;
          padding:3px;
        }
        #check_host_spinner .spinner-element {
          animation-duration: 1s;
          animation-iteration-count: infinite;
          animation-name: slide;
          animation-timing-function: linear;
          animation-direction: alternate-reverse;
          animation-play-state: running;
          background-color: #000;
          border-radius: 50%;
          border: 2px solid #fff;
          color: #fff;
          height: 1em;
          margin: auto;
          margin-left: 0;
          width: 1em;
          margin-top: -0.5em;
        }

        @keyframes slide {
          from {
            margin-left:0
          }
          to {
            margin-left:80%
          }
        }
      `
    }

    let div = document.getElementById('check_host_spinner')
    if (!div) {
      div = document.body.appendChild(document.createElement('div'))
      div.setAttribute('id', 'check_host_spinner')
      const text = div.appendChild(document.createElement('span'))
      text.setAttribute('id', 'check_host_text')
      const spinner = div.appendChild(document.createElement('div'))
      spinner.classList.add('spinner-element')
    }
    document.getElementById('check_host_text').innerHTML = msg || ''
    document.querySelector('#check_host_spinner .spinner-element').style.display = 'block'
  }

  function stopSpinner () {
    const e = document.querySelector('#check_host_spinner .spinner-element')
    if (e) {
      e.style.display = 'none'
    }
  }

  function hasPaywallElements (doc, selectors) {
    return selectors.some(selector => doc.querySelector(selector))
  }

  function hasJsonLdPaywall (doc) {
    const scripts = doc.querySelectorAll('script[type="application/ld+json"]')
    for (const script of scripts) {
      const text = script.textContent || ''
      if (!text) continue

      // Many publishers expose hard/soft paywalls through schema.org metadata.
      if (/"isAccessibleForFree"\s*:\s*false/i.test(text)) {
        return true
      }
    }
    return false
  }

  function checkSitePaywall (doc, selectors) {
    return hasPaywallElements(doc, selectors) || hasJsonLdPaywall(doc)
  }

  function hostnameMatches (hostname, siteHostname) {
    if (!siteHostname) return false

    // Match leading-dot suffixes like `.faz.net`
    if (siteHostname.startsWith('.')) {
      return hostname.endsWith(siteHostname)
    }

    // Match prefix patterns like `archive.` for `archive.today`, `archive.ph`, ...
    if (siteHostname.endsWith('.')) {
      const exact = siteHostname.slice(0, -1)
      return hostname === exact || hostname.startsWith(siteHostname)
    }

    return hostname === siteHostname || hostname.endsWith(`.${siteHostname}`)
  }

  async function archivePage (url) {
    window.setTimeout(() => showSpinner('archive'), 0)

    // Check which hostname of archive is currently available
    let workingHostname = null
    const cachedHostname = getCachedArchiveHostname()
    const hostnamesToCheck = cachedHostname
      ? [cachedHostname, ...hostnames.filter(hostname => hostname !== cachedHostname)]
      : hostnames

    for (const hostname of hostnamesToCheck) {
      try {
        window.setTimeout(() => showSpinner(hostname), 0)
        await checkAvailability(hostname)
        workingHostname = hostname
        setCachedArchiveHostname(hostname)
        break
      } catch (err) {
        if (err && 'message' in err && err.message === 'HOST_UNAVAILABLE') {
          if (hostname === cachedHostname) {
            clearCachedArchiveHostname()
          }
          console.debug(`${hostname} is NOT available`)
        } else {
          throw err
        }
      }
    }

    if (workingHostname) {
      const redirectUrl = `https://${workingHostname}/submit/?url=${encodeURIComponent(url)}`
      /*
      // push current url to document history
      document.location.assign(url)
      // wait that the url is pushed to history
      setTimeout(() => {
        document.location.assign(redirectUrl)
      }, 100)
      */
     document.location.href = redirectUrl
    } else {
      window.setTimeout(() => {
        showSpinner(`<a href="https://archive.today/submit/?url=${encodeURIComponent(url)}">Try archive.today</a>`)
        stopSpinner()
      }, 200)
      window.alert(scriptName +
        '\n\nSorry, all of the archive.today domains seem to be down.\n\nChecked:\n' +
        hostnames.join('\n') +
        '\n\nIf you are using a Cloudflare DNS, try to switch to another DNS provider or use a VPN. Currently Cloudflare can\'t reliably resolve archive.today.')
    }
  }

  GM.registerMenuCommand(scriptName + ' - Archive.today page', () => archivePage(document.location.href))

  let running = false
  let checking = false
  let firstRun = true

  async function main () {
    if (running || checking) {
      return
    }
    checking = true

    try {

      // {
      //   hostname: 'spiegel.de',
      //   check: (doc, win) => {},
      //   waitOnFirstRun: true, // optional: defaults to false
      //   action: (doc, win) => {}, // optional - defaults to `archivePage`
      // }

      const sites = [
        {
          hostname: 'bazonline.ch',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="metered"]',
              '[class*="plus"]',
              '[data-testid*="paywall"]'
            ])
          }
        },
        {
          hostname: 'nzz.ch',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="nzzplus"]',
              '[class*="metered"]',
              '[data-testid*="paywall"]'
            ])
          }
        },
        {
          hostname: 'ga.de',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="piano"]',
              '[data-area*="paywall"]',
              '[data-testid*="paywall"]'
            ])
          }
        },
        {
          hostname: 'bild.de',
          check: (doc) => {
            return doc.querySelector('ps-lefty-next-web')
          }
        },
        {
          hostname: 'badische-zeitung.de',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="bzplus"]',
              '[data-area*="paywall"]',
              '[data-testid*="paywall"]'
            ])
          }
        },
        {
          hostname: 'heise.de',
          check: (doc) => {
            return doc.querySelector('.js-upscore-article-content-for-paywall')
          }
        },
        {
          hostname: 'waz.de',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="piano"]',
              '[data-qa*="paywall"]',
              '[data-testid*="paywall"]'
            ])
          }
        },
        {
          hostname: 'berliner-zeitung.de',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="metered"]',
              '[class*="paid-content"]',
              '[data-area*="paywall"]'
            ])
          }
        },
        {
          hostname: 'welt.de',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="c-paywall"]',
              '[class*="o-paywall"]',
              '[data-testid*="paywall"]'
            ])
          }
        },
        {
          hostname: 'freitag.de',
          check: (doc) => {
            return doc.location.pathname.length > 1 && checkSitePaywall(doc, [
              '#paywall',
              '[id*="paywall"]',
              '[class*="paywall"]',
              '[class*="pluswall"]',
              '[class*="paid-content"]',
              '[data-area*="paywall"]'
            ])
          }
        },
        {
          hostname: 'spiegel.de',
          check: (doc) => {
            return doc.location.pathname.length > 1 && (
              doc.querySelector('[data-area="paywall"]') || (
                doc.querySelector('#Inhalt article header #spon-spplus-flag-l') &&
                    doc.querySelectorAll('article h2').length === 1
              )
            )
          }
        },
        {
          hostname: 'tagesspiegel.de',
          check: (doc) => {
            return doc.querySelectorAll('#paywall').length !== 0
          }
        },
        {
          hostname: 'zeit.de',
          check: (doc, win) => {
            return doc.location.pathname.length > 1 && (
              doc.querySelector('.zplus-badge__link') ||
                (doc.getElementById('paywall')?.childElementCount ?? 0) !== 0 ||
                ('k5aMeta' in win && win.k5aMeta.paywall === 'hard')
            )
          }
        },
        {
          hostname: '.faz.net',
          waitOnFirstRun: 6000,
          check: (doc) => {
            return doc.location.pathname.endsWith('.html') &&
                doc.querySelectorAll('.article [data-external-selector="header-title"]').length === 1 && ( // one heading -> article page
              doc.querySelector('[class*=atc-ContainerPaywall]') || // desktop
                    doc.querySelector('.wall.paywall') // desktop & mobile
            )
          }
        },
        {
          hostname: 'zerohedge.com',
          check: (doc, win) => {
            return doc.location.pathname.length > 1 && (
              doc.querySelector('[class*=PremiumOverlay] [class*=PremiumOverlay]') ||
                ('__NEXT_DATA__' in win && win.__NEXT_DATA__.props?.pageProps?.node?.isPremium === true)
            )
          }
        },
        {
          hostname: 'sz-magazin.sueddeutsche.de',
          check: (doc) => {
            return doc.location.search.includes('reduced=true') &&
                doc.querySelector('.articlemain__inner--reduced .paragraph--reduced')
          }
        },
        {
          hostname: 'sueddeutsche.de',
          check: (doc) => {
            return doc.location.search.includes('reduced=true') &&
                doc.querySelector('#sz-paywall iframe')
          }
        },
        {
          hostname: 'nytimes.com',
          check: (doc) => {
            return doc.querySelectorAll('#gateway-content iframe').length === 1
          }
        },
        {
          hostname: 'archive.',
          check: (doc) => {
            return doc.querySelector('form#submiturl [type=submit]')
          },
          action: (doc) => {
            const inputField = doc.querySelector('form#submiturl input#url')
            const submitButton = doc.querySelector('form#submiturl [type=submit]')
            const m = doc.location.search.match(/url=([^&]+)/)
            if (submitButton && inputField && m) {
              inputField.value = decodeURIComponent(m[1])
              submitButton.click()
            }
          }
        },
        {
          hostname: 'archive.',
          check: (doc, win) => {
            const input = doc.querySelector('#HEADER form input[name="q"]')
            if (!input || !input.value) return false

            let inputHostname
            try {
              const url = new URL(input.value)
              inputHostname = url.hostname
            } catch (err) {
              console.warn('Invalid URL in input:', input.value)
              return false
            }

            return sites.some(site =>
              site.hostname !== 'archive.' &&
                hostnameMatches(inputHostname, site.hostname) &&
                site.check(doc, win)
            )
          },
          action: (doc, win) => {
            // Redirect to history of this page, if there is also a paywall in this archive
            // Only redirect once for this session
            const key = doc.location.href
            const alreadyRedirected = win.sessionStorage.getItem(key)
            const historyLink = Array.from(doc.querySelectorAll('#HEADER form a'))
              .find(e => e.textContent.includes('history'))

            if (!alreadyRedirected && historyLink) {
              win.sessionStorage.setItem(key, '1')
              historyLink.click()
            }
          }
        }
      ]

      for (const site of sites) {
        if (hostnameMatches(document.location.hostname, site.hostname)) {
          const shouldWait = firstRun && site.waitOnFirstRun

          if (shouldWait) {
            // Wait a little the first time to let bypass-paywalls-firefox-clean do the job.
            firstRun = false
            await sleep(typeof site.waitOnFirstRun === 'number' ? site.waitOnFirstRun : 3000)
          }

          const result = await site.check(document, window)
          if (result) {
            running = true

            if (typeof site.action === 'function') {
              site.action(document, window)
            } else {
              await archivePage(document.location.href)
            }

            break
          }
        }
      }
      firstRun = false
    } finally {
      checking = false
    }
  }

  function watchForLatePaywalls (durationMs = 8000) {
    const root = document.documentElement
    if (!root) return

    let rerunTimeout = null
    const observer = new window.MutationObserver(() => {
      if (running) return

      if (rerunTimeout) {
        window.clearTimeout(rerunTimeout)
      }
      rerunTimeout = window.setTimeout(() => {
        void main()
      }, 250)
    })

    observer.observe(root, {
      childList: true,
      subtree: true,
      attributes: true
    })

    window.setTimeout(() => {
      observer.disconnect()
      if (rerunTimeout) {
        window.clearTimeout(rerunTimeout)
      }
    }, durationMs)
  }

  await main()
  watchForLatePaywalls()
})()