返回首頁 

Webpage mask

A customizable mask layer above any webpage. You can use it as a privacy mask, a screensaver, a nightmode filter... and so on.


Install this script?
// ==UserScript==// @name               Webpage mask// @name:zh-CN         网页遮罩层// @name:en            Webpage mask// @namespace          https://greasyfork.org/users/667968-pyudng// @version            0.4.2// @description        A customizable mask layer above any webpage. You can use it as a privacy mask, a screensaver, a nightmode filter... and so on.// @description:zh-CN  在网页上方添加一个可以自定义的遮罩层。可以用来遮挡隐私内容,或者用作屏保,又或是用来设置护眼模式... 等等等等// @description:en     A customizable mask layer above any webpage. You can use it as a privacy mask, a screensaver, a nightmode filter... and so on.// @author             PY-DNG// @license            MIT// @match              http*://*/*// @require            https://update.greasyfork.org/scripts/456034/1532680/Basic%20Functions%20%28For%20userscripts%29.js// @grant              GM_registerMenuCommand// @grant              GM_getValue// @grant              GM_setValue// @grant              GM_addElement// @run-at             document-start// ==/UserScript==/* eslint-disable no-multi-spaces *//* eslint-disable no-return-assign *//* global LogLevel DoLog Err $ $All $CrE $AEL $$CrE addStyle detectDom destroyEvent copyProp copyProps parseArgs escJsStr replaceText getUrlArgv dl_browser dl_GM AsyncManager queueTask FunctionLoader loadFuncs require isLoaded *//* Important note: this script is for convenience, but is NOT a security tool.ANYONE with basic web programming knowledge CAN EASYILY UNLOCK/UNCENSOR/REMOVE MASKwithout permission/password AND EVEN YOU CANNOT KNOW IT */(function __MAIN__() {'use strict';const CONST = {TextAllLang: {DEFAULT: 'en','zh-CN': {CompatAlert: '用户脚本 [网页遮罩层] 提示:\n(本提示仅展示一次)本脚本推荐使用最新版Tampermonkey运行,如果使用旧版Tampermonkey或其他脚本管理器可能导致兼容性问题,请注意。',Mask: '开启',Unmask: '关闭',EnableAutomask: '为此网站开启自动遮罩',DisableAutomask: '关闭此网站的自动遮罩',SetIDLETime: '设置自动遮罩触发时间',PromptIDLETime: '每当 N 秒无操作后,将为网站自动开启遮罩\n您希望 N 为:',TamperorilyDisable: '暂时禁用遮罩层',TamperorilyDisabled: '已暂时禁用遮罩层:当前网页在下一次刷新前,都不会展示遮罩层',CustomUserstyle: '自定义遮罩层样式',PromptUserstyle: '您可以在此彻底地自定义遮罩层\n如果您不确定怎么写或者不小心写错了,留空并点击确定即可重置为默认值\n\n格式:\ncss:CSS值 - 设定自定义CSS样式\nimg:网址 - 在遮罩层上全屏显示网址对应的图片\njs:代码 - 执行自定义js代码,您可以使用js:debugger测试运行环境、调试您的代码',IDLETimeInvalid: '您的输入不正确:只能输入大于等于零的整数或小数'},'en': {CompatAlert: '(This is a one-time alert)\nFrom userscript [Privacy mask]:\nThis userscript is designed for latest versions of Tampermonkey, working with old versions or other script manager may encounter bugs.',Mask: 'Show mask',Unmask: 'Hide mask',EnableAutomask: 'Enable auto-mask for this site',DisableAutomask: 'Disable auto-mask for this site',SetIDLETime: 'Configure auto-mask time',PromptIDLETime: 'Mask will be shown after the webpage has been idle for N second(s).\n You can set that N here:',TamperorilyDisable: 'Tamperorily disable mask',TamperorilyDisabled: 'Mask tamperorily disabled: mask will not be shown in current webpage before refreshing the webpage',CustomUserstyle: 'Custom auto-mask style',PromptUserstyle: 'You can custom the content and style of the mask here\nIf you\'re not sure how to compose styles, leave it blank to set back to default.\n\nStyle format:\ncss:CSS - Apply custom css stylesheet\nimg:url - Display custom image by fullscreen\njs:code - Execute custom javascript when mask created. You can use "js:debugger" to test your code an the environment.',IDLETimeInvalid: 'Invalid input: positive numbers only'}},Style: {BuiltinStyle: '#mask {position: fixed; top: 0; left: 0; right: 100vw; bottom: 100vh; width: 100vw; height: 100vh; border: 0; margin: 0; padding: 0; background: transparent; z-index: 2147483647; display: none} #mask.show {display: block;}',DefaultUserstyle: 'css:#mask {backdrop-filter: blur(30px);}'}};// Init languageconst i18n = Object.keys(CONST.TextAllLang).includes(navigator.language) ? navigator.language : CONST.TextAllLang.DEFAULT;CONST.Text = CONST.TextAllLang[i18n];loadFuncs([{id: 'mask',desc: 'Core: create mask DOM, provide basic mask api',detectDom: 'body',dependencies: 'utils',func: () => {const utils = require('utils');const return_obj = new EventTarget();// Make maskconst mask_container = $$CrE({tagName: 'div',styles: { all: 'initial' }});const mask = $$CrE({tagName: 'div',props: { id: 'mask' }});const shadow = mask_container.attachShadow({ mode: unsafeWindow.isPY_DNG ? 'open' : 'closed' });shadow.appendChild(mask);document.body.after(mask_container);// Stylesconst style = addStyle(shadow, CONST.Style.BuiltinStyle, 'mask-builtin-style');applyUserstyle();['mouseup', 'keyup', 'dragenter'].forEach(evtname => utils.$AEL(unsafeWindow, evtname, e => hide()));const return_props = {mask_container, element: mask, shadow, style, show, hide,get showing() { return showing(); },set showing(v) { v ? show() : hide() },get userstyle() { return getUserstyle() },set userstyle(v) { return setUserstyle(v) }};utils.copyPropDescs(return_props, return_obj);return return_obj;function show() {const defaultNotPrevented = return_obj.dispatchEvent(new Event('show', { cancelable: true }));defaultNotPrevented && mask.classList.add('show');}function hide() {const defaultNotPrevented = return_obj.dispatchEvent(new Event('hide', { cancelable: true }));defaultNotPrevented && mask.classList.remove('show');}function showing() {return mask.classList.contains('show');}function getUserstyle() {return GM_getValue('userstyle', CONST.Style.DefaultUserstyle);}function setUserstyle(val) {const defaultNotPrevented = return_obj.dispatchEvent(new Event('restyle', { cancelable: true }));if (defaultNotPrevented) {applyUserstyle(val);GM_setValue('userstyle', val);}}function applyUserstyle(val) {if (!val) { val = getUserstyle() }if (!val.includes(':')) { Err('mask.applyUserStyle: type not found') }const type = val.substring(0, val.indexOf(':')).toLowerCase();const value = val.substring(val.indexOf(':') + 1).trim();switch (type) {case 'css':GM_addElement(shadow, 'style', { textContent: value, style: 'user' });utils.$AEL(return_obj, 'restyle', e => $(shadow, 'style[style="user"]').remove(), { once: true });break;case 'js':case 'javascript':utils.exec(value, { require, CONST });break;case 'img':case 'image': {addImage(value);break;function addImage(src, remaining_retry=3) {const img = GM_addElement(mask, 'img', {src: value,style: 'width: 100vw; height: 100vh; border: 0; padding: 0; margin: 0;',crossorigin: 'anonymous'}) ?? $(mask, 'img');const controller = new AbortController();utils.$AEL(img, 'error', e => {if (remaining_retry-- > 0) {DoLog(LogLevel.Warning, `Mask image load error, retrying...\n(remaining ${remaining_retry} retries)`);controller.abort();img.remove();addImage(src, remaining_retry);} else {DoLog(LogLevel.Error, `Mask image load error (after maximum retries)\nTry reloading the page or changing an image\nImage url: ${src}`);}}, { once: true });utils.$AEL(return_obj, 'restyle', e => img.remove(), {once: true,signal: controller.signal});}}default:Error(`mask.applyUserStyle: Unknown type: ${type}`);}}}}, {id: 'control',desc: 'Provide mask control ui to user',dependencies: ['utils', 'mask'],func: () => {const utils = require('utils');const mask = require('mask');// Switch menu builderconst buildMenu = (text_getter, callback, id=null) => GM_registerMenuCommand(text_getter(), callback, id !== null ? { id } : {});// Enable/Disable switchconst show_text_getter = () => CONST.Text[mask.showing ? 'Unmask' : 'Mask'];const show_menu_onclick = e => mask.showing = !mask.showing;const buildShowMenu = (id = null) => buildMenu(show_text_getter, show_menu_onclick, id);const id = buildShowMenu();utils.$AEL(mask, 'show', e => setTimeout(() => buildShowMenu(id)));utils.$AEL(mask, 'hide', e => setTimeout(() => buildShowMenu(id)));// Tamperorily disableGM_registerMenuCommand(CONST.Text.TamperorilyDisable, e => {mask.hide();utils.$AEL(mask, 'show', e => e.preventDefault());DoLog(LogLevel.Success, CONST.Text.TamperorilyDisabled);setTimeout(() => alert(CONST.Text.TamperorilyDisabled));});// Custom user styleGM_registerMenuCommand(CONST.Text.CustomUserstyle, e => {let style = prompt(CONST.Text.PromptUserstyle, mask.userstyle);if (style === null) { return; }if (style === '') { style = CONST.Style.DefaultUserstyle }// Here should add an style valid checkmask.userstyle = style;});return { id };}}, {id: 'automask',desc: 'extension: auto-mask after certain idle time',detectDom: 'body',dependencies: ['mask', 'utils'],func: () => {const utils = require('utils');const mask = require('mask');const id = GM_registerMenuCommand(isAutomaskEnabled() ? CONST.Text.DisableAutomask : CONST.Text.EnableAutomask,function onClick(e) {isAutomaskEnabled() ? disable() : enable();GM_registerMenuCommand(isAutomaskEnabled() ? CONST.Text.DisableAutomask : CONST.Text.EnableAutomask,onClick, { id });isAutomaskEnabled() && check_idle();});GM_registerMenuCommand(CONST.Text.SetIDLETime, e => {const config = getConfig();const time = prompt(CONST.Text.PromptIDLETime, config.idle_time);if (time === null) { return; }if (!/^(\d+\.)?\d+$/.test(time)) { alert(CONST.Text.IDLETimeInvalid); return; }config.idle_time = +time;setConfig(config);});// Auto-mask when idlelet last_refreshed = Date.now();const cancel_idle = () => {// Iframe events won't bubble into parent window, so manually tell top window to also cancel_idleconst in_iframe = unsafeWindow !== unsafeWindow.top;in_iframe && utils.postMessage(unsafeWindow.top, 'iframe_cancel_idle');// Refresh time recordlast_refreshed = Date.now();};['mousemove', 'mousedown', 'mouseup', 'wheel', 'keydown', 'keyup'].forEach(evt_name =>utils.$AEL(unsafeWindow, evt_name, e => cancel_idle(), { capture: true }));utils.recieveMessage('iframe_cancel_idle', e => cancel_idle());const check_idle = () => {const config = getConfig();const time_left = config.idle_time * 1000 - (Date.now() - last_refreshed);if (time_left <= 0) {isAutomaskEnabled() && !mask.showing && mask.show();utils.$AEL(mask, 'hide', e => {cancel_idle();check_idle();}, { once: true });} else {setTimeout(check_idle, time_left);}}check_idle();return {id, enable, disable,get enabled() { return isAutomaskEnabled(); }};function getConfig() {return GM_getValue('automask', {sites: [],idle_time: 30});}function setConfig(val) {return GM_setValue('automask', val);}function isAutomaskEnabled() {return getConfig().sites.includes(location.host);}function enable() {if (isAutomaskEnabled()) { return; }const config = getConfig();config.sites.push(location.host);setConfig(config);}function disable() {if (!isAutomaskEnabled()) { return; }const config = getConfig();config.sites.splice(config.sites.indexOf(location.host), 1);setConfig(config);}}}, {id: 'utils',desc: 'helper functions',func: () => {function GM_hasVersion(version) {return hasVersion(GM_info?.version || '0', version);function hasVersion(ver1, ver2) {return compareVersions(ver1.toString(), ver2.toString()) >= 0;// https://greasyfork.org/app/javascript/versioncheck.js// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version/formatfunction compareVersions(a, b) {if (a == b) {return 0;}let aParts = a.split('.');let bParts = b.split('.');for (let i = 0; i < aParts.length; i++) {let r###lt = compareVersionPart(aParts[i], bParts[i]);if (r###lt != 0) {return r###lt;}}// If all of a's parts are the same as b's parts, but b has additional parts, b is greater.if (bParts.length > aParts.length) {return -1;}return 0;}function compareVersionPart(partA, partB) {let partAParts = parseVersionPart(partA);let partBParts = parseVersionPart(partB);for (let i = 0; i < partAParts.length; i++) {// "A string-part that exists is always less than a string-part that doesn't exist"if (partAParts[i].length > 0 && partBParts[i].length == 0) {return -1;}if (partAParts[i].length == 0 && partBParts[i].length > 0) {return 1;}if (partAParts[i] > partBParts[i]) {return 1;}if (partAParts[i] < partBParts[i]) {return -1;}}return 0;}// It goes number, string, number, string. If it doesn't exist, then// 0 for numbers, empty string for strings.function parseVersionPart(part) {if (!part) {return [0, "", 0, ""];}let partParts = /([0-9]*)([^0-9]*)([0-9]*)([^0-9]*)/.exec(part)return [partParts[1] ? parseInt(partParts[1]) : 0,partParts[2],partParts[3] ? parseInt(partParts[3]) : 0,partParts[4]];}}}function copyPropDescs(from, to) {Object.defineProperties(to, Object.getOwnPropertyDescriptors(from));}function randint(min, max) {return Math.random() * (max - min) + min;}function randstr(len) {const letters = [...Array(26).keys()].map( i => String.fromCharCode('a'.charCodeAt(0) + i) );let str = '';for (let i = 0; i < len; i++) {str += letters.at(randint(0, 25));}return str;}/*** execute js code in a global function closure and try to bypass CSP rules* { name: 'John', number: 123456 } will be executed by (function(name, number) { code }) ('John', 123456);** @param {string} code* @param {Object} args*/function exec(code, args) {// Parse argsconst arg_names = Object.keys(args);const arg_vals = arg_names.map(name => args[name]);// Construct middle code and middle objconst id = randstr(16);const middle_obj = unsafeWindow[id] = { id, arg_vals, url: null };const middle_code_parts = {single_instance: ['// Run code only once',`if (!window.hasOwnProperty('${id}')) { return; }`].join('\n'),cleaner: ['// Do some cleaning first',`const middle_obj = window.${id};`,`delete window.${id};`,`URL.revokeObjectURL(middle_obj.url);`,`document.querySelector('#${id}')?.remove();`].join('\n'),executer: ['// Execute user code',`(function(${arg_names.join(', ')}, middle_obj) {`,code,`}).call(null, ...middle_obj.arg_vals, undefined);`].join('\n'),}const middle_code = `(function() {\n${Object.values(middle_code_parts).join('\n\n')}\n}) ();`;const blob = new Blob([middle_code], { type: 'application/javascript' });const url = middle_obj.url = URL.createObjectURL(blob);// Create and execute <script>GM_addElement(document.head, 'script', { src: url, id });GM_addElement(document.head, 'script', { textContent: middle_code, id });}/*** Some website (like icourse163.com, dolmods.com, etc.) hooked addEventListener,\* so calling target.addEventListener has no effect.* this function get a "pure" addEventListener from new iframe, and make use of it*/function $AEL(target, ...args) {if (!$AEL.id_prop) {$AEL.id_prop = randstr(16);$AEL.id_val = randstr(16);GM_addElement(document.body, 'iframe', {srcdoc: '<html></html>',style: ['border: 0','padding: 0','margin: 0','width: 0','height: 0','display: block','visibility: visible'].concat('').join(' !important; '),[$AEL.id_prop]: $AEL.id_val,});}try {const ifr = $(`[${$AEL.id_prop}=${$AEL.id_val}]`);const AEL = ifr.contentWindow.EventTarget.prototype.addEventListener;return AEL.call(target, ...args);} catch (e) {if (!$(`[${$AEL.id_prop}=${$AEL.id_val}]`)) {DoLog(LogLevel.Warning, 'GM_addElement is not working properly: added iframe not found\nUsing normal addEventListener instead');} else {DoLog(LogLevel.Warning, 'Unknown error occured\nUsing normal addEventListener instead')}return window.EventTarget.prototype.addEventListener.call(target, ...args);}}const [postMessage, recieveMessage] = (function() {// Check and init security keylet securityKey = GM_getValue('Message-Security-Key');if (!securityKey) {securityKey = { prop: randstr(8), value: randstr(16) };GM_setValue('Message-Security-Key', securityKey);}/*** post a message to target window using window.postMessage* name, data will be formed as an object in format of { name, data, securityKeyProp: securityKeyVal }* securityKey will be randomly generated with first initialization* and saved with GM_setValue('Message-Security-Key', { prop, value })** @param {string} name - type of this message* @param {*} [data] - data of this message*/function postMessage(targetWindow, name, data=null) {const securityKeyProp = securityKey.prop;const securityKeyVal = securityKey.value;targetWindow.postMessage({ name, data, [securityKey.prop]: securityKey.value }, '*');}/*** recieve message posted by postMessage* @param {string} name - which kind of message you want to recieve* @param {function} [callback] - if provided, return undefined and call this callback function each time a messagerecieved; if not, returns a Promise that will be fulfilled with next message for once* @returns {(Promise<number>|undefined)}*/function recieveMessage(name, callback=null) {const win = typeof unsafeWindow === 'object' ? unsafeWindow : window;let resolve;$AEL(win, 'message', function listener(e) {// Check security key firstif (!(e?.data?.[securityKey.prop] === securityKey.value)) { return; }if (e?.data?.name === name) {if (callback) {callback(e);} else {resolve(e);}}});if (!callback) {return new Promise((res, rej) => { resolve = res; });}}return [postMessage, recieveMessage];}) ();return { GM_hasVersion, copyPropDescs, randint, randstr, exec, $AEL, postMessage, recieveMessage };}}, {desc: 'compatibility alert',dependencies: 'utils',func: () => {const utils = require('utils');if (!GM_getValue('compat-alert') && (GM_info.scriptHandler !== 'Tampermonkey' || !utils.GM_hasVersion('5.0'))) {alert(CONST.Text.CompatAlert);GM_setValue('compat-alert', true);}}}]);})();