remove page limit

Remove various page limit, check the code for website list, or add other websites

// ==UserScript==
// @namespace ATGT
// @name     remove page limit
// @name:zh-CN     解除网页限制,网站列表请看代码,也可以添加自定义网站
// @description   Remove various page limit, check the code for website list, or add other websites
//    quora.com: Remove login page
//    other domains: Remove copy or select limit
// How to remove other web page's copy or select limit:
//    1. Add domain to @match *://*.your.domain/*
//    2. (Optional): Add your unlock handlers to variable `unlockPageHandlers' below
// @description:zh-CN   解除各种网页限制,网站列表请看代码,也可以添加自定义网站
//    quora.com: 移除登录页面
//    其它网站:解除选择和复制限制
// 怎么移除其它网页的限制:
//    1. 将域名加到 @match,格式如下:
//        // @match *://*.your.domain/*
//    2. (可选):将解除限制的方法加到 `unlockPageHandlers' 这个数组
//
// @version  1.4.8
// @match    *://*.quora.com/*
// @match    *://*.360doc.com/*
// @match    *://*.baidu.com/*
// @match    *://*.sdifen.com/*
// @match    *://*.popbee.com/*
// @match    *://*.baikemy.com/*
// @exclude    *://pan.baidu.com/*
// @exclude    *://ditu.baidu.com/*
// @exclude    *://map.baidu.com/*
// @exclude    *://maps.baidu.com/*
// @run-at   document-start
// ==/UserScript==

/*
ChangeLog:
v1.4.8:
  5 Mar 2020, Delay before enable copy handler
v1.4.7:
  9 Nov 2019, Enable user select by css rules on '*' selector,
              Match baikemy.com
v1.4.6:
  16 Oct 2019, remove dead site, merge baidu handlers
v1.4.5:
  15 Oct 2019, skip hijack baidu login verify page
v1.4.4:
  18 Aug 2019, exclucde baidu map
v1.4.3:
  30 Jun 2019, Try remove all limit by default, may not work for all sites.
v1.4:
  27 Nov 2018, Add wenku.baidu.com
v1.3:
  29 Jan 2018, Added generic functions:
                1. Intercept event handlers.
                2. Wait for some node and do the unlock
v1.1:
  someday, Enable user select.
v1:
  someday, Remove quora login page.

*/

console.log(`=== unlock-page ${location.href} ===`);
(function () {
	var unlockPageHandlers = [
		/* !!! Need to add the global match to @include also */
		/* Formats:
		 *    [ /domain name regex/, [ selector-string or node-object, event-type, event-handler, delay-ms-before-run-event-handle, event-handler-parameter ] ]
		 *  -- or --
		 *    [ /domain name regex/, [ selector-string or node-object, event-type, event-handler, observed-object-to-be-added-dynamicly-before-run-event-handle, event-handler-parameter ] ]
		 *  -- or multiple handlers for the domain, syntax similar to previous ones --
		 *    [ /domain name regex/, [
		 *        [ selector-string or node-object, event-type, event-handler, delay or observed-object before run event handler, event-handler-parameter ],
		 *        [ selector-string or node-object, event-type, event-handler, delay or observed-object before run event handler, event-handler-parameter ], ]
		 *    ]
		 * Notes:
		 * 1. empty selector-string/node-object and event-type means run immediately/after-some-delay at document-start
		 * 2. some event does not need a node to run on, e.g. DOMContentLoaded
		 * 3. DOMContentLoaded is trigger before window's load event, since window's load event will trigger after external script/resource are loaded.
		 */
		[
			/quora\.com/,
			[ /* not needed for event DOMContentLoaded */, 'DOMContentLoaded', quoraHandler, 0]
		],
		[
			/360doc\.com/,
			[window, 'load', enableCopyHandler, 0]
		],
		[
			/popbee\.com/,
			[window, 'load', enableCopyHandler, 0]
		],
		[
			/wenku\.baidu\.com/,
			[
				[, , interceptJackEvent, 0], /* no selector/node and no event means run immediately at document-start */
				[window, 'load', enableCopyHandler, 0, '.bd.doc-reader'],
			]
		],
		[
			/^https?:\/\/([^/?&#%]*\.)?baidu\.com/,  // <=> http*://*.baidu.com
			[
				[, , interceptJackEvent, 0, '.vcode-body'],
				[, 'DOMContentLoaded', enableUserSelect, 0, 'body'],
				[window, 'load', enableCopyHandler, 0],
			]
		],
		[
			/sdifen\.com/,
			[
				[, , interceptJackEvent, 0],
				[, 'DOMContentLoaded', enableUserSelect, 0, 'body'],
				[window, 'load', enableCopyHandler, 0],
			]
		],
		[
			/.*/,
			[
				[, , interceptJackEvent, 0],
				[, 'DOMContentLoaded', enableUserSelect, 0, 'body'],
				[window, 'load', enableCopyHandler, 1000],
			]
		],
	];


	function quoraHandler() {
		console.log(new Date().toLocaleString(), ' ', arguments.callee.name);
		for (var d of document.body.childNodes) {
			if (/signup_wall_wrapper$/.test(d.id)) {
				d.remove();
				break;
			}
		}
		document.body.style.overflow = 'visible';
	}
	function replaceAddEventListener() {
		console.log('replaceAddEventListener');
		var r0_EventTargetRegFunc = EventTarget.prototype.addEventListener;
		//var r1_documentRegFunc = document.addEventListener;
		function dummyEvtRegFunc(type, listener, options) {
			//console.log('dummyEvtRegFunc', this, type, listener, options);
			var regFunc = r0_EventTargetRegFunc;
			if (window.ATGT_noHijackNodes) {
				let nhjk = document.querySelectorAll(window.ATGT_noHijackNodes);
				for (let n of nhjk) {
					if (n.contains(this)) {
						console.log('dummyEvtRegFunc skip hijack', this);
						regFunc.call(this, type, listener, options);
						return;
					}
				}
			}
			if (!this.ATGT_disabledEventHandlers)
				this.ATGT_disabledEventHandlers = {};
			if (!this.ATGT_enabledEventHandlers)
				this.ATGT_enabledEventHandlers = {};
			//console.log('window.ATGT_eventFilter', window.ATGT_eventFilter);
			if (window.ATGT_eventFilter && window.ATGT_eventFilter.test(type)) {
				console.log('dummyEvtRegFunc', this, type, listener, options);
				console.log('this event is %cdisabled.', 'color: red;');
				this.ATGT_disabledEventHandlers[type] = [listener, options];
			} else {
				//console.log('dummyEvtRegFunc', this, type, listener, options);
				//console.log('this event is %cregistered.', 'color: green;');
				this.ATGT_enabledEventHandlers[type] = [listener, options];
				regFunc.call(this, type, listener, options);
			}
		}
		EventTarget.prototype.addEventListener = dummyEvtRegFunc;
		if (document.addEventListener !== dummyEvtRegFunc)
			document.addEventListener = dummyEvtRegFunc;
	}

	function injectFunction(func) {
		var script = document.createElement('script');
		//script.appendChild(document.createTextNode('('+ func +')();'));
		script.appendChild(document.createTextNode('(function (){' + '(' + func + ')();' + '})();'));
		(document.body || document.head || document.documentElement).appendChild(script);
	}
	//console.log(''+injectFunction);
	injectFunction(replaceAddEventListener);

	function interceptJackEvent(noHijackNodes = '') {
		console.log(`interceptJackEvent noHijackNodes='${noHijackNodes}'`);
		var f = function setEventFilter() {
			var eventFilter = /copy|selectstart|mouseup|mousedown|contextmenu|keydown|keyup/;
			window.ATGT_eventFilter = eventFilter;
			window.ATGT_noHijackNodes = '__noHijackNodes__';
		};
		f = f.toString().replace('__noHijackNodes__', noHijackNodes);
		injectFunction(f);
	}
	function addStyleSheet(cssContent) {
		let cssid = `ATGT-remove-page-limit-css`;
		if (document.querySelector(`#${cssid}`))
			return;
		let style = document.createElement('STYLE');
		style.type = 'text/css';
		style.id = cssid;
		style.appendChild(document.createTextNode(cssContent));
		document.head.appendChild(style);
	}
	function enableUserSelect(sel) {
		console.log('enableUserSelect ', sel);
		var b = document.body;
		if (sel)
			b = document.querySelector(sel);
		var uselattrs = ['-webkit-touch-callout',
			'-webkit-user-select',
			'-khtml-user-select',
			'-moz-user-select',
			'-ms-user-select',
			'user-select',
		];
		for (var usel of uselattrs) {
			try {
				if (b && usel in b.style) {
					console.log('Found style user-select: ' + b.style[usel] + ', replace it.');
					b.style[usel] = 'text';
				}
			} catch (e) {
				console.log(e);
			}
		}
		console.log('add css rule to enable user select');
		addStyleSheet(`
			* {
				user-select: unset!important;
			}`);
	}

	function enableCopyHandler(sel) {
		var body = document.body || document.querySelector('body');
		var doc = document;
		console.log('enableCopyHandler', new Date().toLocaleString(), ' ', arguments.callee.name, body.oncopy, doc.oncopy);
		function replaceUserHandlers(n) {
			if (!n)
				return;
			if (n.onclick)
				n.onclick = null;
			if (n.oncontextmenu)
				n.oncontextmenu = null;
			if (n.oncopy)
				n.oncopy = null;
			if (n.onmouseup)
				n.onmouseup = null;
			if (n.onmousedown)
				n.onmousedown = null;
			if (n.onselectstart)
				n.onselectstart = null;
		}
		//console.log('body', body);
		replaceUserHandlers(body);
		replaceUserHandlers(doc);
		var node = document.querySelector(sel);
		//for (var n of nodes)
		console.log('sel', sel, '=>', node);
		replaceUserHandlers(node);
	}

	function waitForNode(targetSel, nodeFilter, nodeHandler, attrHandler) {
		console.log('waitForNode ', targetSel, nodeFilter, nodeHandler, attrHandler);
		// Select the node that will be observed for mutations
		var targetNode = document;
		if (typeof targetSel === 'object')
			targetNode = targetSel;
		else if (typeof targetSel === 'string')
			targetNode = document.querySelector(targetSel);

		// console.log('targetNode', targetNode);

		// Options for the observer (which mutations to observe)
		var config = {
			attributes: !!attrHandler,
			childList: !!nodeHandler,
			subtree: true,
		};

		// Callback function to execute when mutations are observed
		var callback = function (mutationsList) {
			for (var mutation of mutationsList) {
				if (nodeHandler && mutation.type == 'childList') {
					//console.log('A child node has been added ', mutation.addedNodes, ' or removed.');
					for (var node of mutation.addedNodes) {
						if (node.querySelector instanceof Function && node.querySelector(nodeFilter) || node === node.parentNode.querySelector(nodeFilter)) {
							setTimeout(nodeHandler, 0);
							this.disconnect();
							break;
						}
					}
				} else if (attrHandler && mutation.type == 'attributes') {
					//console.log('The ' + mutation.attributeName + ' attribute was modified.');
					setTimeout(attrHandler, 0);
				}
			}
		};

		// Create an observer instance linked to the callback function
		var observer = new MutationObserver(callback);
		// Start observing the target node for configured mutations
		observer.observe(targetNode, config);
		// Later, you can stop observing
		//observer.disconnect();
	}

	function runHandler(url, info) {
		try {
			console.log(new Date().toLocaleString(), ' handle ', url, ' with ', info);
			var nodeSel = info[0];
			var evt = info[1];
			var func = info[2];
			var delay_or_observe = info[3];
			var param = info[4];
			var node = document;
			var handler;
			if (typeof delay_or_observe === 'number') {
				handler = function () {
					setTimeout(func, delay_or_observe, param);
				};
			} else {
				handler = function () {
					waitForNode(delay_or_observe, param, function () { func(param); });
				};
			}
			if (typeof nodeSel === 'object')
				node = nodeSel;
			else if (typeof nodeSel === 'string')
				node = document.querySelector(nodeSel);
			console.info('nodeSel', nodeSel, 'node', node);
			if (evt)
				node && node.addEventListener(evt, () => { handler(param); });
			else
				handler(param);
		} catch (e) {
			console.log('Error handling ', url, ' ', e);
		}
	}

	for (var ph of unlockPageHandlers) {
		var url = ph[0];
		if (url.test(location.href)) {
			var info_list = ph[1];
			if (!(info_list[0] instanceof Array)) {
				try {
					runHandler(url, info_list);
				} catch (e) {
					console.log(e);
				}
			} else for (var info of info_list) {
				try {
					runHandler(url, info);
				} catch (e) {
					console.log(e);
				}
			}

			// only one rule runs on one site
			break;
		}
	}
})();

console.log(`=== /unlock-page ${location.href} ===`);