Unlimit-Web

解除网页限制: 恢复文本的选中和复制, 过滤文本小尾巴, 恢复右键菜单. Remove webpage restrictions: restore the selection and copy of text, clear the text tail, and restore the right-click menu.

目前為 2025-02-18 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Unlimit-Web
  3. // @description 解除网页限制: 恢复文本的选中和复制, 过滤文本小尾巴, 恢复右键菜单. Remove webpage restrictions: restore the selection and copy of text, clear the text tail, and restore the right-click menu.
  4. // @version 17.0
  5. // @author xcanwin
  6. // @namespace https://github.com/xcanwin/Unlimit-Web/
  7. // @supportURL https://github.com/xcanwin/Unlimit-Web/
  8. // @icon data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" stroke-width="2" fill="none" stroke="currentColor"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
  9. // @license GPL-2.0-only
  10. // @match *://www.zhihu.com/*
  11. // @match *://blog.csdn.net/*
  12. // @match *://www.bilibili.com/*
  13. // @match *://www.cnblogs.com/*
  14. // @match *://www.360doc.com/*
  15. // @match *://blog.51cto.com/*
  16. // @match *://guofeng.yuedu.163.com/*
  17. // @match *://www.kuwo.cn/*
  18. // @match *://chuangshi.qq.com/*
  19. // @match *://read.qidian.com/*
  20. // @match *://dafrok.github.io/*
  21. // @match *://shushan.zhangyue.net/*
  22. // @match *://aqistudy.cn/*
  23. // @match *://www.xuexila.com/*
  24. // @match *://www.51test.net/*
  25. // @match *://www.laokaoya.com/*
  26. // @match *://utaten.com/*
  27. // @match *://book.qq.com/*
  28. // @match *://doc.mbalib.com/*
  29. // @match *://www.oh100.com/*
  30. // @match *://51test.net/*
  31. // @match *://www.cspengbo.com/*
  32. // @match *://www.diyifanwen.com/*
  33. // @match *://www.ahsrst.cn/*
  34. // @match *://kt250.com/*
  35. // @match *://*/*
  36. // @grant GM_getValue
  37. // @grant GM_setValue
  38. // @grant GM_registerMenuCommand
  39. // @grant GM_unregisterMenuCommand
  40. // @grant GM_addStyle
  41. // @run-at document-end
  42. // ==/UserScript==
  43.  
  44. (function() {
  45. 'use strict';
  46.  
  47. const $ = (Selector, el) => (el || document).querySelector(Selector);
  48. const $$ = (Selector, el) => (el || document).querySelectorAll(Selector);
  49.  
  50. const muob = (Selector, el, func) => {
  51. const observer = new MutationObserver((mutationsList, observer2) => {
  52. for (let mutation of mutationsList) {
  53. if (mutation.type === 'childList') {
  54. const target = mutation.target.querySelector(Selector);
  55. if (target && !target.hasAttribute('data-duplicate')) {
  56. target.setAttribute('data-duplicate', 'true');
  57. func(target);
  58. }
  59. }
  60. }
  61. });
  62. observer.observe(el, {
  63. childList: true,
  64. subtree: true
  65. });
  66. };
  67.  
  68. /*黑名单: 需解除限制*/
  69. const block_list = {
  70. // 域名
  71. domain: {
  72. // 初始化,首次安装插件时使用此列表,之后使用插件存储的列表
  73. init: ["www.zhihu.com", "blog.csdn.net","www.bilibili.com","www.cnblogs.com","www.360doc.com","blog.51cto.com","guofeng.yuedu.163.com","www.kuwo.cn","chuangshi.qq.com","read.qidian.com","dafrok.github.io","shushan.zhangyue.net","aqistudy.cn","www.xuexila.com","www.51test.net","www.laokaoya.com","utaten.com","book.qq.com","doc.mbalib.com","www.oh100.com","51test.net","www.cspengbo.com","www.diyifanwen.com","www.ahsrst.cn","kt250.com"],
  74. // 硬编码,除了使用插件存储的列表,每次也会使用此硬编码列表
  75. hard: [],
  76. },
  77. };
  78.  
  79. /*白名单: 指的是放行,无需解除限制*/
  80. const allow_list = {
  81. // 网页元素名称
  82. element: ['script', 'style', 'video'],
  83. // 网页元素id
  84. id: ['video'],
  85. // 网页元素className
  86. className: ['video'],
  87. };
  88.  
  89. const symbol = ["❎", "✅"];
  90. const symbol2 = ["未勾选", "已勾选"];
  91. let mc = [];
  92.  
  93. const sv = (key, value = "") => {
  94. GM_setValue(key, value);
  95. };
  96.  
  97. const gv = (key, value = "") => {
  98. return GM_getValue(key, value);
  99. };
  100.  
  101. const purify_style = `
  102. .unslcl {
  103. /* 浅色模式下的文本选中样式 */
  104. @media (prefers-color-scheme: light) {
  105. :not(foo):not(bar):not(baz):not(qux)::selection {
  106. background-color: #007BFF !important;
  107. color: white !important;
  108. }
  109. }
  110.  
  111. /* 深色模式下的文本选中样式 */
  112. @media (prefers-color-scheme: dark) {
  113. :not(foo):not(bar):not(baz):not(qux)::selection {
  114. background-color: #5DACDD !important;
  115. color: black !important;
  116. }
  117. }
  118. }
  119. `;
  120.  
  121. /*枚举网页元素*/
  122. const eNumUnLimit = (EL = document) => {
  123. $('html').classList.add('unslcl');
  124. $$("*", EL).forEach(unLimit);
  125. try {
  126. console.clear = () => {};
  127. window.debugger = () => {};
  128. } catch (e) {
  129. }
  130. };
  131.  
  132. /*判断是否包含*/
  133. const isIn = (el, list, type) => {
  134. /*
  135. 例如 'video' 包含于 ['hello', 'video']
  136. 例如 'video' 模糊包含于 ['hello', 'good_player_top']
  137. 例如 'good_video_top' 特殊包含于 ['hello', 'video']
  138. */
  139. switch (type) {
  140. case 'fuzzy': // 模糊包含
  141. return list.some(item => item === el || item.includes(el));
  142. case 'fancy': // 特殊包含
  143. return list.some(item => item === el || el.includes(item));
  144. default: // 正常包含
  145. return list.some(item => item === el);
  146. }
  147. };
  148.  
  149. /*解除限制*/
  150. const unLimit = (el = null) => {
  151. if (
  152. isIn(el.nodeName.toLowerCase(), allow_list.element)
  153. || isIn(el.id?.toString().toLowerCase(), allow_list.id, 'fancy')
  154. || isIn(el.className?.toString().toLowerCase(), allow_list.className, 'fancy')
  155. ) return;
  156.  
  157. [
  158. "user-select", "-webkit-user-select", "-moz-user-select", "-ms-user-select", "-khtml-user-select",
  159. ].forEach(xcanwin => {
  160. const ec = el.childNodes;
  161. const j1 = ec && ec.length == 1 && ec[0] && ec[0].nodeType && ec[0].nodeType == 3;
  162. const style = document.defaultView.getComputedStyle(el, null)[xcanwin];
  163. const j2 = style && style != 'auto';
  164. if (j1 || j2){
  165. // 处理第一个子标签是text类型的标签 或者 处理select值被修改过的标签
  166. el.style.setProperty(xcanwin, "unset", "important");
  167. }
  168. });
  169.  
  170. [
  171. "onselect", "onselectstart", "onselectionchange",
  172. "oncopy", "onbeforecopy",
  173. "onpaste", "onbeforepaste", "oncut", "onbeforecut",
  174. "onpointercancel", "onpointerdown", "onpointerenter", "onpointerleave", "onpointerlockchange", "onpointerlockerror", "onpointermove", "onpointerout", "onpointerover", "onpointerrawupdate", "onpointerup",
  175. ].forEach(xcanwin => {
  176. el[xcanwin] = e => {
  177. // 处理能影响文本的事件
  178. e.stopImmediatePropagation();
  179. }
  180. });
  181.  
  182. [
  183. "onmouseenter", "onmousedown", "onmouseup", "onmouseout", "onmouseleave", "onmouseover",
  184. ].forEach(xcanwin => {
  185. el[xcanwin] = e => {
  186. if ([ "P" ].indexOf(e.target.nodeName) >=0 && e.button == 0) {
  187. // 处理单击左键和滑动左键下的html文本标签
  188. e.stopImmediatePropagation();
  189. }
  190. }
  191. });
  192.  
  193. [
  194. "onkeypress", "onkeyup", "onkeydown",
  195. ].forEach(xcanwin => {
  196. el[xcanwin] = e => {
  197. const keyCode = e.keyCode || e.which || e.charCode;
  198. const ctrlKey = e.ctrlKey || e.metaKey;
  199. if ((ctrlKey && keyCode == 67) || keyCode == 123) {
  200. // 处理ctrl+c和F12
  201. e.stopImmediatePropagation();
  202. }
  203. }
  204. });
  205.  
  206. [
  207. "oncontextmenu",
  208. ].forEach(xcanwin => {
  209. el[xcanwin] = e => {
  210. if (e.target && e.target.points == undefined){
  211. // 处理普通的单击右键,跳过滑动右键
  212. e.stopImmediatePropagation();
  213. }
  214. }
  215. });
  216. };
  217.  
  218. /*加入自动破解列表*/
  219. const switchAuto = (domain) => {
  220. let autolist = JSON.parse(gv("ul_autolist", "[]"));
  221. domain = domain ? domain : getdomain();
  222. if (isIn(domain, autolist)) {
  223. autolist = autolist.filter(el => el !== domain);
  224. } else {
  225. autolist.push(domain);
  226. }
  227. sv("ul_autolist", JSON.stringify(autolist));
  228. rmc();
  229. eNumUnLimit();
  230. };
  231.  
  232. /*查看自动破解列表*/
  233. const showAuto = () => {
  234. prompt("自动破解列表", gv("ul_autolist", "[]"));
  235. };
  236.  
  237. /*初始化自动破解列表*/
  238. const initAutoList = () => {
  239. const init = block_list.domain.init;
  240. //为空或者为[]时,说明首次运行,进行初始化
  241. if (gv("ul_autolist", "[]") === "[]") {
  242. sv("ul_autolist", JSON.stringify(init));
  243. }
  244. //解析移除时,进行初始化
  245. try {
  246. JSON.parse(gv("ul_autolist", "[]"));
  247. } catch (e) {
  248. sv("ul_autolist", JSON.stringify(init));
  249. }
  250. };
  251.  
  252. /*取消注册菜单*/
  253. const unrmc = () => {
  254. mc.forEach(x => GM_unregisterMenuCommand(x));
  255. };
  256.  
  257. /*注册菜单*/
  258. const rmc = () => {
  259. unrmc();
  260. let isauto;
  261. const autolist = JSON.parse(gv("ul_autolist", "[]"));
  262. const domain = getdomain();
  263. if (isIn(domain, autolist.concat(block_list.domain.hard))) {
  264. isauto = 1;
  265. } else {
  266. isauto = 0;
  267. }
  268. mc.push(GM_registerMenuCommand(`查看自动破解列表`, () => showAuto()));
  269. mc.push(GM_registerMenuCommand(`临时破解:${domain}`, () => eNumUnLimit()));
  270. mc.push(GM_registerMenuCommand(`自动破解:${domain} ${symbol[isauto]}${symbol2[isauto]}`, () => switchAuto(domain)));
  271. };
  272.  
  273. const getdomain = () => {
  274. return (new URL(location.href)).hostname;
  275. };
  276.  
  277. const main = () => {
  278. initAutoList();
  279. rmc();
  280. const autolist = JSON.parse(gv("ul_autolist", "[]"));
  281. const domain = getdomain();
  282. if (isIn(domain, autolist.concat(block_list.domain.hard))) {
  283. eNumUnLimit();
  284. setInterval(() => eNumUnLimit(), 3000);
  285. muob(`*`, $(`body`), unLimit);
  286. }
  287. };
  288.  
  289. GM_addStyle(purify_style);
  290. main();
  291.  
  292. })();