网页枷锁破除

解除网页右键菜单/选择文本/拷贝粘贴/拖拽内容限制 恢复自由交互体验 轻点右下角灵动指示器 | 使用快捷键 Meta/Ctrl+Alt+L | 脚本菜单切换状态

  1. // ==UserScript==
  2. // @name Universal Web Liberator
  3. // @name:zh-CN 网页枷锁破除
  4. // @name:zh-TW 網頁枷鎖破除
  5. // @description Disable webpage restrictions on Right-Click / Selection / Copy / Drag | Restore a seamless interactive experience | Tap the dynamic indicator in the bottom-right corner | Use the shortcut Meta/Ctrl+Alt+L | Toggle state via the userscript menu
  6. // @description:zh-CN 解除网页右键菜单/选择文本/拷贝粘贴/拖拽内容限制 恢复自由交互体验 轻点右下角灵动指示器 | 使用快捷键 Meta/Ctrl+Alt+L | 脚本菜单切换状态
  7. // @description:zh-TW 解除網頁右鍵選單/選取文字/複製貼上/拖曳內容限制 恢復自由互動體驗 輕點右下角靈動指示器 | 使用快捷鍵 Meta/Ctrl+Alt+L | 腳本選單切換狀態
  8. // @version 1.5.5
  9. // @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/main/Icons/Universal-Web-Liberator-Icon.svg
  10. // @author 念柚
  11. // @namespace https://github.com/MiPoNianYou/UserScripts
  12. // @supportURL https://github.com/MiPoNianYou/UserScripts/issues
  13. // @license AGPL-3.0
  14. // @match *://*/*
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_deleteValue
  18. // @grant GM_addStyle
  19. // @grant GM_registerMenuCommand
  20. // @grant GM_unregisterMenuCommand
  21. // @run-at document-start
  22. // ==/UserScript==
  23.  
  24. (function () {
  25. "use strict";
  26.  
  27. const UIManager = {
  28. ELEMENT_IDS: {
  29. STATUS_NOTIFICATION: "WebLiberatorStatusNotification",
  30. DYNAMIC_INDICATOR: "WebLiberatorDynamicIndicator",
  31. OVERRIDE_STYLE_SHEET: "WebLiberatorOverrideStyleSheet",
  32. },
  33. CSS_CLASSES: {
  34. STATUS_NOTIFICATION_VISIBLE: "wl-status-notification--visible",
  35. STATUS_NOTIFICATION_DISABLED: "wl-status-notification--disabled",
  36. BREATHING_DOT: "wl-breathing-dot",
  37. STATUS_NOTIFICATION_MESSAGE: "wl-status-notification-message",
  38. DYNAMIC_INDICATOR_EXPANDED: "wl-dynamic-indicator--expanded",
  39. DYNAMIC_INDICATOR_ACTIVE: "wl-dynamic-indicator--active",
  40. },
  41. SETTINGS: {
  42. UI_FONT_STACK: "-apple-system, BlinkMacSystemFont, system-ui, sans-serif",
  43. ANIMATION_EASING_QUINT: "cubic-bezier(0.23, 1, 0.32, 1)",
  44. ANIMATION_EASING_APPLE_STANDARD: "cubic-bezier(0, 0, 0.58, 1)",
  45. ANIMATION_DURATION_MS: 300,
  46. NOTIFICATION_VISIBILITY_DURATION_MS: 2000,
  47. INDICATOR_EXPANDED_DURATION_MS: 2000,
  48. INDICATOR_TRANSITION_DURATION_S: "0.25s",
  49. ICON_TRANSITION_DURATION_S: "0.2s",
  50. ICON_TRANSITION_DELAY_S: "0.1s",
  51. },
  52. STRINGS: {
  53. "zh-CN": {
  54. STATUS_ENABLED: "枷锁限制破除",
  55. STATUS_DISABLED: "枷锁限制恢复",
  56. MENU_CMD_ENABLED: "枷锁限制破除已启用 ✅",
  57. MENU_CMD_DISABLED: "枷锁限制破除已禁用 ❌",
  58. },
  59. "zh-TW": {
  60. STATUS_ENABLED: "枷鎖限制破除",
  61. STATUS_DISABLED: "枷鎖限制恢復",
  62. MENU_CMD_ENABLED: "枷鎖限制破除已啟用 ✅",
  63. MENU_CMD_DISABLED: "枷鎖限制破除已禁用 ❌",
  64. },
  65. "en-US": {
  66. STATUS_ENABLED: "Restrictions Bypassed",
  67. STATUS_DISABLED: "Restrictions Restored",
  68. MENU_CMD_ENABLED: "Restrictions Bypassed Activated ✅",
  69. MENU_CMD_DISABLED: "Restrictions Bypassed Deactivated ❌",
  70. },
  71. },
  72. SVG_ICONS: {
  73. DYNAMIC_INDICATOR:
  74. `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 82.129 104.736"><path d="M22.46 67.969h37.306c1.757 0 3.027-1.367 3.027-3.076 0-1.66-1.27-2.93-3.027-2.93H22.46c-1.758 0-3.076 1.27-3.076 2.93 0 1.709 1.318 3.076 3.076 3.076m0 14.648h17.628c1.758 0 3.027-1.27 3.027-2.93 0-1.709-1.27-3.076-3.027-3.076H22.46c-1.758 0-3.076 1.367-3.076 3.076 0 1.66 1.318 2.93 3.076 2.93m8.058-32.47h3.808c.342 0 .586.146.88.439l2.733 2.637c2.149 2.246 4.2 2.197 6.348 0l2.735-2.637c.293-.244.488-.44.878-.44h3.76c3.076 0 4.541-1.416 4.541-4.443v-3.809c0-.341.049-.634.342-.878l2.685-2.735c2.198-2.148 2.198-4.199 0-6.396l-2.685-2.686c-.293-.244-.342-.537-.342-.879v-3.808c0-3.076-1.416-4.492-4.54-4.492H47.9c-.391 0-.587-.196-.88-.391l-2.734-2.637c-2.148-2.246-4.2-2.197-6.347 0l-2.735 2.637c-.244.293-.537.39-.879.39h-3.808c-3.077 0-4.541 1.368-4.541 4.493v3.808c0 .342-.05.635-.342.88l-2.686 2.685c-2.197 2.197-2.197 4.248 0 6.396l2.686 2.735c.293.244.342.537.342.879v3.808c0 3.027 1.464 4.444 4.54 4.444m8.593-5.909c-.732 0-1.367-.341-2.002-1.123l-4.394-5.37c-.342-.392-.586-.929-.586-1.466 0-1.123.83-2.1 2.1-2.1.683 0 1.22.343 1.806 1.026l3.028 3.809 6.982-11.182c.39-.635 1.025-1.123 1.758-1.123 1.074 0 2.1.781 2.1 1.953 0 .537-.294 1.123-.538 1.514l-8.203 12.89c-.488.83-1.22 1.172-2.05 1.172M0 89.404c0 10.205 5.03 15.284 15.137 15.284h51.855c10.108 0 15.137-5.079 15.137-15.284V15.332C82.129 5.176 77.099 0 66.992 0H15.137C5.029 0 0 5.176 0 15.332Zm7.861-.146v-73.78c0-4.882 2.588-7.617 7.666-7.617h51.075c5.078 0 7.666 2.735 7.666 7.618v73.779c0 4.883-2.588 7.568-7.666 7.568H15.527c-5.078 0-7.666-2.685-7.666-7.568" fill="currentColor"/></svg>`.trim(),
  75. },
  76. statusNotificationTimer: null,
  77. statusNotificationRemovalTimer: null,
  78. dynamicIndicatorElement: null,
  79. indicatorCollapseTimer: null,
  80. userScriptMenuCommandId: null,
  81.  
  82. injectCoreStyles() {
  83. const easeOutQuint = this.SETTINGS.ANIMATION_EASING_QUINT;
  84. const appleEaseOutStandard =
  85. this.SETTINGS.ANIMATION_EASING_APPLE_STANDARD;
  86. const animationDurationMs = this.SETTINGS.ANIMATION_DURATION_MS;
  87. const indicatorTransitionDuration =
  88. this.SETTINGS.INDICATOR_TRANSITION_DURATION_S;
  89. const iconTransitionDuration = this.SETTINGS.ICON_TRANSITION_DURATION_S;
  90. const iconTransitionDelay = this.SETTINGS.ICON_TRANSITION_DELAY_S;
  91.  
  92. const baseCSS = `
  93. :root {
  94. --ctp-frappe-red: rgb(231, 130, 132);
  95. --ctp-frappe-green: rgb(166, 209, 137);
  96. --ctp-frappe-text: rgb(198, 208, 245);
  97. --ctp-frappe-surface1: rgb(81, 87, 109);
  98. --ctp-frappe-surface2: rgb(98, 104, 128);
  99. --ctp-frappe-base: rgb(48, 52, 70);
  100. --ctp-frappe-crust: rgb(35, 38, 52);
  101.  
  102. --ctp-latte-red: rgb(210, 15, 57);
  103. --ctp-latte-green: rgb(64, 160, 43);
  104. --ctp-latte-text: rgb(76, 79, 105);
  105. --ctp-latte-overlay0: rgb(156, 160, 176);
  106. --ctp-latte-surface1: rgb(188, 192, 204);
  107. --ctp-latte-surface2: rgb(172, 176, 190);
  108. --ctp-latte-base: rgb(239, 241, 245);
  109.  
  110. --wl-notify-bg-dark: rgb(from var(--ctp-frappe-base) r g b / 0.85);
  111. --wl-notify-text-dark: var(--ctp-frappe-text);
  112. --wl-notify-border-dark: rgb(from var(--ctp-frappe-surface2) r g b / 0.25);
  113. --wl-notify-dot-color-enabled-dark: var(--ctp-frappe-green);
  114. --wl-notify-dot-color-disabled-dark: var(--ctp-frappe-red);
  115. --wl-notify-dot-glow-enabled-dark: rgb(from var(--ctp-frappe-green) r g b / 0.35);
  116. --wl-notify-dot-glow-disabled-dark: rgb(from var(--ctp-frappe-red) r g b / 0.35);
  117. --wl-indicator-inactive-bg-dark: var(--ctp-frappe-surface2);
  118. --wl-indicator-inactive-opacity-dark: 0.55;
  119. --wl-indicator-hover-bg-dark: rgb(from var(--ctp-frappe-surface1) r g b / 0.5);
  120. --wl-indicator-expanded-bg-dark: rgb(from var(--ctp-frappe-base) r g b / 0.85);
  121. --wl-indicator-active-bg-dark: rgb(from var(--ctp-frappe-green) r g b / 0.88);
  122. --wl-indicator-border-dark: rgb(from var(--ctp-frappe-surface2) r g b / 0.2);
  123. --wl-icon-color-dark: var(--ctp-frappe-text);
  124. --wl-icon-active-color-dark: var(--ctp-frappe-crust);
  125.  
  126. --wl-notify-bg-light: rgb(from var(--ctp-latte-base) r g b / 0.85);
  127. --wl-notify-text-light: var(--ctp-latte-text);
  128. --wl-notify-border-light: rgb(from var(--ctp-latte-surface2) r g b / 0.25);
  129. --wl-notify-dot-color-enabled-light: var(--ctp-latte-green);
  130. --wl-notify-dot-color-disabled-light: var(--ctp-latte-red);
  131. --wl-notify-dot-glow-enabled-light: rgb(from var(--ctp-latte-green) r g b / 0.35);
  132. --wl-notify-dot-glow-disabled-light: rgb(from var(--ctp-latte-red) r g b / 0.35);
  133. --wl-indicator-inactive-bg-light: var(--ctp-latte-overlay0);
  134. --wl-indicator-inactive-opacity-light: 0.65;
  135. --wl-indicator-hover-bg-light: rgb(from var(--ctp-latte-surface1) r g b / 0.5);
  136. --wl-indicator-expanded-bg-light: rgb(from var(--ctp-latte-base) r g b / 0.85);
  137. --wl-indicator-active-bg-light: rgb(from var(--ctp-latte-green) r g b / 0.88);
  138. --wl-indicator-border-light: rgb(from var(--ctp-latte-surface2) r g b / 0.2);
  139. --wl-icon-color-light: var(--ctp-latte-text);
  140. --wl-icon-active-color-light: var(--ctp-latte-base);
  141.  
  142. --wl-shadow-dark:
  143. 0 1px 2px rgba(0, 0, 0, 0.1),
  144. 0 6px 12px rgba(0, 0, 0, 0.2);
  145. --wl-shadow-light:
  146. 0 1px 2px rgba(90, 90, 90, 0.06),
  147. 0 6px 12px rgba(90, 90, 90, 0.12);
  148. }
  149.  
  150. @keyframes wl-breathing-animation {
  151. 0%, 100% {
  152. transform: scale(0.85);
  153. opacity: 0.7;
  154. }
  155. 50% {
  156. transform: scale(1);
  157. opacity: 1;
  158. }
  159. }
  160.  
  161. #${this.ELEMENT_IDS.STATUS_NOTIFICATION} {
  162. position: fixed;
  163. bottom: 20px;
  164. left: 50%;
  165. z-index: 2147483646;
  166. display: flex;
  167. align-items: center;
  168. padding: 10px 16px;
  169. border: 1px solid var(--wl-notify-border-dark);
  170. border-radius: 20px;
  171. background-color: var(--wl-notify-bg-dark);
  172. color: var(--wl-notify-text-dark);
  173. box-shadow: var(--wl-shadow-dark);
  174. box-sizing: border-box;
  175. opacity: 0;
  176. font-family: ${this.SETTINGS.UI_FONT_STACK};
  177. text-align: left;
  178. backdrop-filter: blur(16px) saturate(180%);
  179. -webkit-backdrop-filter: blur(16px) saturate(180%);
  180. transform: translate(-50%, calc(100% + 40px));
  181. transition: transform ${animationDurationMs}ms ${easeOutQuint},
  182. opacity ${animationDurationMs * 0.8}ms ${easeOutQuint};
  183. }
  184.  
  185. #${this.ELEMENT_IDS.STATUS_NOTIFICATION}.${
  186. this.CSS_CLASSES.STATUS_NOTIFICATION_VISIBLE
  187. } {
  188. transform: translate(-50%, 0);
  189. opacity: 1;
  190. }
  191.  
  192. #${this.ELEMENT_IDS.STATUS_NOTIFICATION} .${
  193. this.CSS_CLASSES.BREATHING_DOT
  194. } {
  195. width: 8px;
  196. height: 8px;
  197. margin-right: 10px;
  198. border-radius: 50%;
  199. background-color: var(--wl-notify-dot-color-enabled-dark);
  200. box-shadow: 0 0 8px 3px var(--wl-notify-dot-glow-enabled-dark);
  201. flex-shrink: 0;
  202. animation: wl-breathing-animation 2000ms ease-in-out infinite;
  203. transition: background-color 0.3s ease, box-shadow 0.3s ease;
  204. }
  205.  
  206. #${this.ELEMENT_IDS.STATUS_NOTIFICATION}.${
  207. this.CSS_CLASSES.STATUS_NOTIFICATION_DISABLED
  208. } .${this.CSS_CLASSES.BREATHING_DOT} {
  209. background-color: var(--wl-notify-dot-color-disabled-dark);
  210. box-shadow: 0 0 8px 3px var(--wl-notify-dot-glow-disabled-dark);
  211. }
  212.  
  213. #${this.ELEMENT_IDS.STATUS_NOTIFICATION} .${
  214. this.CSS_CLASSES.STATUS_NOTIFICATION_MESSAGE
  215. } {
  216. color: var(--wl-notify-text-dark);
  217. font-size: 13px;
  218. font-weight: 500;
  219. line-height: 1.2;
  220. white-space: nowrap;
  221. overflow: hidden;
  222. text-overflow: ellipsis;
  223. }
  224.  
  225. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR} {
  226. position: fixed;
  227. bottom: 20px;
  228. right: 20px;
  229. z-index: 2147483647;
  230. display: flex;
  231. align-items: center;
  232. justify-content: center;
  233. width: 12px;
  234. height: 12px;
  235. border-radius: 50%;
  236. background-color: var(--wl-indicator-inactive-bg-dark);
  237. color: var(--wl-icon-color-dark);
  238. opacity: var(--wl-indicator-inactive-opacity-dark);
  239. overflow: hidden;
  240. cursor: pointer;
  241. backdrop-filter: blur(3px);
  242. -webkit-backdrop-filter: blur(3px);
  243. transition: width ${indicatorTransitionDuration} ${appleEaseOutStandard},
  244. height ${indicatorTransitionDuration} ${appleEaseOutStandard},
  245. opacity ${indicatorTransitionDuration} ${appleEaseOutStandard},
  246. background-color ${indicatorTransitionDuration} ${appleEaseOutStandard},
  247. color ${indicatorTransitionDuration} ${appleEaseOutStandard},
  248. backdrop-filter ${indicatorTransitionDuration} ${appleEaseOutStandard},
  249. box-shadow ${indicatorTransitionDuration} ${appleEaseOutStandard},
  250. transform ${iconTransitionDuration} ${appleEaseOutStandard};
  251. user-select: none !important;
  252. -webkit-user-select: none !important;
  253. }
  254.  
  255. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}:hover {
  256. background-color: var(--wl-indicator-hover-bg-dark);
  257. opacity: 0.8;
  258. transform: scale(1.1);
  259. }
  260.  
  261. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  262. this.CSS_CLASSES.DYNAMIC_INDICATOR_ACTIVE
  263. }:not(.${this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED}) {
  264. background-color: var(--wl-indicator-active-bg-dark);
  265. opacity: 0.6;
  266. }
  267.  
  268. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  269. this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED
  270. } {
  271. width: 40px;
  272. height: 40px;
  273. border: 1px solid var(--wl-indicator-border-dark);
  274. background-color: var(--wl-indicator-expanded-bg-dark);
  275. box-shadow: var(--wl-shadow-dark);
  276. opacity: 1;
  277. backdrop-filter: blur(16px) saturate(180%);
  278. -webkit-backdrop-filter: blur(16px) saturate(180%);
  279. transform: scale(1);
  280. }
  281.  
  282. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  283. this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED
  284. }:hover {
  285. transform: scale(1.08);
  286. }
  287.  
  288. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  289. this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED
  290. }.${this.CSS_CLASSES.DYNAMIC_INDICATOR_ACTIVE} {
  291. background-color: var(--wl-indicator-active-bg-dark);
  292. color: var(--wl-icon-active-color-dark);
  293. }
  294.  
  295. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR} svg {
  296. display: block;
  297. width: 20px;
  298. height: 20px;
  299. opacity: 0;
  300. transform: scale(0.5);
  301. transition: opacity ${iconTransitionDuration} ${appleEaseOutStandard} ${iconTransitionDelay},
  302. transform ${iconTransitionDuration} ${appleEaseOutStandard} ${iconTransitionDelay};
  303. pointer-events: none;
  304. }
  305.  
  306. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  307. this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED
  308. } svg {
  309. opacity: 1;
  310. transform: scale(1);
  311. }
  312.  
  313. @media (prefers-color-scheme: light) {
  314. #${this.ELEMENT_IDS.STATUS_NOTIFICATION} {
  315. border: 1px solid var(--wl-notify-border-light);
  316. background-color: var(--wl-notify-bg-light);
  317. color: var(--wl-notify-text-light);
  318. }
  319.  
  320. #${this.ELEMENT_IDS.STATUS_NOTIFICATION} .${
  321. this.CSS_CLASSES.BREATHING_DOT
  322. } {
  323. background-color: var(--wl-notify-dot-color-enabled-light);
  324. box-shadow: 0 0 8px 3px var(--wl-notify-dot-glow-enabled-light);
  325. }
  326.  
  327. #${this.ELEMENT_IDS.STATUS_NOTIFICATION}.${
  328. this.CSS_CLASSES.STATUS_NOTIFICATION_DISABLED
  329. } .${this.CSS_CLASSES.BREATHING_DOT} {
  330. background-color: var(--wl-notify-dot-color-disabled-light);
  331. box-shadow: 0 0 8px 3px var(--wl-notify-dot-glow-disabled-light);
  332. }
  333.  
  334. #${this.ELEMENT_IDS.STATUS_NOTIFICATION} .${
  335. this.CSS_CLASSES.STATUS_NOTIFICATION_MESSAGE
  336. } {
  337. color: var(--wl-notify-text-light);
  338. }
  339.  
  340. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR} {
  341. background-color: var(--wl-indicator-inactive-bg-light);
  342. color: var(--wl-icon-color-light);
  343. opacity: var(--wl-indicator-inactive-opacity-light);
  344. }
  345.  
  346. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}:hover {
  347. background-color: var(--wl-indicator-hover-bg-light);
  348. }
  349.  
  350. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  351. this.CSS_CLASSES.DYNAMIC_INDICATOR_ACTIVE
  352. }:not(.${this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED}) {
  353. background-color: var(--wl-indicator-active-bg-light);
  354. opacity: 0.6;
  355. }
  356.  
  357. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  358. this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED
  359. } {
  360. border: 1px solid var(--wl-indicator-border-light);
  361. background-color: var(--wl-indicator-expanded-bg-light);
  362. box-shadow: var(--wl-shadow-light);
  363. }
  364.  
  365. #${this.ELEMENT_IDS.DYNAMIC_INDICATOR}.${
  366. this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED
  367. }.${this.CSS_CLASSES.DYNAMIC_INDICATOR_ACTIVE} {
  368. background-color: var(--wl-indicator-active-bg-light);
  369. color: var(--wl-icon-active-color-light);
  370. }
  371. }
  372. `;
  373. try {
  374. GM_addStyle(baseCSS);
  375. } catch (e) {}
  376. },
  377.  
  378. displayStatusNotification(statusMessageKey) {
  379. if (this.statusNotificationTimer)
  380. clearTimeout(this.statusNotificationTimer);
  381. if (this.statusNotificationRemovalTimer)
  382. clearTimeout(this.statusNotificationRemovalTimer);
  383. this.statusNotificationTimer = null;
  384. this.statusNotificationRemovalTimer = null;
  385.  
  386. const message = StateManager.getLocalizedString(statusMessageKey);
  387.  
  388. const renderNotification = () => {
  389. let notificationElement = document.getElementById(
  390. this.ELEMENT_IDS.STATUS_NOTIFICATION
  391. );
  392. if (!notificationElement && document.body) {
  393. notificationElement = document.createElement("div");
  394. notificationElement.id = this.ELEMENT_IDS.STATUS_NOTIFICATION;
  395. notificationElement.innerHTML = `
  396. <div class="${this.CSS_CLASSES.BREATHING_DOT}"></div>
  397. <div class="${this.CSS_CLASSES.STATUS_NOTIFICATION_MESSAGE}"></div>
  398. `.trim();
  399. document.body.appendChild(notificationElement);
  400. } else if (!notificationElement) {
  401. return;
  402. }
  403.  
  404. if (!StateManager.isScriptActive) {
  405. notificationElement.classList.add(
  406. this.CSS_CLASSES.STATUS_NOTIFICATION_DISABLED
  407. );
  408. } else {
  409. notificationElement.classList.remove(
  410. this.CSS_CLASSES.STATUS_NOTIFICATION_DISABLED
  411. );
  412. }
  413.  
  414. const messageElement = notificationElement.querySelector(
  415. `.${this.CSS_CLASSES.STATUS_NOTIFICATION_MESSAGE}`
  416. );
  417. if (messageElement) messageElement.textContent = message;
  418.  
  419. notificationElement.classList.remove(
  420. this.CSS_CLASSES.STATUS_NOTIFICATION_VISIBLE
  421. );
  422. void notificationElement.offsetWidth;
  423.  
  424. requestAnimationFrame(() => {
  425. const currentElement = document.getElementById(
  426. this.ELEMENT_IDS.STATUS_NOTIFICATION
  427. );
  428. if (currentElement) {
  429. currentElement.classList.add(
  430. this.CSS_CLASSES.STATUS_NOTIFICATION_VISIBLE
  431. );
  432. }
  433. });
  434.  
  435. this.statusNotificationTimer = setTimeout(() => {
  436. const currentElement = document.getElementById(
  437. this.ELEMENT_IDS.STATUS_NOTIFICATION
  438. );
  439. if (currentElement) {
  440. currentElement.classList.remove(
  441. this.CSS_CLASSES.STATUS_NOTIFICATION_VISIBLE
  442. );
  443. this.statusNotificationRemovalTimer = setTimeout(() => {
  444. document
  445. .getElementById(this.ELEMENT_IDS.STATUS_NOTIFICATION)
  446. ?.remove();
  447. this.statusNotificationTimer = null;
  448. this.statusNotificationRemovalTimer = null;
  449. }, this.SETTINGS.ANIMATION_DURATION_MS);
  450. } else {
  451. this.statusNotificationTimer = null;
  452. this.statusNotificationRemovalTimer = null;
  453. }
  454. }, this.SETTINGS.NOTIFICATION_VISIBILITY_DURATION_MS);
  455. };
  456.  
  457. if (document.readyState === "loading") {
  458. document.addEventListener("DOMContentLoaded", renderNotification, {
  459. once: true,
  460. });
  461. } else {
  462. renderNotification();
  463. }
  464. },
  465.  
  466. updateUserScriptMenuCommand() {
  467. if (this.userScriptMenuCommandId) {
  468. try {
  469. GM_unregisterMenuCommand(this.userScriptMenuCommandId);
  470. } catch (e) {}
  471. this.userScriptMenuCommandId = null;
  472. }
  473. const labelKey = StateManager.isScriptActive
  474. ? "MENU_CMD_ENABLED"
  475. : "MENU_CMD_DISABLED";
  476. const commandLabel = StateManager.getLocalizedString(labelKey);
  477. try {
  478. this.userScriptMenuCommandId = GM_registerMenuCommand(
  479. commandLabel,
  480. ScriptManager.toggleActivationWithDebounce
  481. );
  482. } catch (e) {
  483. this.userScriptMenuCommandId = null;
  484. }
  485. },
  486.  
  487. createDynamicIndicator() {
  488. if (
  489. !document.body ||
  490. document.getElementById(this.ELEMENT_IDS.DYNAMIC_INDICATOR)
  491. ) {
  492. return;
  493. }
  494. this.dynamicIndicatorElement = document.createElement("div");
  495. this.dynamicIndicatorElement.id = this.ELEMENT_IDS.DYNAMIC_INDICATOR;
  496. this.dynamicIndicatorElement.innerHTML = this.SVG_ICONS.DYNAMIC_INDICATOR;
  497. document.body.appendChild(this.dynamicIndicatorElement);
  498. this.updateDynamicIndicatorStateVisuals();
  499. EventManager.bindDynamicIndicatorEvents(this.dynamicIndicatorElement);
  500. },
  501.  
  502. ensureDynamicIndicatorExists() {
  503. if (
  504. this.dynamicIndicatorElement &&
  505. document.body?.contains(this.dynamicIndicatorElement)
  506. ) {
  507. this.updateDynamicIndicatorStateVisuals();
  508. return;
  509. }
  510. let existingIndicator = document.getElementById(
  511. this.ELEMENT_IDS.DYNAMIC_INDICATOR
  512. );
  513. if (existingIndicator) {
  514. this.dynamicIndicatorElement = existingIndicator;
  515. this.updateDynamicIndicatorStateVisuals();
  516. EventManager.bindDynamicIndicatorEvents(this.dynamicIndicatorElement);
  517. return;
  518. }
  519. if (document.body) {
  520. this.createDynamicIndicator();
  521. } else {
  522. document.addEventListener(
  523. "DOMContentLoaded",
  524. () => this.createDynamicIndicator(),
  525. { once: true }
  526. );
  527. }
  528. },
  529.  
  530. updateDynamicIndicatorStateVisuals() {
  531. const indicator =
  532. this.dynamicIndicatorElement ||
  533. document.getElementById(this.ELEMENT_IDS.DYNAMIC_INDICATOR);
  534. if (indicator) {
  535. indicator.classList.toggle(
  536. this.CSS_CLASSES.DYNAMIC_INDICATOR_ACTIVE,
  537. StateManager.isScriptActive
  538. );
  539. }
  540. },
  541.  
  542. expandAndCollapseDynamicIndicator() {
  543. const indicator =
  544. this.dynamicIndicatorElement ||
  545. document.getElementById(this.ELEMENT_IDS.DYNAMIC_INDICATOR);
  546. if (!indicator) return;
  547. clearTimeout(this.indicatorCollapseTimer);
  548. indicator.classList.remove(this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED);
  549. void indicator.offsetWidth;
  550. requestAnimationFrame(() => {
  551. indicator.classList.add(this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED);
  552. this.indicatorCollapseTimer = setTimeout(() => {
  553. indicator.classList.remove(
  554. this.CSS_CLASSES.DYNAMIC_INDICATOR_EXPANDED
  555. );
  556. this.indicatorCollapseTimer = null;
  557. }, this.SETTINGS.INDICATOR_EXPANDED_DURATION_MS);
  558. });
  559. },
  560. };
  561.  
  562. const StateManager = {
  563. STORAGE_KEYS: {
  564. ACTIVATION_STATE_PREFIX: "webliberator_state_",
  565. STATE_ENABLED_VALUE: "enabled",
  566. },
  567. stateToggleDebounceMS: 200,
  568. defaultActivationState: false,
  569. isScriptActive: false,
  570. currentLocale: "en-US",
  571. localizedStrings: UIManager.STRINGS["en-US"],
  572. pageOrigin: window.location.origin,
  573.  
  574. loadAndSetInitialState() {
  575. this.isScriptActive = this.defaultActivationState;
  576. this.currentLocale = this.detectUserLanguage();
  577. this.localizedStrings =
  578. UIManager.STRINGS[this.currentLocale] || UIManager.STRINGS["en-US"];
  579. this.loadActivationState();
  580. },
  581. detectUserLanguage() {
  582. const languages = navigator.languages || [navigator.language];
  583. for (const lang of languages) {
  584. const langLower = lang.toLowerCase();
  585.  
  586. if (langLower === "zh-cn") return "zh-CN";
  587. if (
  588. langLower === "zh-tw" ||
  589. langLower === "zh-hk" ||
  590. langLower === "zh-mo" ||
  591. langLower === "zh-hant"
  592. )
  593. return "zh-TW";
  594. if (langLower === "en-us") return "en-US";
  595.  
  596. if (langLower.startsWith("zh")) return "zh-CN";
  597. if (langLower.startsWith("en")) return "en-US";
  598. }
  599. return "en-US";
  600. },
  601. getActivationStateStorageKey() {
  602. const origin = String(this.pageOrigin || "").replace(/\/$/, "");
  603. return `${this.STORAGE_KEYS.ACTIVATION_STATE_PREFIX}${origin}`;
  604. },
  605. loadActivationState() {
  606. const storageKey = this.getActivationStateStorageKey();
  607. let storedValue = null;
  608. try {
  609. storedValue = GM_getValue(storageKey, null);
  610. } catch (e) {}
  611.  
  612. if (storedValue === this.STORAGE_KEYS.STATE_ENABLED_VALUE) {
  613. this.isScriptActive = true;
  614. } else if (storedValue === null) {
  615. this.isScriptActive = this.defaultActivationState;
  616. } else {
  617. this.isScriptActive = this.defaultActivationState;
  618. try {
  619. GM_deleteValue(storageKey);
  620. } catch (e) {}
  621. }
  622. },
  623. saveActivationState() {
  624. const storageKey = this.getActivationStateStorageKey();
  625. try {
  626. if (this.isScriptActive) {
  627. GM_setValue(storageKey, this.STORAGE_KEYS.STATE_ENABLED_VALUE);
  628. } else {
  629. GM_deleteValue(storageKey);
  630. }
  631. } catch (e) {}
  632. },
  633. toggleActivation() {
  634. this.isScriptActive = !this.isScriptActive;
  635. this.saveActivationState();
  636. return this.isScriptActive;
  637. },
  638. getLocalizedString(key) {
  639. return (
  640. this.localizedStrings[key] ??
  641. UIManager.STRINGS["en-US"][key] ??
  642. `${key}?`
  643. );
  644. },
  645. };
  646.  
  647. const RestrictionManager = {
  648. RESTRICTION_OVERRIDES: {
  649. EVENTS_TO_INTERCEPT: [
  650. "contextmenu",
  651. "selectstart",
  652. "copy",
  653. "cut",
  654. "paste",
  655. "drag",
  656. "dragstart",
  657. ],
  658. INLINE_HANDLER_ATTRIBUTES_TO_CLEAR: [
  659. "onmousedown",
  660. "oncontextmenu",
  661. "onselect",
  662. "onselectstart",
  663. "oncopy",
  664. "oncut",
  665. "onpaste",
  666. "onbeforecopy",
  667. "onbeforecut",
  668. "onbeforepaste",
  669. "ondrag",
  670. "ondragstart",
  671. ],
  672. },
  673. overrideStyleSheetElement: null,
  674. applyOverrides() {
  675. this.injectRestrictionOverrideStyles();
  676. EventManager.registerEventInterceptors();
  677. },
  678. removeOverrides() {
  679. this.removeRestrictionOverrideStyles();
  680. EventManager.unregisterEventInterceptors();
  681. },
  682. injectRestrictionOverrideStyles() {
  683. if (
  684. this.overrideStyleSheetElement ||
  685. document.getElementById(UIManager.ELEMENT_IDS.OVERRIDE_STYLE_SHEET)
  686. )
  687. return;
  688. const css = `
  689. *, *::before, *::after {
  690. user-select: text !important; -webkit-user-select: text !important; -moz-user-select: text !important; -ms-user-select: text !important;
  691. cursor: auto !important;
  692. -webkit-user-drag: auto !important; user-drag: auto !important;
  693. }
  694. body { cursor: auto !important; }
  695. ::selection { background-color: highlight !important; color: highlighttext !important; }
  696. ::-moz-selection { background-color: highlight !important; color: highlighttext !important; }
  697. `;
  698. this.overrideStyleSheetElement = document.createElement("style");
  699. this.overrideStyleSheetElement.id =
  700. UIManager.ELEMENT_IDS.OVERRIDE_STYLE_SHEET;
  701. this.overrideStyleSheetElement.textContent = css;
  702. (document.head || document.documentElement).appendChild(
  703. this.overrideStyleSheetElement
  704. );
  705. },
  706. removeRestrictionOverrideStyles() {
  707. this.overrideStyleSheetElement?.remove();
  708. this.overrideStyleSheetElement = null;
  709. document
  710. .getElementById(UIManager.ELEMENT_IDS.OVERRIDE_STYLE_SHEET)
  711. ?.remove();
  712. },
  713. clearElementInlineHandlers(element) {
  714. if (!element || element.nodeType !== Node.ELEMENT_NODE) return;
  715. for (const prop of this.RESTRICTION_OVERRIDES
  716. .INLINE_HANDLER_ATTRIBUTES_TO_CLEAR) {
  717. if (
  718. prop in element &&
  719. (typeof element[prop] === "function" || element[prop] !== null)
  720. ) {
  721. try {
  722. element[prop] = null;
  723. } catch (e) {}
  724. }
  725. if (element.hasAttribute(prop)) {
  726. try {
  727. element.removeAttribute(prop);
  728. } catch (e) {}
  729. }
  730. }
  731. },
  732. handlersRecursively(rootNode) {
  733. if (!StateManager.isScriptActive || !rootNode) return;
  734.  
  735. const processSingleNode = (element) => {
  736. if (element.nodeType !== Node.ELEMENT_NODE) return;
  737.  
  738. if (
  739. element.closest(
  740. `#${UIManager.ELEMENT_IDS.DYNAMIC_INDICATOR}, #${UIManager.ELEMENT_IDS.STATUS_NOTIFICATION}`
  741. )
  742. ) {
  743. return;
  744. }
  745.  
  746. this.clearElementInlineHandlers(element);
  747.  
  748. if (element.shadowRoot?.mode === "open") {
  749. for (const childInShadow of Array.from(element.shadowRoot.children)) {
  750. processSingleNode(childInShadow);
  751. }
  752. }
  753.  
  754. for (const child of Array.from(element.children)) {
  755. processSingleNode(child);
  756. }
  757. };
  758.  
  759. try {
  760. if (
  761. rootNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
  762. rootNode.nodeType === Node.DOCUMENT_NODE
  763. ) {
  764. for (const child of Array.from(rootNode.children)) {
  765. processSingleNode(child);
  766. }
  767. } else if (rootNode.nodeType === Node.ELEMENT_NODE) {
  768. processSingleNode(rootNode);
  769. }
  770. } catch (error) {}
  771. },
  772. };
  773.  
  774. const EventManager = {
  775. domMutationObserver: null,
  776. debouncedHandleDOMMutation: null,
  777. DOM_OBSERVER_DEBOUNCE_MS: 300,
  778.  
  779. init() {
  780. this.debouncedHandleDOMMutation = this.debounce(
  781. this.handleDOMMutation.bind(this),
  782. this.DOM_OBSERVER_DEBOUNCE_MS
  783. );
  784. this.initializeGlobalKeyboardShortcutListener();
  785. },
  786. debounce(func, wait) {
  787. let timeout;
  788. return function executedFunction(...args) {
  789. const later = () => {
  790. clearTimeout(timeout);
  791. func.apply(this, args);
  792. };
  793. clearTimeout(timeout);
  794. timeout = setTimeout(later, wait);
  795. };
  796. },
  797. bindDynamicIndicatorEvents(indicatorElement) {
  798. if (indicatorElement && !indicatorElement.dataset.listenerAttached) {
  799. indicatorElement.addEventListener(
  800. "click",
  801. this.handleDynamicIndicatorClick.bind(this)
  802. );
  803. indicatorElement.dataset.listenerAttached = "true";
  804. }
  805. },
  806. stopPropagationHandler(event) {
  807. event.stopImmediatePropagation();
  808. },
  809. registerEventInterceptors() {
  810. RestrictionManager.RESTRICTION_OVERRIDES.EVENTS_TO_INTERCEPT.forEach(
  811. (type) => {
  812. document.addEventListener(type, this.stopPropagationHandler, {
  813. capture: true,
  814. passive: false,
  815. });
  816. }
  817. );
  818. },
  819. unregisterEventInterceptors() {
  820. RestrictionManager.RESTRICTION_OVERRIDES.EVENTS_TO_INTERCEPT.forEach(
  821. (type) => {
  822. document.removeEventListener(type, this.stopPropagationHandler, {
  823. capture: true,
  824. });
  825. }
  826. );
  827. },
  828. handleDOMMutation(mutations) {
  829. if (!StateManager.isScriptActive) return;
  830. for (const mutation of mutations) {
  831. if (mutation.type === "childList") {
  832. for (const node of mutation.addedNodes) {
  833. RestrictionManager.handlersRecursively(node);
  834. }
  835. }
  836. }
  837. },
  838. initializeDOMObserver() {
  839. if (this.domMutationObserver || !document.documentElement) return;
  840. const observerOptions = { childList: true, subtree: true };
  841. this.domMutationObserver = new MutationObserver(
  842. this.debouncedHandleDOMMutation
  843. );
  844. try {
  845. this.domMutationObserver.observe(
  846. document.documentElement,
  847. observerOptions
  848. );
  849. } catch (error) {
  850. this.domMutationObserver = null;
  851. }
  852. },
  853. disconnectDOMObserver() {
  854. if (this.domMutationObserver) {
  855. this.domMutationObserver.disconnect();
  856. this.domMutationObserver = null;
  857. }
  858. },
  859. handleDynamicIndicatorClick(event) {
  860. event.stopPropagation();
  861. UIManager.expandAndCollapseDynamicIndicator();
  862. ScriptManager.toggleActivationWithDebounce();
  863. },
  864. handleKeyboardShortcut(event) {
  865. if (
  866. (event.ctrlKey || event.metaKey) &&
  867. event.altKey &&
  868. event.code === "KeyL"
  869. ) {
  870. event.preventDefault();
  871. event.stopPropagation();
  872. ScriptManager.toggleActivationWithDebounce();
  873. }
  874. },
  875. initializeGlobalKeyboardShortcutListener() {
  876. document.addEventListener(
  877. "keydown",
  878. this.handleKeyboardShortcut.bind(this),
  879. { capture: true }
  880. );
  881. },
  882. };
  883.  
  884. const ScriptManager = {
  885. init() {
  886. try {
  887. StateManager.loadAndSetInitialState();
  888. UIManager.injectCoreStyles();
  889. UIManager.ensureDynamicIndicatorExists();
  890. UIManager.updateUserScriptMenuCommand();
  891.  
  892. if (StateManager.isScriptActive) {
  893. RestrictionManager.applyOverrides();
  894. RestrictionManager.handlersRecursively(
  895. document.documentElement
  896. );
  897. EventManager.initializeDOMObserver();
  898. }
  899. EventManager.init();
  900. } catch (error) {}
  901. },
  902. toggleActivation() {
  903. const isActiveNow = StateManager.toggleActivation();
  904. if (isActiveNow) {
  905. RestrictionManager.applyOverrides();
  906. RestrictionManager.handlersRecursively(
  907. document.documentElement
  908. );
  909. EventManager.initializeDOMObserver();
  910. UIManager.displayStatusNotification("STATUS_ENABLED");
  911. } else {
  912. RestrictionManager.removeOverrides();
  913. EventManager.disconnectDOMObserver();
  914. UIManager.displayStatusNotification("STATUS_DISABLED");
  915. }
  916. UIManager.updateUserScriptMenuCommand();
  917. UIManager.updateDynamicIndicatorStateVisuals();
  918. },
  919. debounce(func, wait) {
  920. let timeout;
  921. return function executedFunction(...args) {
  922. const later = () => {
  923. clearTimeout(timeout);
  924. func.apply(this, args);
  925. };
  926. clearTimeout(timeout);
  927. timeout = setTimeout(later, wait);
  928. };
  929. },
  930. toggleActivationWithDebounce: null,
  931. };
  932. ScriptManager.toggleActivationWithDebounce = ScriptManager.debounce(
  933. ScriptManager.toggleActivation,
  934. StateManager.stateToggleDebounceMS
  935. );
  936.  
  937. if (window.self === window.top) {
  938. if (document.readyState === "loading") {
  939. document.addEventListener(
  940. "DOMContentLoaded",
  941. () => ScriptManager.init(),
  942. { once: true }
  943. );
  944. } else {
  945. ScriptManager.init();
  946. }
  947. }
  948. })();

QingJ © 2025

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