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-14 提交的版本,查看 最新版本

  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.7.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.  
  13. (function $$($) {
  14. 'use strict';
  15. if (document == null || !document.documentElement) return window.requestAnimationFrame($$); // this is tampermonkey bug?? not sure
  16. //console.log('script at', location)
  17.  
  18. function isSupportAdvancedEventListener() {
  19. if ('_b1750' in $) return $._b1750
  20. var prop = 0;
  21. document.createAttribute('z').addEventListener('', null, {
  22. get passive() {
  23. prop++;
  24. },
  25. get once() {
  26. prop++;
  27. }
  28. });
  29. return ($._b1750 = prop == 2);
  30. }
  31.  
  32. var getSelection = window.getSelection || Error()(),
  33. requestAnimationFrame = window.requestAnimationFrame || Error()(),
  34. getComputedStyle = window.getComputedStyle || Error()();
  35.  
  36. $ = {
  37. utSelectionColorHack: 'msmtwejkzrqa',
  38. utTapHighlight: 'xfcklblvkjsj',
  39. utLpSelection: 'gykqyzwufxpz',
  40. ksFuncReplacerNonFalse: '___dqzadwpujtct___',
  41. ksEventReturnValue: ' ___ndjfujndrlsx___',
  42. ksSetData: '___rgqclrdllmhr___',
  43.  
  44. mAlert_DOWN: function() {}, //dummy function in case alert replacement is not valid
  45. mAlert_UP: function() {}, //dummy function in case alert replacement is not valid
  46.  
  47. isAnySelection: function() {
  48. var sel = getSelection();
  49. return !sel ? null : (typeof sel.isCollapsed == 'boolean') ? !sel.isCollapsed : (sel.toString().length > 0);
  50. },
  51.  
  52. createCSSElement: function(cssStyle, container) {
  53. var css = document.createElement('style'); //slope: DOM throughout
  54. css.type = 'text/css';
  55. css.innerHTML = cssStyle;
  56. if (container) container.appendChild(css);
  57. return css;
  58. },
  59.  
  60. createFakeAlert: function(_alert) {
  61. if (typeof _alert != 'function') return null;
  62.  
  63. function alert(msg) {
  64. setTimeout(() => (alert.__isDisabled__() ? console.log("alert msg disabled: ", msg) : _alert.apply(this, arguments)), 9);
  65. };
  66. alert.toString = () => "function alert() { [native code] }";
  67. return alert;
  68. },
  69.  
  70. createFuncReplacer: function(originalFunc, pName, resFX) {
  71. resFX = function(ev) {
  72. var res = originalFunc.apply(this, arguments);
  73. if (!this || this[pName] != resFX) return res; // if this is null or undefined, or this.onXXX is not this function
  74. if (res === false) return; // return undefined when "return false;"
  75. originalFunc[$.ksFuncReplacerNonFalse] = true;
  76. this[pName] = originalFunc; // restore original
  77. return res;
  78. }
  79. resFX.toString = () => originalFunc.toString();
  80. return resFX;
  81. },
  82.  
  83. listenerDisableAll: function(evt) {
  84. var elmNode = evt.target;
  85. while (elmNode && elmNode.nodeType > 0) { //i.e. HTMLDocument or HTMLElement
  86. var pName = 'on' + evt.type
  87. var f = elmNode[pName];
  88. if (typeof f == 'function' && f[$.ksFuncReplacerNonFalse] !== true) {
  89. var nf = $.createFuncReplacer(f, pName);
  90. nf[$.ksFuncReplacerNonFalse] = true;
  91. elmNode[pName] = nf;
  92. }
  93. elmNode = elmNode.parentNode;
  94. }
  95. },
  96.  
  97. onceCssHighlightSelection: () => {
  98. $.onceCssHighlightSelection = null
  99. 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
  100. var elm = !s.length ? document.body : s[s.length >> 1];
  101. var selectionStyle = getComputedStyle(elm, ':selection');
  102. if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(selectionStyle.getPropertyValue('background-color'))) document.documentElement.setAttribute($.utSelectionColorHack, "");
  103. var elmStyle = getComputedStyle(elm)
  104. if (/^rgba\(\d+,\s*\d+,\s*\d+,\s*0\)$/.test(elmStyle.getPropertyValue('-webkit-tap-highlight-color'))) document.documentElement.setAttribute($.utTapHighlight, "");
  105. },
  106.  
  107. isCurrentClipboardDataReplaced: function(clipboardData) {
  108. var items = clipboardData ? clipboardData.items : null;
  109. if (items && items.length > 0) {
  110. for (var i = 0, l = items.length; i < l; i++) {
  111. if (items[i].type == 'text/plain') return true;
  112. }
  113. }
  114. return false;
  115. },
  116.  
  117. replacementSetData: function(_setData, evt) {
  118. if (typeof _setData != 'function') return;
  119.  
  120. function setData() {
  121. var res = _setData.apply(this, arguments);
  122. try {
  123. if (evt.clipboardData === this && this.setData === setData && evt.cancelable && evt.defaultPrevented === false) {
  124. if ($.isCurrentClipboardDataReplaced(evt.clipboardData)) {
  125. evt.preventDefault();
  126. if (evt.defaultPrevented === true) {
  127. this.setData = _setData;
  128. delete this[$.ksSetData];
  129. }
  130. }
  131. }
  132. } catch (e) {}
  133. return res;
  134. }
  135. setData.toString = () => _setData.toString();
  136. evt.clipboardData.setData = setData;
  137. evt.clipboardData[$.ksSetData] = _setData;
  138. },
  139.  
  140.  
  141. enableSelectClickCopy: function() {
  142.  
  143. $.eyEvts = ['keydown', 'keyup', 'copy', 'contextmenu', 'select', 'selectstart', 'dragstart', 'beforecopy']; //slope: throughout
  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.  
  156. if (!('clipboardData' in evt && 'setData' in DataTransfer.prototype)) return true; // Event oncopy not supporting clipboardData
  157. // see the richtext hack in https://www.cleancss.com/css-beautify/
  158. // see https://developer.mozilla.org/zh-CN/docs/Web/API/Element/copy_event
  159. // see https://w3c.github.io/clipboard-apis/#widl-ClipboardEvent-clipboardData
  160.  
  161. if ($.isCurrentClipboardDataReplaced(evt.clipboardData) == false) { //no replacement data
  162. if (!evt.clipboardData[$.ksSetData] && evt.cancelable && evt.defaultPrevented === false) $.replacementSetData(evt.clipboardData.setData, evt);
  163. return true;
  164. }
  165. var trimedSelectionText = getSelection().toString().trim()
  166. if (trimedSelectionText) {
  167. //there is replacement data and the selection is not empty
  168. console.log("copy event - clipboardData replacement is allowed and the selection is not empty", trimedSelectionText)
  169. return false;
  170. } else {
  171. //there is replacement data and the selection is empty
  172. return false;
  173. }
  174. break; // for js formatting only
  175. default:
  176. return true;
  177. }
  178. }
  179.  
  180. Event.prototype.preventDefault = (function(f) {
  181. function preventDefault() {
  182. if (!isDeactivePreventDefault(this)) f.apply(this);
  183. }
  184. preventDefault.toString = () => f.toString();
  185. return preventDefault;
  186. })(Event.prototype.preventDefault);
  187.  
  188. Object.defineProperty(Event.prototype, "returnValue", {
  189. get() {
  190. return $.ksEventReturnValue in this ? this[$.ksEventReturnValue] : true;
  191. },
  192. set(newValue) {
  193. if (!isDeactivePreventDefault(this) && newValue === false) this.preventDefault();
  194. this[$.ksEventReturnValue] = newValue;
  195. },
  196. enumerable: true,
  197. configurable: true
  198. });
  199.  
  200. for (var i = 2, eventsCount = $.eyEvts.length; i < eventsCount; i++) {
  201. document.addEventListener($.eyEvts[i], $.listenerDisableAll, true); // Capture Event; passive:false; expected occurrence COMPLETELY before Target Capture and Target Bubble
  202. }
  203.  
  204. var _alert = window.alert; //slope: temporary
  205. if (typeof _alert == 'function') {
  206. var _mAlert = $.createFakeAlert(_alert);
  207. if (_mAlert) {
  208. var lastClickAt = 0;
  209. _mAlert.__isDisabled__ = () => lastClickAt + 50 > +new Date;
  210. $.mAlert_DOWN = () => (lastClickAt = +new Date);
  211. $.mAlert_UP = () => (lastClickAt = 0);
  212. window.alert = _mAlert
  213. }
  214. }
  215.  
  216. },
  217.  
  218. mainEnableScript: () => {
  219. var cssStyleOnReady = `
  220.  
  221. *, body *, div, span, body *::before, body *::after, *:hover, *:link, *:visited, *:active , *[style], *[class]{
  222. -khtml-user-select: auto !important; -moz-user-select: auto !important; -ms-user-select: auto !important;
  223. -webkit-touch-callout: default !important; -webkit-user-select: auto !important; user-select: auto !important;
  224. }
  225. a:hover, a:hover *{
  226. -khtml-user-select: text !important; -moz-user-select: text !important;-ms-user-select: text !important;
  227. -webkit-user-select: text !important; user-select: text !important;
  228. }
  229. *:hover>img[src]{pointer-events:auto;}
  230.  
  231. [${$.utSelectionColorHack}] :not(input):not(textarea)::selection{ background-color: Highlight !important; color: HighlightText !important;}
  232. [${$.utSelectionColorHack}] :not(input):not(textarea)::-moz-selection{ background-color: Highlight !important; color: HighlightText !important;}
  233. [${$.utTapHighlight}] *{ -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18) !important;}
  234.  
  235. html[${$.utLpSelection}] *:hover, html[${$.utLpSelection}] *:hover * { cursor:text !important;}
  236. html[${$.utLpSelection}] :not(input):not(textarea)::selection {background-color: rgba(255, 156, 179,0.5) !important;}
  237. html[${$.utLpSelection}] :not(input):not(textarea)::-moz-selection {background-color: rgba(255, 156, 179,0.5) !important;}
  238.  
  239. `.trim();
  240.  
  241. $.enableSelectClickCopy()
  242. $.createCSSElement(cssStyleOnReady, document.documentElement);
  243.  
  244. },
  245.  
  246. mainEvents: (listenerPress, listenerRelease) => {
  247. (["mousedown", "click", "dblclick", "contextmenu"]).forEach(function(event) {
  248. document.addEventListener(event, listenerPress, true); // Capture Event; passive:false; ensure the occurrence of 1st capture event COMPLETELY before executing other listeners
  249. });
  250. document.addEventListener("mouseup", listenerRelease, false); // Bubble Event; passive: true/false; order for releasing is insignificant
  251. }
  252.  
  253. }
  254.  
  255. $.mainEnableScript();
  256.  
  257. $.mainEvents(function(evt) {
  258. if ($.onceCssHighlightSelection) window.requestAnimationFrame($.onceCssHighlightSelection);
  259. if (evt.type != "contextmenu" && evt.which != 3) return;
  260. if ($.cid_mouseup > 0) $.cid_mouseup = clearTimeout($.cid_mouseup);
  261. $.mAlert_DOWN();
  262. },
  263. function(evt) {
  264. if (evt.which != 3) return;
  265. $.cid_mouseup = setTimeout($.mAlert_UP, 17);
  266. });
  267.  
  268. console.log('userscript running - To Re-Enable Selection & Copying');
  269.  
  270.  
  271. if (isSupportAdvancedEventListener() && 'assign' in Object) { // this is an optional feature for modern browser
  272.  
  273. Object.assign($, {
  274. lpMouseActive: 0,
  275.  
  276. lpCheckPointer: function(targetElm) {
  277. if (targetElm && targetElm.nodeType == 1 && targetElm.matches('*:hover')) {
  278. if (getComputedStyle(targetElm).getPropertyValue('cursor') == 'pointer' && targetElm.textContent) return true;
  279. }
  280. return false;
  281. },
  282.  
  283. lpFullCancel: function(evt) {
  284. $.bypass = true;
  285. evt.preventDefault()
  286. evt.stopPropagation();
  287. evt.stopImmediatePropagation();
  288. $.bypass = false;
  289. },
  290.  
  291. lpMouseDown: function(evt) {
  292. $.lpMouseActive = 0;
  293. if (evt.altKey && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && $.lpCheckPointer(evt.target)) {
  294. $.lpMouseActive = 1
  295. $.lpFullCancel(evt)
  296. document.documentElement.setAttribute($.utLpSelection, '')
  297. document.addEventListener('mouseup', $.lpMouseUp, {
  298. capture: true,
  299. passive: false,
  300. once: true
  301. })
  302. document.addEventListener('click', $.lpClick, {
  303. capture: true,
  304. passive: false,
  305. once: true
  306. })
  307. }
  308. },
  309.  
  310. lpMouseUp: function(evt) {
  311. if ($.lpMouseActive == 1) {
  312. $.lpMouseActive = 2;
  313. document.documentElement.removeAttribute($.utLpSelection);
  314. $.lpFullCancel(evt)
  315. }
  316. },
  317.  
  318. lpClick: function(evt) {
  319. if ($.lpMouseActive == 2) {
  320. $.lpFullCancel(evt)
  321. }
  322. }
  323. });
  324.  
  325. document.addEventListener('mousedown', $.lpMouseDown, {
  326. capture: true,
  327. passive: true
  328. })
  329.  
  330. }
  331.  
  332. })();