- // ==UserScript==
- // @name 解除网页限制
- // @description 破解禁止复制/剪切/粘贴/选择/右键菜单的网站
- // @namespace http://github.com/rxliuli/userjs
- // @version 2.4.2
- // @author rxliuli
- // @include *
- // @require https://cdn.jsdelivr.net/npm/rx-util@1.9.2/dist/index.min.js
- // @connect userjs.rxliuli.com
- // @run-at document-start
- // @license MIT
- // @grant GM_getValue
- // @grant GM_setValue
- // @grant GM_deleteValue
- // @grant GM_listValues
- // @grant GM_registerMenuCommand
- // @grant GM_addStyle
- // @grant GM_xmlhttpRequest
- // @grant unsafeWindow
- // ==/UserScript==
-
- (function (rxUtil) {
- 'use strict';
-
- //region 公共的函数
-
- const LastUpdateKey = 'LastUpdate';
- const LastValueKey = 'LastValue';
-
- /**
- * 在固定时间周期内只执行函数一次
- * @param {Function} fn 执行的函数
- * @param {Number} time 时间周期
- * @returns {Function} 包装后的函数
- */
- function onceOfCycle(fn, time) {
- const get = window.GM_getValue.bind(window);
- const set = window.GM_setValue.bind(window);
- return new Proxy(fn, {
- apply(_, _this, args) {
- const now = Date.now();
- const last = get(LastUpdateKey);
- if (
- ![null, undefined, 'null', 'undefined'].includes(last ) &&
- now - (last ) < time
- ) {
- return rxUtil.safeExec(() => JSON.parse(get(LastValueKey)), 1)
- }
- return rxUtil.compatibleAsync(Reflect.apply(_, _this, args), (res) => {
- set(LastUpdateKey, now);
- set(LastValueKey, JSON.stringify(res));
- return res
- })
- },
- })
- }
-
- //endregion
-
-
-
-
-
-
-
-
-
-
-
- /**
- * 解除限制
- */
- class UnblockLimit {
- static __initStatic() {this.eventTypes = [
- 'copy',
- 'cut',
- 'paste',
- 'select',
- 'selectstart',
- 'contextmenu',
- 'dragstart',
- 'mousedown',
- ];}
- static __initStatic2() {this.keyEventTypes = [
- 'keydown',
- 'keypress',
- 'keyup',
- ];}
-
- /**
- * 监听 event 的添加
- * 注:必须及早运行
- */
- static watchEventListener() {
- const documentAddEventListener = document.addEventListener;
- const eventTargetAddEventListener = EventTarget.prototype.addEventListener;
-
- const proxyHandler = {
- apply(
- target,
- thisArg,
- [type, listener, useCapture]
-
-
-
- ,
- ) {
- const $addEventListener =
- target instanceof Document
- ? documentAddEventListener
- : eventTargetAddEventListener;
- // 在这里阻止会更合适一点
- if (UnblockLimit.eventTypes.includes(type )) {
- console.log('拦截 addEventListener: ', type, this);
- return
- }
- Reflect.apply($addEventListener, thisArg, [type, listener, useCapture]);
- },
- };
-
- document.addEventListener = new Proxy(
- documentAddEventListener,
- proxyHandler,
- );
- EventTarget.prototype.addEventListener = new Proxy(
- eventTargetAddEventListener,
- proxyHandler,
- );
- }
-
- // 代理网页添加的键盘快捷键,阻止自定义 C-C/C-V/C-X 这三个快捷键
- static proxyKeyEventListener() {
- const documentAddEventListener = document.addEventListener;
- const eventTargetAddEventListener = EventTarget.prototype.addEventListener;
-
- const keyProxyHandler = {
- apply(
- target,
- thisArg,
- argArray,
- ) {
- const ev = argArray[0];
- const proxyKey = ['c', 'x', 'v', 'a'];
- const proxyAssistKey = ['Control', 'Alt'];
- if (
- (ev.ctrlKey && proxyKey.includes(ev.key)) ||
- proxyAssistKey.includes(ev.key)
- ) {
- console.log('已阻止: ', ev.ctrlKey, ev.altKey, ev.key);
- return
- }
- if (ev.altKey) {
- return
- }
- Reflect.apply(target, thisArg, argArray);
- },
- };
-
- const proxyHandler = {
- apply(
- target,
- thisArg,
- [type, listener, useCapture]
-
-
-
- ,
- ) {
- const $addEventListener =
- target instanceof Document
- ? documentAddEventListener
- : eventTargetAddEventListener;
- Reflect.apply($addEventListener, thisArg, [
- type,
- UnblockLimit.keyEventTypes.includes(type )
- ? new Proxy(listener , keyProxyHandler)
- : listener,
- useCapture,
- ]);
- },
- };
-
- document.addEventListener = new Proxy(
- documentAddEventListener,
- proxyHandler,
- );
- EventTarget.prototype.addEventListener = new Proxy(
- eventTargetAddEventListener,
- proxyHandler,
- );
- }
-
- // 清理使用 onXXX 添加到事件
- static clearJsOnXXXEvent() {
- const emptyFunc = () => {};
-
- function modifyPrototype(
- prototype,
- type,
- ) {
- Object.defineProperty(prototype, `on${type}`, {
- get() {
- return emptyFunc
- },
- set() {
- return true
- },
- });
- }
-
- UnblockLimit.eventTypes.forEach((type) => {
- modifyPrototype(HTMLElement.prototype, type);
- modifyPrototype(document, type);
- });
- }
-
- // 清理或还原DOM节点的onXXX 属性
- static clearDomOnXXXEvent() {
- function _innerClear() {
- UnblockLimit.eventTypes.forEach((type) => {
- document
- .querySelectorAll(`[on${type}]`)
- .forEach((el) => el.setAttribute(`on${type}`, 'return true'));
- });
- }
-
- setInterval(_innerClear, 3000);
- }
-
- // 清理掉网页添加的全局防止复制/选择的 CSS
- static clearCSS() {
- GM_addStyle(
- `html, body, div, span, applet, object, iframe,
- h1, h2, h3, h4, h5, h6, p, blockquote, pre,
- a, abbr, acronym, address, big, cite, code,
- del, dfn, em, img, ins, kbd, q, s, samp,
- small, strike, strong, sub, sup, tt, var,
- b, u, i, center,
- dl, dt, dd, ol, ul, li,
- fieldset, form, label, legend,
- table, caption, tbody, tfoot, thead, tr, th, td,
- article, aside, canvas, details, embed,
- figure, figcaption, footer, header, hgroup,
- menu, nav, output, ruby, section, summary,
- time, mark, audio, video, html body * {
- -webkit-user-select: text !important;
- -moz-user-select: text !important;
- user-select: text !important;
- }
-
- ::-moz-selection {
- color: #111 !important;
- background: #05d3f9 !important;
- }
-
- ::selection {
- color: #111 !important;
- background: #05d3f9 !important;
- }
- `,
- );
- }
- } UnblockLimit.__initStatic(); UnblockLimit.__initStatic2();
-
- /**
- * 屏蔽配置项类型
- */
-
-
-
-
-
-
-
- //更新屏蔽列表
- class BlockHost {
- static fetchHostList() {
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: 'https://userjs.rxliuli.com/blockList.json',
- headers: {
- 'Cache-Control': 'no-cache',
- },
- onload(res) {
- const list = JSON.parse(res.responseText);
- resolve(list);
- console.info('更新配置成功: ', list);
- },
- onerror(e) {
- reject(e);
- console.error('更新配置失败: ', e);
- },
- });
- })
- }
-
- static updateHostList(hostList) {
- hostList
- .filter((config) => GM_getValue(JSON.stringify(config)) === undefined)
- .forEach((domain) => {
- console.log('更新了屏蔽域名: ', domain);
- GM_setValue(JSON.stringify(domain), true);
- });
- }
- // 更新支持的网站列表
- static __initStatic3() {this.updateBlockHostList = onceOfCycle(async () => {
- BlockHost.updateHostList(await BlockHost.fetchHostList());
- }, 1000 * 60 * 60 * 24);}
-
- static findKey() {
- return GM_listValues()
- .filter((config) => GM_getValue(config))
- .find((configStr) => {
- const config = rxUtil.safeExec(() => JSON.parse(configStr) , {
- type: 'domain',
- url: configStr,
- });
- return this.match(new URL(location.href), config)
- })
- }
-
- static match(
- href,
- config
-
- ,
- ) {
- if (typeof config === 'string') {
- return href.host.includes(config)
- } else {
- const { type, url } = config;
- switch (type) {
- case 'domain':
- return href.host === url
- case 'link':
- return href.href === url
- case 'linkPrefix':
- return href.href.startsWith(url)
- case 'regex':
- return new RegExp(url).test(href.href)
- }
- }
- }
- } BlockHost.__initStatic3();
-
- //注册(不可用)菜单
- class MenuHandler {
- static register() {
- const findKey = BlockHost.findKey();
- const key =
- findKey ||
- JSON.stringify({
- type: 'domain',
- url: location.host,
- });
- console.log('key: ', key);
- GM_registerMenuCommand(findKey ? '恢复默认' : '解除限制', () => {
- GM_setValue(key, !GM_getValue(key));
- console.log('isBlock: ', key, GM_getValue(key));
- location.reload();
- });
- }
- }
-
- /**
- * 屏蔽列表配置 API,用以在指定 API 进行高级配置
- */
- class ConfigBlockApi {
- listKey() {
- return GM_listValues().filter(
- (key) => ![LastUpdateKey, LastValueKey].includes(key),
- )
- }
- list() {
- return this.listKey().map((config) => ({
- ...rxUtil.safeExec(() => JSON.parse(config ), {
- type: 'domain',
- url: config,
- } ),
- enable: GM_getValue(config),
- key: config,
- }))
- }
- switch(key) {
- console.log('ConfigBlockApi.switch: ', key);
- GM_setValue(key, !GM_getValue(key));
- }
- remove(key) {
- console.log('ConfigBlockApi.remove: ', key);
- GM_deleteValue(key);
- }
- add(config) {
- console.log('ConfigBlockApi.add: ', config);
- GM_setValue(JSON.stringify(config), true);
- }
- clear() {
- const delKeyList = this.listKey();
- console.log('ConfigBlockApi.clear: ', delKeyList);
- delKeyList.forEach(GM_deleteValue);
- }
- async update() {
- await BlockHost.updateHostList(await BlockHost.fetchHostList());
- }
- }
-
- //启动类
- class Application {
- start() {
- MenuHandler.register();
- if (BlockHost.findKey()) {
- UnblockLimit.watchEventListener();
- UnblockLimit.proxyKeyEventListener();
- UnblockLimit.clearJsOnXXXEvent();
- }
- BlockHost.updateBlockHostList();
- window.addEventListener('load', function () {
- if (BlockHost.findKey()) {
- UnblockLimit.clearDomOnXXXEvent();
- UnblockLimit.clearCSS();
- }
- });
- if (
- location.href.startsWith('https://userjs.rxliuli.com/') ||
- location.hostname === '127.0.0.1'
- ) {
- Reflect.set(
- unsafeWindow,
- 'com.rxliuli.UnblockWebRestrictions.configBlockApi',
- new ConfigBlockApi(),
- );
- }
- }
- }
-
- new Application().start();
-
- }(rx));