Created
April 24, 2020 21:51
-
-
Save ItsDanielHarris/dd15f0fbc6ad6a6c3acd881c0109d3a2 to your computer and use it in GitHub Desktop.
Newspaper Paywall Bypasser Updated
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Newspaper Paywall Bypasser | |
// @namespace https://greasyfork.org/users/649 | |
// @version 1.2.5 | |
// @description Bypass the paywall on online newspapers | |
// @author Adrien Pyke | |
// @match *://www.thenation.com/article/* | |
// @match *://www.wsj.com/articles/* | |
// @match *://blogs.wsj.com/* | |
// @match *://www.bostonglobe.com/* | |
// @match *://www.nytimes.com/* | |
// @match *://myaccount.nytimes.com/mobile/wall/smart/* | |
// @match *://mobile.nytimes.com/* | |
// @match *://www.latimes.com/* | |
// @match *://www.washingtonpost.com/* | |
// @match *://www.bloomberg.com/* | |
// @grant GM_xmlhttpRequest | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @grant GM_registerMenuCommand | |
// @grant unsafeWindow | |
// @require https://gitcdn.link/repo/fuzetsu/userscripts/477063e939b9658b64d2f91878da20a7f831d98b/wait-for-elements/wait-for-elements.js | |
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js | |
// @noframes | |
// ==/UserScript== | |
(() => { | |
'use strict'; | |
// short reference to unsafeWindow (or window if unsafeWindow is unavailable e.g. bookmarklet) | |
const W = typeof unsafeWindow === 'undefined' ? window : unsafeWindow; | |
const SCRIPT_NAME = 'Newspaper Paywall Bypasser'; | |
const Util = { | |
log(...args) { | |
args.unshift(`%c${SCRIPT_NAME}:`, 'font-weight: bold;color: #233c7b;'); | |
console.log(...args); | |
}, | |
q(query, context = document) { | |
return context.querySelector(query); | |
}, | |
qq(query, context = document) { | |
return Array.from(context.querySelectorAll(query)); | |
}, | |
getQueryParameter(name, url = W.location.href) { | |
name = name.replace(/[[\]]/gu, '\\$&'); | |
const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`, 'u'); | |
const results = regex.exec(url); | |
if (!results) return null; | |
if (!results[2]) return ''; | |
return decodeURIComponent(results[2].replace(/\+/gu, ' ')); | |
}, | |
appendStyle(css) { | |
let out = ''; | |
for (const selector in css) { | |
out += `${selector}{`; | |
for (const rule in css[selector]) { | |
out += `${rule}:${css[selector][rule]}!important;`; | |
} | |
out += '}'; | |
} | |
const style = document.createElement('style'); | |
style.type = 'text/css'; | |
style.appendChild(document.createTextNode(out)); | |
document.head.appendChild(style); | |
}, | |
clearAllIntervals() { | |
const interval_id = window.setInterval(null, 9999); | |
for (let i = 1; i <= interval_id; i++) { | |
window.clearInterval(i); | |
} | |
}, | |
hijackScrollEvent(cb) { | |
document.onscroll = e => { | |
if (cb) { | |
cb(e); | |
} | |
e.preventDefault(); | |
e.stopImmediatePropagation(); | |
return false; | |
}; | |
}, | |
addScript(src, onload) { | |
const s = document.createElement('script'); | |
s.onload = onload; | |
s.src = src; | |
document.body.appendChild(s); | |
}, | |
prepend(parent, child) { | |
parent.insertBefore(child, parent.firstChild); | |
} | |
}; | |
// GM_xmlhttpRequest polyfill | |
if (typeof GM_xmlhttpRequest === 'undefined') { | |
Util.log('Adding GM_xmlhttpRequest polyfill'); | |
W.GM_xmlhttpRequest = function(config) { | |
const xhr = new XMLHttpRequest(); | |
xhr.open(config.method || 'GET', config.url); | |
if (config.headers) { | |
for (const header in config.headers) { | |
xhr.setRequestHeader(header, config.headers[header]); | |
} | |
} | |
if (config.anonymous) { | |
xhr.setRequestHeader('Authorization', ''); | |
} | |
if (config.onload) { | |
xhr.onload = function() { | |
config.onload(xhr); | |
}; | |
} | |
if (config.onerror) { | |
xhr.onerror = function() { | |
config.onerror(xhr.status); | |
}; | |
} | |
xhr.send(); | |
}; | |
} | |
/** | |
* Sample Implementation: | |
{ | |
name: 'something', // name of the implementation | |
match: '^https?://domain.com/.*', // the url to react to | |
remove: '#element', // css selector to get elements to remove | |
wait: 3000, // how many ms to wait before running (to wait for elements to load), or a css selector to keep trying until it returns an elem | |
referer: 'something', // load content in with an xhr using this referrer | |
replace: '#element', // css selector to get element to replace with xhr | |
replaceUsing: 'url', // url to use for the replace xhr. If null, it'll use the curren url. | |
replaceWith: '#element', // css selector to get element to replace the element with. if null, it will use the same seletor as replace. | |
css: {}, // object, keyed by css selector of css rules | |
bmmode: function() { }, // function to call before doing anything else if in BM_MODE | |
fn: function() { }, // a function to run before doing anything else for more complicated logic | |
afterReplace: function() { } // a function that runs after the replace is done | |
} | |
* Any of the CSS selectors can be functions instead that return the desired value. | |
*/ | |
const implementations = [ | |
{ | |
name: 'The Nation', | |
match: '^https?://www\\.thenation\\.com/article/.*', | |
remove: '#paywall', | |
wait: '#paywall', | |
bmmode() { | |
W.Paywall.hide(); | |
} | |
}, | |
{ | |
name: 'Wall Street Journal', | |
match: '^https?://.*\\.wsj\\.com/.*', | |
wait: '.wsj-snippet-login', | |
referer: 'https://t.co/T1323aaaa', | |
afterReplace() { | |
W.loadCSS('//asset.wsj.net/public/extra.production-2a7a40d6.css'); | |
const scripts = Util.qq('script'); | |
const add = function(regex, onload) { | |
const matching = scripts.filter(script => script.src.match(regex)); | |
if (matching.length > 0) { | |
Util.addScript(matching[0].src, onload); | |
} else { | |
onload(); | |
} | |
}; | |
add(/\/common\\.js$/iu, () => { | |
add(/\/article\\.js$/iu, () => { | |
add(/\/snippet\\.js$/iu); | |
}); | |
}); | |
} | |
}, | |
{ | |
name: 'Boston Globe', | |
match: '^https?://www\\.bostonglobe\\.com/.*', | |
css: { | |
'html, body, #contain': { | |
overflow: 'visible' | |
}, | |
'body': { | |
position: 'initial', | |
overflow: 'scroll' | |
}, | |
'.mfp-wrap, .mfp-ready': { | |
display: 'none' | |
}, | |
'.meter-paywall--visible, .paywall': { | |
display: 'none' | |
} | |
}, | |
fn() { | |
$("button:contains('Read full article')").trigger('click'); | |
} | |
}, | |
{ | |
name: 'NY Times', | |
match: '^https?://www\\.nytimes\\.com/.*', | |
css: { | |
'html, body': { | |
overflow: 'visible' | |
}, | |
'#Gateway_optly, #overlay': { | |
display: 'none' | |
}, | |
'.media .image': { | |
'margin-bottom': '7px' | |
}, | |
'.new-story-body-text': { | |
'font-size': '1.0625rem', | |
'line-height': '1.625rem' | |
} | |
}, | |
cleanupStory(story) { | |
if (story) { | |
// prevent payywall from finding the elements to remove | |
Util.qq('figure', story).forEach(figure => { | |
figure.outerHTML = figure.outerHTML | |
.replace(/<figure/u, '<div') | |
.replace(/<\/figure/u, '</div'); | |
}); | |
Util.qq('.story-body-text', story).forEach(paragraph => { | |
paragraph.classList.remove('story-body-text'); | |
paragraph.classList.add('new-story-body-text'); | |
}); | |
} | |
return story; | |
}, | |
bmmode() { | |
const self = this; | |
Util.clearAllIntervals(); | |
GM_xmlhttpRequest({ | |
url: W.location.href, | |
method: 'GET', | |
onload(response) { | |
const tempDiv = document.createElement('div'); | |
tempDiv.innerHTML = response.responseText; | |
const story = self.cleanupStory(Util.q('#story', tempDiv)); | |
if (story) { | |
Util.q('#story').innerHTML = story.innerHTML; | |
} | |
} | |
}); | |
}, | |
fn() { | |
// clear intervals once the paywall comes up to prevent changes afterward | |
waitForElems({ | |
sel: '#Gateway_optly', | |
stop: true, | |
onmatch: Util.clearAllIntervals | |
}); | |
waitForElems({ | |
sel: '#wallIframe', | |
stop: true, | |
onmatch: ()=>{ | |
Util.clearAllIntervals() | |
document.getElementsByTagName('html')[0].style = "" | |
document.getElementsByTagName('body')[0].style = "" | |
document.getElementById("wallIframe").parentElement.parentElement.hidden = true; | |
}, | |
}); | |
this.cleanupStory(Util.q('#story')); | |
setTimeout(() => { | |
W.require(['jquery/nyt'], $ => { | |
W.require(['vhs'], vhs => { | |
Util.qq('.video').forEach(video => { | |
video.setAttribute('style', 'position: relative'); | |
const bind = document.createElement('div'); | |
bind.classList.add('video-bind'); | |
const div = document.createElement('div'); | |
div.setAttribute( | |
'style', | |
'padding-bottom: 56.25%; position: relative; overflow: hidden;' | |
); | |
bind.appendChild(div); | |
Util.prepend(video, bind); | |
vhs.player({ | |
id: video.dataset.videoid, | |
container: $(div), | |
width: '100%', | |
height: '100%', | |
mode: 'html5', | |
controlsOverlay: { | |
mode: 'article' | |
}, | |
cover: { | |
mode: 'article' | |
}, | |
newControls: true | |
}); | |
}); | |
}); | |
}); | |
}, 0); | |
} | |
}, | |
{ | |
name: 'NY Times Mobile Redirect', | |
match: '^https?://myaccount\\.nytimes\\.com/mobile/wall/smart/.*', | |
fn() { | |
const article = Util.getQueryParameter('EXIT_URI'); | |
if (article) { | |
W.location.replace( | |
`http://mobile.nytimes.com?LOAD_ARTICLE=${encodeURIComponent(article)}` | |
); | |
} | |
} | |
}, | |
{ | |
name: 'NY Times Mobile Loader', | |
match: '^https?://mobile\\.nytimes\\.com', | |
css: { | |
'.full-art': { | |
'font-family': 'Georgia,serif', | |
color: '#333' | |
}, | |
'.full-art .article-body': { | |
'margin-bottom': '26px', | |
'font-size': '1.6em', | |
'line-height': '1.4em' | |
} | |
}, | |
replaceUsing: Util.getQueryParameter('LOAD_ARTICLE'), | |
replace() { | |
if (this.repalceUsing) { | |
return '.sect'; | |
} | |
return null; | |
}, | |
replaceWith() { | |
if (this.repalceUsing) { | |
return 'article'; | |
} | |
return null; | |
} | |
}, | |
{ | |
name: 'Bloomberg', | |
match: '^https?://www\\.bloomberg\\.com/.*', | |
css: { | |
'#graphics-paywall-overlay': { | |
display: 'none' | |
}, | |
'body[data-paywall-overlay-status="show"]': { | |
overflow: 'scroll' | |
} | |
} | |
}, | |
{ | |
name: 'LA Times', | |
match: '^https?://www\\.latimes\\.com/.*', | |
css: { | |
'div#reg-overlay': { | |
display: 'none' | |
}, | |
'html, body': { | |
overflow: 'visible' | |
} | |
}, | |
fn: Util.hijackScrollEvent | |
}, | |
{ | |
name: 'Washington Post', | |
match: '^https?://www\\.washingtonpost\\.com/.*', | |
css: { | |
'.wp_signin, #wp_Signin': { | |
display: 'none' | |
}, | |
'html, body': { | |
overflow: 'visible' | |
}, | |
'#wallIframe': { | |
display: 'none' | |
}, | |
'.o-50': { | |
display: 'none' | |
}, | |
'div[data-qa=paywall]': { | |
display: 'none' | |
} | |
}, | |
fn() { | |
const handler = e => { | |
e.stopImmediatePropagation(); | |
}; | |
document.addEventListener('keydown', handler, true); | |
document.addEventListener('mousewheel', handler, true); | |
} | |
} | |
]; | |
// END OF IMPLEMENTATIONS | |
const Config = { | |
load() { | |
const defaults = { | |
blacklist: {} | |
}; | |
let cfg = GM_getValue('cfg'); | |
if (!cfg) return defaults; | |
cfg = JSON.parse(cfg); | |
Object.entries(defaults).forEach(([key, value]) => { | |
if (typeof cfg[key] === 'undefined') { | |
cfg[key] = value; | |
} | |
}); | |
return cfg; | |
}, | |
save(cfg) { | |
GM_setValue('cfg', JSON.stringify(cfg)); | |
}, | |
toggleBlacklist(imp) { | |
const cfg = Config.load(); | |
if (cfg.blacklist[imp]) { | |
cfg.blacklist[imp] = false; | |
} else { | |
cfg.blacklist[imp] = true; | |
} | |
Config.save(cfg); | |
} | |
}; | |
const App = { | |
currentImpName: null, | |
bypass(imp) { | |
if (W.BM_MODE && imp.bmmode) { | |
Util.log('Running bookmarkelet specific function'); | |
imp.bmmode(); | |
} | |
if (imp.fn) { | |
Util.log('Running site specific function'); | |
imp.fn(); | |
} | |
if (imp.css) { | |
Util.log('Adding style'); | |
const cssObj = typeof imp.css === 'function' ? imp.css() : imp.css; | |
Util.appendStyle(cssObj); | |
} | |
if (imp.remove) { | |
Util.log('Removing elements'); | |
const elemsToRemove = typeof imp.remove === 'function' ? imp.remove() : Util.qq(imp.remove); | |
elemsToRemove.forEach(elem => { | |
elem.remove(); | |
}); | |
} | |
const replaceSelector = typeof imp.replace === 'function' ? imp.replace() : imp.replace; | |
let replaceUsing = | |
typeof imp.replaceUsing === 'function' ? imp.replaceUsing() : imp.replaceUsing; | |
const theReferer = typeof imp.referer === 'function' ? imp.referer() : imp.referer; | |
if (replaceSelector || replaceUsing || theReferer) { | |
replaceUsing = replaceUsing || W.location.href; | |
Util.log(`Loading xhr for "${replaceUsing}" with referer: ${theReferer}`); | |
GM_xmlhttpRequest({ | |
method: 'GET', | |
url: replaceUsing, | |
headers: { | |
referer: theReferer | |
}, | |
anonymous: true, | |
onload(response) { | |
if (replaceSelector) { | |
let replaceWithSelector = | |
typeof imp.replaceWith === 'function' ? imp.replaceWith() : imp.replaceWith; | |
replaceWithSelector = replaceWithSelector || replaceSelector; | |
const tempDiv = document.createElement('div'); | |
tempDiv.innerHTML = response.responseText; | |
Util.q(replaceSelector).innerHTML = Util.q(replaceWithSelector, tempDiv).innerHTML; | |
} else { | |
document.body.innerHTML = response.responseText; | |
} | |
if (imp.afterReplace) { | |
Util.log('Performing after replace logic'); | |
imp.afterReplace(); | |
} | |
}, | |
onerror() { | |
Util.log('error occured when loading xhr'); | |
} | |
}); | |
} | |
Util.log('Paywall Bypassed.'); | |
}, | |
waitAndBypass(imp) { | |
if (imp.wait) { | |
const waitType = typeof imp.wait; | |
if (waitType === 'number') { | |
setTimeout(App.bypass(imp), imp.wait || 0); | |
} else { | |
const wait = waitType === 'function' ? imp.wait() : imp.wait; | |
waitForElems({ | |
sel: wait, | |
stop: true, | |
onmatch() { | |
Util.log('Condition fulfilled, bypassing'); | |
App.bypass(imp); | |
} | |
}); | |
} | |
} else { | |
App.bypass(imp); | |
} | |
}, | |
start(imps) { | |
Util.log('starting...'); | |
const success = imps.some(imp => { | |
if (imp.match && new RegExp(imp.match, 'iu').test(W.location.href)) { | |
App.currentImpName = imp.name; | |
if (W.BM_MODE) { | |
App.waitAndBypass(imp); | |
} else { | |
let menuCommandText; | |
if (!Config.load().blacklist[imp.name]) { | |
menuCommandText = `Disable ${SCRIPT_NAME} for ${imp.name}`; | |
App.waitAndBypass(imp); | |
} else { | |
menuCommandText = `Enable ${SCRIPT_NAME} for ${imp.name}`; | |
Util.log(`${imp.name} blacklisted`); | |
} | |
GM_registerMenuCommand(menuCommandText, () => { | |
Config.toggleBlacklist(imp.name); | |
location.reload(); | |
}); | |
} | |
return true; | |
} | |
}); | |
if (!success) { | |
Util.log(`no implementation for ${W.location.href}`, 'error'); | |
} | |
} | |
}; | |
App.start(implementations); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment