解除网页限制

破解禁止复制/剪切/粘贴/选择/右键菜单的网站

  1. // ==UserScript==
  2. // @name 解除网页限制
  3. // @description 破解禁止复制/剪切/粘贴/选择/右键菜单的网站
  4. // @namespace http://github.com/rxliuli/userjs
  5. // @version 2.4.2
  6. // @author rxliuli
  7. // @include *
  8. // @require https://cdn.jsdelivr.net/npm/rx-util@1.9.2/dist/index.min.js
  9. // @connect userjs.rxliuli.com
  10. // @run-at document-start
  11. // @license MIT
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_deleteValue
  15. // @grant GM_listValues
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_addStyle
  18. // @grant GM_xmlhttpRequest
  19. // @grant unsafeWindow
  20. // ==/UserScript==
  21.  
  22. (function (rxUtil) {
  23. 'use strict';
  24.  
  25. //region 公共的函数
  26.  
  27. const LastUpdateKey = 'LastUpdate';
  28. const LastValueKey = 'LastValue';
  29.  
  30. /**
  31. * 在固定时间周期内只执行函数一次
  32. * @param {Function} fn 执行的函数
  33. * @param {Number} time 时间周期
  34. * @returns {Function} 包装后的函数
  35. */
  36. function onceOfCycle(fn, time) {
  37. const get = window.GM_getValue.bind(window);
  38. const set = window.GM_setValue.bind(window);
  39. return new Proxy(fn, {
  40. apply(_, _this, args) {
  41. const now = Date.now();
  42. const last = get(LastUpdateKey);
  43. if (
  44. ![null, undefined, 'null', 'undefined'].includes(last ) &&
  45. now - (last ) < time
  46. ) {
  47. return rxUtil.safeExec(() => JSON.parse(get(LastValueKey)), 1)
  48. }
  49. return rxUtil.compatibleAsync(Reflect.apply(_, _this, args), (res) => {
  50. set(LastUpdateKey, now);
  51. set(LastValueKey, JSON.stringify(res));
  52. return res
  53. })
  54. },
  55. })
  56. }
  57.  
  58. //endregion
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69.  
  70. /**
  71. * 解除限制
  72. */
  73. class UnblockLimit {
  74. static __initStatic() {this.eventTypes = [
  75. 'copy',
  76. 'cut',
  77. 'paste',
  78. 'select',
  79. 'selectstart',
  80. 'contextmenu',
  81. 'dragstart',
  82. 'mousedown',
  83. ];}
  84. static __initStatic2() {this.keyEventTypes = [
  85. 'keydown',
  86. 'keypress',
  87. 'keyup',
  88. ];}
  89.  
  90. /**
  91. * 监听 event 的添加
  92. * 注:必须及早运行
  93. */
  94. static watchEventListener() {
  95. const documentAddEventListener = document.addEventListener;
  96. const eventTargetAddEventListener = EventTarget.prototype.addEventListener;
  97.  
  98. const proxyHandler = {
  99. apply(
  100. target,
  101. thisArg,
  102. [type, listener, useCapture]
  103.  
  104.  
  105.  
  106. ,
  107. ) {
  108. const $addEventListener =
  109. target instanceof Document
  110. ? documentAddEventListener
  111. : eventTargetAddEventListener;
  112. // 在这里阻止会更合适一点
  113. if (UnblockLimit.eventTypes.includes(type )) {
  114. console.log('拦截 addEventListener: ', type, this);
  115. return
  116. }
  117. Reflect.apply($addEventListener, thisArg, [type, listener, useCapture]);
  118. },
  119. };
  120.  
  121. document.addEventListener = new Proxy(
  122. documentAddEventListener,
  123. proxyHandler,
  124. );
  125. EventTarget.prototype.addEventListener = new Proxy(
  126. eventTargetAddEventListener,
  127. proxyHandler,
  128. );
  129. }
  130.  
  131. // 代理网页添加的键盘快捷键,阻止自定义 C-C/C-V/C-X 这三个快捷键
  132. static proxyKeyEventListener() {
  133. const documentAddEventListener = document.addEventListener;
  134. const eventTargetAddEventListener = EventTarget.prototype.addEventListener;
  135.  
  136. const keyProxyHandler = {
  137. apply(
  138. target,
  139. thisArg,
  140. argArray,
  141. ) {
  142. const ev = argArray[0];
  143. const proxyKey = ['c', 'x', 'v', 'a'];
  144. const proxyAssistKey = ['Control', 'Alt'];
  145. if (
  146. (ev.ctrlKey && proxyKey.includes(ev.key)) ||
  147. proxyAssistKey.includes(ev.key)
  148. ) {
  149. console.log('已阻止: ', ev.ctrlKey, ev.altKey, ev.key);
  150. return
  151. }
  152. if (ev.altKey) {
  153. return
  154. }
  155. Reflect.apply(target, thisArg, argArray);
  156. },
  157. };
  158.  
  159. const proxyHandler = {
  160. apply(
  161. target,
  162. thisArg,
  163. [type, listener, useCapture]
  164.  
  165.  
  166.  
  167. ,
  168. ) {
  169. const $addEventListener =
  170. target instanceof Document
  171. ? documentAddEventListener
  172. : eventTargetAddEventListener;
  173. Reflect.apply($addEventListener, thisArg, [
  174. type,
  175. UnblockLimit.keyEventTypes.includes(type )
  176. ? new Proxy(listener , keyProxyHandler)
  177. : listener,
  178. useCapture,
  179. ]);
  180. },
  181. };
  182.  
  183. document.addEventListener = new Proxy(
  184. documentAddEventListener,
  185. proxyHandler,
  186. );
  187. EventTarget.prototype.addEventListener = new Proxy(
  188. eventTargetAddEventListener,
  189. proxyHandler,
  190. );
  191. }
  192.  
  193. // 清理使用 onXXX 添加到事件
  194. static clearJsOnXXXEvent() {
  195. const emptyFunc = () => {};
  196.  
  197. function modifyPrototype(
  198. prototype,
  199. type,
  200. ) {
  201. Object.defineProperty(prototype, `on${type}`, {
  202. get() {
  203. return emptyFunc
  204. },
  205. set() {
  206. return true
  207. },
  208. });
  209. }
  210.  
  211. UnblockLimit.eventTypes.forEach((type) => {
  212. modifyPrototype(HTMLElement.prototype, type);
  213. modifyPrototype(document, type);
  214. });
  215. }
  216.  
  217. // 清理或还原DOM节点的onXXX 属性
  218. static clearDomOnXXXEvent() {
  219. function _innerClear() {
  220. UnblockLimit.eventTypes.forEach((type) => {
  221. document
  222. .querySelectorAll(`[on${type}]`)
  223. .forEach((el) => el.setAttribute(`on${type}`, 'return true'));
  224. });
  225. }
  226.  
  227. setInterval(_innerClear, 3000);
  228. }
  229.  
  230. // 清理掉网页添加的全局防止复制/选择的 CSS
  231. static clearCSS() {
  232. GM_addStyle(
  233. `html, body, div, span, applet, object, iframe,
  234. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  235. a, abbr, acronym, address, big, cite, code,
  236. del, dfn, em, img, ins, kbd, q, s, samp,
  237. small, strike, strong, sub, sup, tt, var,
  238. b, u, i, center,
  239. dl, dt, dd, ol, ul, li,
  240. fieldset, form, label, legend,
  241. table, caption, tbody, tfoot, thead, tr, th, td,
  242. article, aside, canvas, details, embed,
  243. figure, figcaption, footer, header, hgroup,
  244. menu, nav, output, ruby, section, summary,
  245. time, mark, audio, video, html body * {
  246. -webkit-user-select: text !important;
  247. -moz-user-select: text !important;
  248. user-select: text !important;
  249. }
  250.  
  251. ::-moz-selection {
  252. color: #111 !important;
  253. background: #05d3f9 !important;
  254. }
  255.  
  256. ::selection {
  257. color: #111 !important;
  258. background: #05d3f9 !important;
  259. }
  260. `,
  261. );
  262. }
  263. } UnblockLimit.__initStatic(); UnblockLimit.__initStatic2();
  264.  
  265. /**
  266. * 屏蔽配置项类型
  267. */
  268.  
  269.  
  270.  
  271.  
  272.  
  273.  
  274.  
  275. //更新屏蔽列表
  276. class BlockHost {
  277. static fetchHostList() {
  278. return new Promise((resolve, reject) => {
  279. GM_xmlhttpRequest({
  280. method: 'GET',
  281. url: 'https://userjs.rxliuli.com/blockList.json',
  282. headers: {
  283. 'Cache-Control': 'no-cache',
  284. },
  285. onload(res) {
  286. const list = JSON.parse(res.responseText);
  287. resolve(list);
  288. console.info('更新配置成功: ', list);
  289. },
  290. onerror(e) {
  291. reject(e);
  292. console.error('更新配置失败: ', e);
  293. },
  294. });
  295. })
  296. }
  297.  
  298. static updateHostList(hostList) {
  299. hostList
  300. .filter((config) => GM_getValue(JSON.stringify(config)) === undefined)
  301. .forEach((domain) => {
  302. console.log('更新了屏蔽域名: ', domain);
  303. GM_setValue(JSON.stringify(domain), true);
  304. });
  305. }
  306. // 更新支持的网站列表
  307. static __initStatic3() {this.updateBlockHostList = onceOfCycle(async () => {
  308. BlockHost.updateHostList(await BlockHost.fetchHostList());
  309. }, 1000 * 60 * 60 * 24);}
  310.  
  311. static findKey() {
  312. return GM_listValues()
  313. .filter((config) => GM_getValue(config))
  314. .find((configStr) => {
  315. const config = rxUtil.safeExec(() => JSON.parse(configStr) , {
  316. type: 'domain',
  317. url: configStr,
  318. });
  319. return this.match(new URL(location.href), config)
  320. })
  321. }
  322.  
  323. static match(
  324. href,
  325. config
  326.  
  327. ,
  328. ) {
  329. if (typeof config === 'string') {
  330. return href.host.includes(config)
  331. } else {
  332. const { type, url } = config;
  333. switch (type) {
  334. case 'domain':
  335. return href.host === url
  336. case 'link':
  337. return href.href === url
  338. case 'linkPrefix':
  339. return href.href.startsWith(url)
  340. case 'regex':
  341. return new RegExp(url).test(href.href)
  342. }
  343. }
  344. }
  345. } BlockHost.__initStatic3();
  346.  
  347. //注册(不可用)菜单
  348. class MenuHandler {
  349. static register() {
  350. const findKey = BlockHost.findKey();
  351. const key =
  352. findKey ||
  353. JSON.stringify({
  354. type: 'domain',
  355. url: location.host,
  356. });
  357. console.log('key: ', key);
  358. GM_registerMenuCommand(findKey ? '恢复默认' : '解除限制', () => {
  359. GM_setValue(key, !GM_getValue(key));
  360. console.log('isBlock: ', key, GM_getValue(key));
  361. location.reload();
  362. });
  363. }
  364. }
  365.  
  366. /**
  367. * 屏蔽列表配置 API,用以在指定 API 进行高级配置
  368. */
  369. class ConfigBlockApi {
  370. listKey() {
  371. return GM_listValues().filter(
  372. (key) => ![LastUpdateKey, LastValueKey].includes(key),
  373. )
  374. }
  375. list() {
  376. return this.listKey().map((config) => ({
  377. ...rxUtil.safeExec(() => JSON.parse(config ), {
  378. type: 'domain',
  379. url: config,
  380. } ),
  381. enable: GM_getValue(config),
  382. key: config,
  383. }))
  384. }
  385. switch(key) {
  386. console.log('ConfigBlockApi.switch: ', key);
  387. GM_setValue(key, !GM_getValue(key));
  388. }
  389. remove(key) {
  390. console.log('ConfigBlockApi.remove: ', key);
  391. GM_deleteValue(key);
  392. }
  393. add(config) {
  394. console.log('ConfigBlockApi.add: ', config);
  395. GM_setValue(JSON.stringify(config), true);
  396. }
  397. clear() {
  398. const delKeyList = this.listKey();
  399. console.log('ConfigBlockApi.clear: ', delKeyList);
  400. delKeyList.forEach(GM_deleteValue);
  401. }
  402. async update() {
  403. await BlockHost.updateHostList(await BlockHost.fetchHostList());
  404. }
  405. }
  406.  
  407. //启动类
  408. class Application {
  409. start() {
  410. MenuHandler.register();
  411. if (BlockHost.findKey()) {
  412. UnblockLimit.watchEventListener();
  413. UnblockLimit.proxyKeyEventListener();
  414. UnblockLimit.clearJsOnXXXEvent();
  415. }
  416. BlockHost.updateBlockHostList();
  417. window.addEventListener('load', function () {
  418. if (BlockHost.findKey()) {
  419. UnblockLimit.clearDomOnXXXEvent();
  420. UnblockLimit.clearCSS();
  421. }
  422. });
  423. if (
  424. location.href.startsWith('https://userjs.rxliuli.com/') ||
  425. location.hostname === '127.0.0.1'
  426. ) {
  427. Reflect.set(
  428. unsafeWindow,
  429. 'com.rxliuli.UnblockWebRestrictions.configBlockApi',
  430. new ConfigBlockApi(),
  431. );
  432. }
  433. }
  434. }
  435.  
  436. new Application().start();
  437.  
  438. }(rx));

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址