A Universal Script to Re-Enable the Selection and Copying

Enables select, right-click, copy and drag on pages that disable them. Additional Feature: Long Press Text Selection

当前为 2021-06-13 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name A Universal Script to Re-Enable the Selection and Copying
  3. // @name:zh-TW A Universal Script to Re-Enable the Selection and Copying
  4. // @version 1.6.1
  5. // @description Enables select, right-click, copy and drag on pages that disable them. Additional Feature: Long Press Text Selection
  6. // @description:zh-TW 解除禁止復制、剪切、選擇文本、右鍵菜單的限制。破解鎖右鍵、文字複製、文字選取。額外功能:長按文字選取。
  7. // @include /^https?\:\/\//
  8. // @grant none
  9. // @run-at document-start
  10. // @namespace https://greasyfork.org/users/371179
  11. // ==/UserScript==
  12. 'use strict';
  13. (function $$($) {
  14.  
  15. console.log('script at', location + " #1")
  16. if (document == null || !document.documentElement) return window.requestAnimationFrame($$); // this is tampermonkey bug?? not sure
  17.  
  18. console.log('script at', location + " #2")
  19.  
  20. function isSupportAdvancedEventListener() {
  21. if ('_b1750' in $) return $._b1750
  22. var prop = 0;
  23. document.createAttribute('z').addEventListener('', null, {
  24. get passive() {
  25. prop++;
  26. },
  27. get once() {
  28. prop++;
  29. }
  30. });
  31. return ($._b1750 = prop == 2);
  32. }
  33.  
  34. var mKey = 'dqzadwpujtct';
  35. var _ksNonFalseFunc = '___nff_' + mKey + '___',
  36. _ksReturnValue = '___returnValue_' + mKey + '___';
  37.  
  38. $ = {
  39. utSelectionColorHack: 'msmtwejkzrqa',
  40. utTapHighlight: 'xfcklblvkjsj',
  41.  
  42. mAlert_DOWN: function() {}, //dummy function in case alert replacement is not valid
  43. mAlert_UP: function() {}, //dummy function in case alert replacement is not valid
  44.  
  45. isAnySelection: function() {
  46. var sel = (window.getSelection || function() {})();
  47. return !sel ? null : (typeof sel.isCollapsed == 'boolean') ? !sel.isCollapsed : (sel.toString().length > 0);
  48. },
  49.  
  50. createCSSElement: function(cssStyle, container) {
  51. var css = document.createElement('style'); //slope: DOM throughout
  52. css.type = 'text/css';
  53. css.innerHTML = cssStyle;
  54. if (container) container.appendChild(css);
  55. return css;
  56. },
  57.  
  58. createFakeAlert: function(_alert) {
  59. if (typeof _alert != 'function') return null;
  60.  
  61. function alert(msg) {
  62. setTimeout(() => (alert.__isDisabled__() ? console.log("alert msg disabled: ", msg) : _alert.apply(this, arguments)), 9);
  63. };
  64. alert.toString = () => "function alert() { [native code] }";
  65. return alert;
  66. },
  67.  
  68. createFuncReplacer: function(originalFunc, pName, resFX) {
  69. resFX = function(ev) {
  70. var res = originalFunc.apply(this, arguments);
  71. if (!this || this[pName] != resFX) return res; // if this is null or undefined, or this.onXXX is not this function
  72. if (res === false) return; // return undefined when "return false;"
  73. originalFunc[_ksNonFalseFunc] = true;
  74. this[pName] = originalFunc; // restore original
  75. return res;
  76. }
  77. resFX.toString = () => originalFunc.toString();
  78. return resFX;
  79. },
  80.  
  81. listenerDisableAll: function(evt) {
  82. var elmNode = evt.target;
  83. while (elmNode && elmNode.nodeType > 0) { //i.e. HTMLDocument or HTMLElement
  84. var pName = 'on' + evt.type
  85. var f = elmNode[pName];
  86. if (typeof f == 'function' && f[_ksNonFalseFunc] !== true) {
  87. var nf = $.createFuncReplacer(f, pName);
  88. nf[_ksNonFalseFunc] = true;
  89. elmNode[pName] = nf;
  90. }
  91. elmNode = elmNode.parentNode;
  92. }
  93. },
  94.  
  95. onceCssHighlightSelection: () => {
  96. $.onceCssHighlightSelection = null
  97. var s = [...document.querySelectorAll('a,p,div,span,b,i,strong,li')].filter(elm => elm.childElementCount === 0); // randomly pick an element containing text only to avoid css style bug
  98. var elm = !s.length ? document.body : s[s.length >> 1];
  99.  
  100. var selectionStyle = window.getComputedStyle(elm, ':selection');
  101. if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(selectionStyle.backgroundColor)) document.documentElement.setAttribute($.utSelectionColorHack, "");
  102.  
  103. var elmStyle = window.getComputedStyle(elm)
  104. if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(elmStyle.webkitTapHighlightColor)) document.documentElement.setAttribute($.utTapHighlight, "");
  105.  
  106. },
  107.  
  108. replacementSetData:function(_setData,evt) {
  109. evt.clipboardData[$.ksSetData] = _setData;
  110.  
  111. function setData() {
  112. var res = _setData.apply(this, arguments);
  113. if (evt.clipboardData === this && this.setData === setData && evt.cancelable && evt.defaultPrevented === false) {
  114. evt.preventDefault();
  115. if (evt.defaultPrevented === true) {
  116. this.setData = _setData;
  117. delete this[$.ksSetData];
  118. }
  119. }
  120. return res;
  121. }
  122. setData.toString = () => _setData.toString();
  123. evt.clipboardData.setData = setData;
  124. },
  125.  
  126.  
  127. enableSelectClickCopy: function() {
  128.  
  129. $.eyEvts = ['keydown', 'keyup', 'copy', 'contextmenu', 'select', 'selectstart', 'dragstart', 'beforecopy']; //slope: throughout
  130.  
  131.  
  132. function getClipText(evt) {
  133.  
  134. var text;
  135. var clip = (evt.originalEvent || evt).clipboardData;
  136. if (clip) {
  137. text = clip.getData('text/plain') || clip.getData('text/html');
  138. } else {
  139. text = window.clipboardData.getData("text") || null;
  140. }
  141. return text;
  142.  
  143. }
  144.  
  145. function isDeactivePreventDefault(evt) {
  146. if ($.bypass) return false;
  147. var j = $.eyEvts.indexOf(evt.type);
  148. switch (j) {
  149. case -1:
  150. return false;
  151. case 0:
  152. case 1:
  153. return (evt.keyCode == 67 && (evt.ctrlKey || evt.metaKey) && !evt.altKey && !evt.shiftKey && $.isAnySelection() === true);
  154. case 2:
  155. var dataTypeString = ((evt.clipboardData || {}).types || [])[0];
  156. // see the richtext hack in https://www.cleancss.com/css-beautify/
  157. // see https://developer.mozilla.org/zh-CN/docs/Web/API/Element/copy_event
  158. // see https://w3c.github.io/clipboard-apis/#widl-ClipboardEvent-clipboardData
  159.  
  160. if (!dataTypeString) {
  161. $.ksSetData = 'rgqclrdllmhr'
  162.  
  163. if (!evt.clipboardData[$.ksSetData] && evt.cancelable && evt.defaultPrevented === false) {
  164. $.replacementSetData(evt.clipboardData.setData,evt);
  165.  
  166. }
  167. //no replacement data
  168. return true;
  169. } else if (window.getSelection().toString().trim()) {
  170.  
  171. //there is replacement data and the selection is not empty
  172. console.log("copy event - clipboardData replacement is allowed and the selection is not empty", window.getSelection().toString().trim())
  173. return false;
  174. } else {
  175. //there is replacement data and the selection is empty
  176. return false;
  177. }
  178.  
  179. default:
  180. return true;
  181. }
  182. }
  183.  
  184. Event.prototype.preventDefault = (function(f) {
  185. return function preventDefault() {
  186. if (!isDeactivePreventDefault(this)) f.apply(this);
  187. }
  188. })(Event.prototype.preventDefault);
  189. Event.prototype.preventDefault.toString = () => "function preventDefault() { [native code] }"
  190.  
  191. Object.defineProperty(Event.prototype, "returnValue", {
  192. get() {
  193. return _ksReturnValue in this ? this[_ksReturnValue] : true;
  194. },
  195. set(newValue) {
  196. if (!isDeactivePreventDefault(this) && newValue === false) this.preventDefault();
  197. this[_ksReturnValue] = newValue;
  198. },
  199. enumerable: true,
  200. configurable: true
  201. });
  202.  
  203. for (var i = 2, eventsCount = $.eyEvts.length; i < eventsCount; i++) {
  204. document.addEventListener($.eyEvts[i], $.listenerDisableAll, true); // Capture Event; passive:false; expected occurrence COMPLETELY before Target Capture and Target Bubble
  205. }
  206.  
  207. var _alert = window.alert; //slope: temporary
  208. if (typeof _alert == 'function') {
  209. var _mAlert = $.createFakeAlert(_alert);
  210. if (_mAlert) {
  211. var lastClickAt = 0;
  212. _mAlert.__isDisabled__ = () => lastClickAt + 50 > +new Date;
  213. $.mAlert_DOWN = () => (lastClickAt = +new Date);
  214. $.mAlert_UP = () => (lastClickAt = 0);
  215. window.alert = _mAlert
  216. }
  217. }
  218.  
  219. },
  220.  
  221. mainEnableScript: () => {
  222. var vv = 'gykqyzwufxpz';
  223. var cssStyleOnReady = `
  224.  
  225. *, body *, div, span, body *::before, body *::after, *:hover, *:link, *:visited, *:active , *[style], *[class]{
  226. -khtml-user-select: auto !important; -moz-user-select: auto !important; -ms-user-select: auto !important;
  227. -webkit-touch-callout: default !important; -webkit-user-select: auto !important; user-select: auto !important;
  228. }
  229. a:hover, a:hover *{
  230. -khtml-user-select: text !important; -moz-user-select: text !important;-ms-user-select: text !important;
  231. -webkit-user-select: text !important; user-select: text !important;
  232. }
  233.  
  234. html[${vv}] *:hover, html[${vv}] *:hover * { cursor:text !important;}
  235. html[${vv}] :not(input):not(textarea)::selection {background-color: rgba(255, 156, 179,0.5) !important;}
  236. html body *:hover>img[src]{pointer-events:auto;}
  237. [${$.utSelectionColorHack}] :not(input):not(textarea)::selection{ background-color: Highlight !important; color: HighlightText !important;}
  238. [${$.utSelectionColorHack}] :not(input):not(textarea)::-moz-selection{ background-color: Highlight !important; color: HighlightText !important;}
  239. [${$.utTapHighlight}] *{ -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18) !important;}
  240.  
  241. `.trim();
  242.  
  243. $.enableSelectClickCopy()
  244. $.createCSSElement(cssStyleOnReady, document.documentElement);
  245.  
  246. },
  247.  
  248. mainEvents: (listenerPress, listenerRelease) => {
  249. (["mousedown", "click", "dblclick", "contextmenu"]).forEach(function(event) {
  250. document.addEventListener(event, listenerPress, true); // Capture Event; passive:false; ensure the occurrence of 1st capture event COMPLETELY before executing other listeners
  251. });
  252. document.addEventListener("mouseup", listenerRelease, false); // Bubble Event; passive: true/false; order for releasing is insignificant
  253. }
  254.  
  255. }
  256.  
  257. $.mainEnableScript();
  258.  
  259. $.mainEvents(function(evt) {
  260. if ($.onceCssHighlightSelection) window.requestAnimationFrame($.onceCssHighlightSelection);
  261. if (evt.type != "contextmenu" && evt.which != 3) return;
  262. if ($.cid_mouseup > 0) $.cid_mouseup = clearTimeout($.cid_mouseup);
  263. $.mAlert_DOWN();
  264. },
  265. function(evt) {
  266. if (evt.which != 3) return;
  267. $.cid_mouseup = setTimeout($.mAlert_UP, 17);
  268. });
  269.  
  270. console.log('userscript running - To Re-Enable Selection & Copying');
  271.  
  272.  
  273. if (isSupportAdvancedEventListener() && 'assign' in Object) {
  274.  
  275. // this is optional feature for modern browser
  276.  
  277.  
  278. function checkPointer(targetElm) {
  279. if(targetElm && targetElm.nodeType==1 && targetElm.matches('*:hover')){
  280. var style = getComputedStyle(targetElm)
  281. if (style.cursor == 'pointer' && targetElm.textContent) return true;
  282. }
  283. return false;
  284.  
  285.  
  286. }
  287.  
  288.  
  289. var longPressForcedCopy = false;
  290. var selectionChange = null;
  291. var cid_timer = 0;
  292.  
  293. Object.assign($, {
  294.  
  295. lpTimerFunc:function() {
  296. cid_timer = 0;
  297. if (!($.isAnySelection() && selectionChange == null) && checkPointer($.lp_targetElm)) { // NOT pressing the selection area
  298.  
  299. document.documentElement.setAttribute('gykqyzwufxpz', '');
  300. longPressForcedCopy = true;
  301. }
  302. },
  303.  
  304. lpMouseDown: function(evt) {
  305.  
  306. $.lp_targetElm=evt.target;
  307. selectionChange = null;
  308. longPressForcedCopy = false;
  309. //console.log('mousedown')
  310. cid_timer = setTimeout($.lpTimerFunc, 300)
  311.  
  312. },
  313.  
  314. lpMouseUp: function(evt) {
  315. //console.log('mouseup')
  316. if (cid_timer) cid_timer = clearTimeout(cid_timer)
  317. if (longPressForcedCopy) document.documentElement.removeAttribute('gykqyzwufxpz')
  318.  
  319. if (longPressForcedCopy && selectionChange === true) {
  320.  
  321. $.bypass = true;
  322. evt.preventDefault()
  323. evt.stopPropagation();
  324. evt.stopImmediatePropagation();
  325. $.bypass = false;
  326. }
  327.  
  328. setTimeout(function() {
  329. longPressForcedCopy = false;
  330. selectionChange = null;
  331. }, 50);
  332. },
  333. lpClick: function(evt) {
  334.  
  335.  
  336. if (longPressForcedCopy && selectionChange === true) {
  337.  
  338. $.bypass = true;
  339. evt.preventDefault()
  340. evt.stopPropagation();
  341. evt.stopImmediatePropagation();
  342. $.bypass = false;
  343. }
  344.  
  345. longPressForcedCopy = false;
  346. selectionChange = null;
  347. },
  348. lpDragStart: function(evt) {
  349. //console.log('dragstart')
  350. if (cid_timer) cid_timer = clearTimeout(cid_timer)
  351.  
  352.  
  353. if (longPressForcedCopy && $.isAnySelection()) { //drag the selected text
  354.  
  355. document.documentElement.removeAttribute('gykqyzwufxpz')
  356. longPressForcedCopy = false;
  357. } else if (longPressForcedCopy) {
  358.  
  359. $.bypass = true;
  360. evt.preventDefault()
  361. evt.stopPropagation();
  362. evt.stopImmediatePropagation();
  363. $.bypass = false;
  364. }
  365.  
  366. },
  367. lpDragEnd: function(evt) {
  368. //console.log('dragend')
  369. if (cid_timer) cid_timer = clearTimeout(cid_timer)
  370. if (longPressForcedCopy) document.documentElement.removeAttribute('gykqyzwufxpz')
  371. longPressForcedCopy = false;
  372. },
  373. lpSelectionChange: function(evt) {
  374. selectionChange = $.isAnySelection() //first selection change shall be to empty
  375. if (selectionChange === true) {
  376. cid_timer = clearTimeout(cid_timer)
  377. }
  378. }
  379.  
  380. });
  381.  
  382.  
  383. document.addEventListener('mousedown', $.lpMouseDown, {
  384. capture: true,
  385. passive: true
  386. })
  387. document.addEventListener('mouseup', $.lpMouseUp, {
  388. capture: true,
  389. passive: false
  390. })
  391.  
  392. document.addEventListener('click', $.lpClick, {
  393. capture: true,
  394. passive: false
  395. })
  396.  
  397. document.addEventListener('dragstart', $.lpDragStart, {
  398. capture: true,
  399. passive: false
  400. })
  401. document.addEventListener('dragend', $.lpDragEnd, {
  402. capture: true,
  403. passive: true
  404. })
  405.  
  406. document.addEventListener('selectionchange', $.lpSelectionChange, {
  407. passive: true
  408. })
  409.  
  410. }
  411.  
  412. })();