密码显示助手

密码输入框内容可聚焦即显 / 悬浮即览 / 双击切换 / 始终可见 | 通过菜单或快捷键(Meta/Ctrl+Alt+P)切换显示模式

  1. // ==UserScript==
  2. // @name Password Revealer
  3. // @name:zh-CN 密码显示助手
  4. // @name:zh-TW 密碼顯示助手
  5. // @description Password Field Content Via - Reveal On Focus / Preview On Hover / Toggle On Double-Click / Always Visible | Switch Display Mode Via Menu Or Shortcut (Meta/Ctrl+Alt+P)
  6. // @description:zh-CN 密码输入框内容可聚焦即显 / 悬浮即览 / 双击切换 / 始终可见 | 通过菜单或快捷键(Meta/Ctrl+Alt+P)切换显示模式
  7. // @description:zh-TW 密碼輸入框內容可聚焦即顯 / 懸停即覽 / 雙擊切換 / 始終可見 | 透過選單或快速鍵(Meta/Ctrl+Alt+P)切換顯示模式
  8. // @version 1.5.0
  9. // @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/main/Icons/Password-Revealer-Icon.svg
  10. // @author 念柚
  11. // @namespace https://github.com/MiPoNianYou/UserScripts
  12. // @supportURL https://github.com/MiPoNianYou/UserScripts/issues
  13. // @license GPL-3.0
  14. // @match *://*/*
  15. // @grant GM_getValue
  16. // @grant GM_setValue
  17. // @grant GM_registerMenuCommand
  18. // @grant GM_unregisterMenuCommand
  19. // @grant GM_addStyle
  20. // @run-at document-idle
  21. // ==/UserScript==
  22.  
  23. (function () {
  24. "use strict";
  25.  
  26. const Config = {
  27. SCRIPT_SETTINGS: {
  28. UI_FONT_STACK: "-apple-system, BlinkMacSystemFont, system-ui, sans-serif",
  29. ANIMATION_DURATION_MS: 300,
  30. NOTIFICATION_VISIBILITY_DURATION_MS: 2000,
  31. },
  32. MODES: {
  33. FOCUS: "Focus",
  34. HOVER: "Hover",
  35. DBLCLICK: "DoubleClick",
  36. ALWAYS_SHOW: "AlwaysShow",
  37. },
  38. get VALID_MODES() {
  39. return [
  40. this.MODES.FOCUS,
  41. this.MODES.HOVER,
  42. this.MODES.DBLCLICK,
  43. this.MODES.ALWAYS_SHOW,
  44. ];
  45. },
  46. ELEMENT_IDS: {
  47. MODE_NOTIFICATION: "PasswordRevealerModeNotification",
  48. },
  49. CSS_CLASSES: {
  50. MODE_NOTIFICATION_VISIBLE: "pr-mode-notification--visible",
  51. BREATHING_DOT: "pr-breathing-dot",
  52. MODE_NOTIFICATION_MESSAGE: "pr-mode-notification-message",
  53. },
  54. ATTRIBUTES: {
  55. PROCESSED: "data-password-revealer-processed",
  56. },
  57. UI_TEXTS: {
  58. "zh-CN": {
  59. SCRIPT_TITLE: "密码显示助手",
  60. MENU_CMD_FOCUS: "「聚焦即显」模式",
  61. MENU_CMD_HOVER: "「悬浮即览」模式",
  62. MENU_CMD_DBLCLICK: "「双击切换」模式",
  63. MENU_CMD_ALWAYS_SHOW: "「始终可见」模式",
  64. ALERT_MSG_FOCUS: "模式已切换为「聚焦即显」",
  65. ALERT_MSG_HOVER: "模式已切换为「悬浮即览」",
  66. ALERT_MSG_DBLCLICK: "模式已切换为「双击切换」",
  67. ALERT_MSG_ALWAYS_SHOW: "模式已切换为「始终可见」",
  68. },
  69. "zh-TW": {
  70. SCRIPT_TITLE: "密碼顯示助手",
  71. MENU_CMD_FOCUS: "「聚焦即顯」模式",
  72. MENU_CMD_HOVER: "「懸停即覽」模式",
  73. MENU_CMD_DBLCLICK: "「雙擊切換」模式",
  74. MENU_CMD_ALWAYS_SHOW: "「始終可見」模式",
  75. ALERT_MSG_FOCUS: "模式已切換為「聚焦即顯」",
  76. ALERT_MSG_HOVER: "模式已切換為「懸停即覽」",
  77. ALERT_MSG_DBLCLICK: "模式已切換為「雙擊切換」",
  78. ALERT_MSG_ALWAYS_SHOW: "模式已切換為「始終可見」",
  79. },
  80. "en-US": {
  81. SCRIPT_TITLE: "Password Revealer",
  82. MENU_CMD_FOCUS: "「Reveal On Focus」Mode",
  83. MENU_CMD_HOVER: "「Preview On Hover」Mode",
  84. MENU_CMD_DBLCLICK: "「Toggle On Double-Click」Mode",
  85. MENU_CMD_ALWAYS_SHOW: "「Always Visible」Mode",
  86. ALERT_MSG_FOCUS: "Mode Switched To 「Reveal On Focus」",
  87. ALERT_MSG_HOVER: "Mode Switched To 「Preview On Hover」",
  88. ALERT_MSG_DBLCLICK: "Mode Switched To 「Toggle On Double-Click」",
  89. ALERT_MSG_ALWAYS_SHOW: "Mode Switched To 「Always Visible」",
  90. },
  91. },
  92. MODE_TO_MENU_TEXT_KEY_MAP: {
  93. ["Focus"]: "MENU_CMD_FOCUS",
  94. ["Hover"]: "MENU_CMD_HOVER",
  95. ["DoubleClick"]: "MENU_CMD_DBLCLICK",
  96. ["AlwaysShow"]: "MENU_CMD_ALWAYS_SHOW",
  97. },
  98. STORAGE_KEYS: {
  99. MODE_KEY: "PasswordDisplayMode",
  100. },
  101. MODE_TO_ALERT_MESSAGE_KEY_MAP: {
  102. ["Focus"]: "ALERT_MSG_FOCUS",
  103. ["Hover"]: "ALERT_MSG_HOVER",
  104. ["DoubleClick"]: "ALERT_MSG_DBLCLICK",
  105. ["AlwaysShow"]: "ALERT_MSG_ALWAYS_SHOW",
  106. },
  107. };
  108.  
  109. const State = {
  110. currentMode: Config.MODES.FOCUS,
  111. currentLocale: "en-US",
  112. localizedStrings: Config.UI_TEXTS["en-US"],
  113.  
  114. loadAndSetInitialState() {
  115. this.currentLocale = this.detectUserLanguage();
  116. this.localizedStrings =
  117. Config.UI_TEXTS[this.currentLocale] || Config.UI_TEXTS["en-US"];
  118. this.loadDisplayMode();
  119. },
  120.  
  121. detectUserLanguage() {
  122. const languages = navigator.languages || [navigator.language];
  123. for (const lang of languages) {
  124. const langLower = lang.toLowerCase();
  125. if (langLower === "zh-cn") return "zh-CN";
  126. if (
  127. langLower === "zh-tw" ||
  128. langLower === "zh-hk" ||
  129. langLower === "zh-mo" ||
  130. langLower === "zh-hant"
  131. )
  132. return "zh-TW";
  133. if (langLower === "en-us") return "en-US";
  134. if (langLower.startsWith("zh-")) return "zh-CN";
  135. if (langLower.startsWith("en-")) return "en-US";
  136. }
  137. for (const lang of languages) {
  138. const langLower = lang.toLowerCase();
  139. if (langLower.startsWith("zh")) return "zh-CN";
  140. if (langLower.startsWith("en")) return "en-US";
  141. }
  142. return "en-US";
  143. },
  144.  
  145. getLocalizedString(key, fallbackLang = "en-US") {
  146. const primaryLangData =
  147. this.localizedStrings || Config.UI_TEXTS[fallbackLang];
  148. const fallbackLangData = Config.UI_TEXTS[fallbackLang];
  149. return primaryLangData[key] ?? fallbackLangData[key] ?? `${key}?`;
  150. },
  151.  
  152. loadDisplayMode() {
  153. let storedValue;
  154. try {
  155. storedValue = GM_getValue(
  156. Config.STORAGE_KEYS.MODE_KEY,
  157. Config.MODES.FOCUS
  158. );
  159. } catch (e) {
  160. storedValue = Config.MODES.FOCUS;
  161. }
  162. if (!Config.VALID_MODES.includes(storedValue)) {
  163. storedValue = Config.MODES.FOCUS;
  164. }
  165. this.currentMode = storedValue;
  166. },
  167.  
  168. saveDisplayMode() {
  169. try {
  170. GM_setValue(Config.STORAGE_KEYS.MODE_KEY, this.currentMode);
  171. } catch (e) {}
  172. },
  173.  
  174. setMode(newMode) {
  175. if (
  176. this.currentMode === newMode ||
  177. !Config.VALID_MODES.includes(newMode)
  178. ) {
  179. return false;
  180. }
  181. this.currentMode = newMode;
  182. this.saveDisplayMode();
  183. return true;
  184. },
  185. };
  186.  
  187. const UserInterface = {
  188. notificationTimer: null,
  189. notificationRemovalTimer: null,
  190. registeredMenuCommandIds: [],
  191.  
  192. injectCoreStyles() {
  193. const easeOutQuint = "cubic-bezier(0.23, 1, 0.32, 1)";
  194. const animationDuration = Config.SCRIPT_SETTINGS.ANIMATION_DURATION_MS;
  195.  
  196. const baseCSS = `
  197. :root {
  198. --ctp-frappe-rosewater: rgb(242, 213, 207);
  199. --ctp-frappe-flamingo: rgb(238, 190, 190);
  200. --ctp-frappe-pink: rgb(244, 184, 228);
  201. --ctp-frappe-mauve: rgb(202, 158, 230);
  202. --ctp-frappe-red: rgb(231, 130, 132);
  203. --ctp-frappe-maroon: rgb(234, 153, 156);
  204. --ctp-frappe-peach: rgb(239, 159, 118);
  205. --ctp-frappe-yellow: rgb(229, 200, 144);
  206. --ctp-frappe-green: rgb(166, 209, 137);
  207. --ctp-frappe-teal: rgb(129, 200, 190);
  208. --ctp-frappe-sky: rgb(153, 209, 219);
  209. --ctp-frappe-sapphire: rgb(133, 193, 220);
  210. --ctp-frappe-blue: rgb(140, 170, 238);
  211. --ctp-frappe-lavender: rgb(186, 187, 241);
  212. --ctp-frappe-text: rgb(198, 208, 245);
  213. --ctp-frappe-subtext1: rgb(181, 191, 226);
  214. --ctp-frappe-subtext0: rgb(165, 173, 206);
  215. --ctp-frappe-overlay2: rgb(148, 156, 187);
  216. --ctp-frappe-overlay1: rgb(131, 139, 167);
  217. --ctp-frappe-overlay0: rgb(115, 121, 148);
  218. --ctp-frappe-surface2: rgb(98, 104, 128);
  219. --ctp-frappe-surface1: rgb(81, 87, 109);
  220. --ctp-frappe-surface0: rgb(65, 69, 89);
  221. --ctp-frappe-base: rgb(48, 52, 70);
  222. --ctp-frappe-mantle: rgb(41, 44, 60);
  223. --ctp-frappe-crust: rgb(35, 38, 52);
  224.  
  225. --ctp-latte-rosewater: rgb(220, 138, 120);
  226. --ctp-latte-flamingo: rgb(221, 120, 120);
  227. --ctp-latte-pink: rgb(234, 118, 203);
  228. --ctp-latte-mauve: rgb(136, 57, 239);
  229. --ctp-latte-red: rgb(210, 15, 57);
  230. --ctp-latte-maroon: rgb(230, 69, 83);
  231. --ctp-latte-peach: rgb(254, 100, 11);
  232. --ctp-latte-yellow: rgb(223, 142, 29);
  233. --ctp-latte-green: rgb(64, 160, 43);
  234. --ctp-latte-teal: rgb(23, 146, 153);
  235. --ctp-latte-sky: rgb(4, 165, 229);
  236. --ctp-latte-sapphire: rgb(32, 159, 181);
  237. --ctp-latte-blue: rgb(30, 102, 245);
  238. --ctp-latte-lavender: rgb(114, 135, 253);
  239. --ctp-latte-text: rgb(76, 79, 105);
  240. --ctp-latte-subtext1: rgb(92, 95, 119);
  241. --ctp-latte-subtext0: rgb(108, 111, 133);
  242. --ctp-latte-overlay2: rgb(124, 127, 147);
  243. --ctp-latte-overlay1: rgb(140, 143, 161);
  244. --ctp-latte-overlay0: rgb(156, 160, 176);
  245. --ctp-latte-surface2: rgb(172, 176, 190);
  246. --ctp-latte-surface1: rgb(188, 192, 204);
  247. --ctp-latte-surface0: rgb(204, 208, 218);
  248. --ctp-latte-base: rgb(239, 241, 245);
  249. --ctp-latte-mantle: rgb(230, 233, 239);
  250. --ctp-latte-crust: rgb(220, 224, 232);
  251.  
  252. --pr-notify-bg-dark: rgb(from var(--ctp-frappe-base) r g b / 0.85);
  253. --pr-notify-text-dark: var(--ctp-frappe-text);
  254. --pr-notify-border-dark: rgb(from var(--ctp-frappe-surface2) r g b / 0.25);
  255. --pr-notify-dot-color-dark: var(--ctp-frappe-green); /* Renamed from --ctp-frappe-green for clarity */
  256. --pr-notify-dot-glow-dark: rgb(from var(--ctp-frappe-green) r g b / 0.35); /* Glow for dot */
  257.  
  258.  
  259. --pr-notify-bg-light: rgb(from var(--ctp-latte-base) r g b / 0.85);
  260. --pr-notify-text-light: var(--ctp-latte-text);
  261. --pr-notify-border-light: rgb(from var(--ctp-latte-surface2) r g b / 0.25);
  262. --pr-notify-dot-color-light: var(--ctp-latte-green); /* Renamed from --ctp-latte-green for clarity */
  263. --pr-notify-dot-glow-light: rgb(from var(--ctp-latte-green) r g b / 0.35); /* Glow for dot */
  264.  
  265. --pr-shadow-dark:
  266. 0 1px 2px rgba(0, 0, 0, 0.1),
  267. 0 6px 12px rgba(0, 0, 0, 0.2);
  268. --pr-shadow-light:
  269. 0 1px 2px rgba(90, 90, 90, 0.06),
  270. 0 6px 12px rgba(90, 90, 90, 0.12);
  271. }
  272.  
  273. @keyframes pr-breathing-animation {
  274. 0%, 100% {
  275. transform: scale(0.85);
  276. opacity: 0.7;
  277. }
  278. 50% {
  279. transform: scale(1);
  280. opacity: 1;
  281. }
  282. }
  283.  
  284. #${Config.ELEMENT_IDS.MODE_NOTIFICATION} {
  285. position: fixed;
  286. bottom: 20px;
  287. left: 50%;
  288. z-index: 2147483646;
  289. display: flex;
  290. align-items: center;
  291. padding: 10px 16px;
  292. border: 1px solid var(--pr-notify-border-dark);
  293. border-radius: 20px;
  294. background-color: var(--pr-notify-bg-dark);
  295. color: var(--pr-notify-text-dark);
  296. box-shadow: var(--pr-shadow-dark);
  297. box-sizing: border-box;
  298. opacity: 0;
  299. font-family: ${Config.SCRIPT_SETTINGS.UI_FONT_STACK};
  300. text-align: left;
  301. backdrop-filter: blur(16px) saturate(180%);
  302. -webkit-backdrop-filter: blur(16px) saturate(180%);
  303. transform: translate(-50%, calc(100% + 40px));
  304. transition: transform ${animationDuration}ms ${easeOutQuint},
  305. opacity ${animationDuration * 0.8}ms ${easeOutQuint};
  306. }
  307.  
  308. #${Config.ELEMENT_IDS.MODE_NOTIFICATION}.${
  309. Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE
  310. } {
  311. transform: translate(-50%, 0);
  312. opacity: 1;
  313. }
  314.  
  315. #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${
  316. Config.CSS_CLASSES.BREATHING_DOT
  317. } {
  318. width: 8px;
  319. height: 8px;
  320. margin-right: 10px;
  321. border-radius: 50%;
  322. background-color: var(--pr-notify-dot-color-dark);
  323. box-shadow: 0 0 8px 3px var(--pr-notify-dot-glow-dark); /* Added glow */
  324. flex-shrink: 0;
  325. animation: pr-breathing-animation 2000ms ease-in-out infinite;
  326. /* No transition needed here as dot color doesn't change based on state for PR */
  327. }
  328.  
  329. #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${
  330. Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE
  331. } {
  332. color: var(--pr-notify-text-dark);
  333. font-size: 13px;
  334. font-weight: 500;
  335. line-height: 1.2;
  336. white-space: nowrap;
  337. overflow: hidden;
  338. text-overflow: ellipsis;
  339. }
  340.  
  341. @media (prefers-color-scheme: light) {
  342. #${Config.ELEMENT_IDS.MODE_NOTIFICATION} {
  343. border: 1px solid var(--pr-notify-border-light);
  344. background-color: var(--pr-notify-bg-light);
  345. color: var(--pr-notify-text-light);
  346. box-shadow: var(--pr-shadow-light);
  347. }
  348. #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${
  349. Config.CSS_CLASSES.BREATHING_DOT
  350. } {
  351. background-color: var(--pr-notify-dot-color-light);
  352. box-shadow: 0 0 8px 3px var(--pr-notify-dot-glow-light); /* Added glow for light mode */
  353. }
  354. #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${
  355. Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE
  356. } {
  357. color: var(--pr-notify-text-light);
  358. }
  359. }
  360. `;
  361. try {
  362. GM_addStyle(baseCSS);
  363. } catch (e) {}
  364. },
  365.  
  366. displayModeNotification(messageKey) {
  367. if (this.notificationTimer) clearTimeout(this.notificationTimer);
  368. if (this.notificationRemovalTimer)
  369. clearTimeout(this.notificationRemovalTimer);
  370. this.notificationTimer = null;
  371. this.notificationRemovalTimer = null;
  372.  
  373. const message = State.getLocalizedString(messageKey) || messageKey;
  374.  
  375. const renderNotification = () => {
  376. let notificationElement = document.getElementById(
  377. Config.ELEMENT_IDS.MODE_NOTIFICATION
  378. );
  379. if (!notificationElement && document.body) {
  380. notificationElement = document.createElement("div");
  381. notificationElement.id = Config.ELEMENT_IDS.MODE_NOTIFICATION;
  382. notificationElement.innerHTML = `
  383. <div class="${Config.CSS_CLASSES.BREATHING_DOT}"></div>
  384. <div class="${Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE}"></div>
  385. `.trim();
  386. document.body.appendChild(notificationElement);
  387. } else if (!notificationElement) {
  388. return;
  389. }
  390.  
  391. const messageElement = notificationElement.querySelector(
  392. `.${Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE}`
  393. );
  394.  
  395. if (messageElement) messageElement.textContent = message;
  396.  
  397. // PR script's dot color does not change based on a disabled class,
  398. // so no need to add/remove a .disabled class here for the dot.
  399. // The glow is always based on the primary dot color (green).
  400.  
  401. notificationElement.classList.remove(
  402. Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE
  403. );
  404. void notificationElement.offsetWidth;
  405.  
  406. requestAnimationFrame(() => {
  407. const currentElement = document.getElementById(
  408. Config.ELEMENT_IDS.MODE_NOTIFICATION
  409. );
  410. if (currentElement) {
  411. currentElement.classList.add(
  412. Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE
  413. );
  414. }
  415. });
  416.  
  417. this.notificationTimer = setTimeout(() => {
  418. const currentElement = document.getElementById(
  419. Config.ELEMENT_IDS.MODE_NOTIFICATION
  420. );
  421. if (currentElement) {
  422. currentElement.classList.remove(
  423. Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE
  424. );
  425. this.notificationRemovalTimer = setTimeout(() => {
  426. document
  427. .getElementById(Config.ELEMENT_IDS.MODE_NOTIFICATION)
  428. ?.remove();
  429. this.notificationTimer = null;
  430. this.notificationRemovalTimer = null;
  431. }, Config.SCRIPT_SETTINGS.ANIMATION_DURATION_MS);
  432. } else {
  433. this.notificationTimer = null;
  434. this.notificationRemovalTimer = null;
  435. }
  436. }, Config.SCRIPT_SETTINGS.NOTIFICATION_VISIBILITY_DURATION_MS);
  437. };
  438. renderNotification();
  439. },
  440.  
  441. updateUserScriptMenuCommands() {
  442. this.registeredMenuCommandIds.forEach((id) => {
  443. try {
  444. GM_unregisterMenuCommand(id);
  445. } catch (e) {}
  446. });
  447. this.registeredMenuCommandIds = [];
  448.  
  449. Config.VALID_MODES.forEach((mode) => {
  450. const menuKey = Config.MODE_TO_MENU_TEXT_KEY_MAP[mode];
  451. const baseText = State.getLocalizedString(menuKey);
  452. const commandText =
  453. baseText + (mode === State.currentMode ? " ✅" : "");
  454.  
  455. try {
  456. const commandId = GM_registerMenuCommand(commandText, () =>
  457. ScriptManager.setModeAndUpdate(mode)
  458. );
  459. this.registeredMenuCommandIds.push(commandId);
  460. } catch (e) {}
  461. });
  462. },
  463. };
  464.  
  465. const InputManager = {
  466. processPasswordInput(input, mode) {
  467. if (
  468. !(input instanceof HTMLInputElement) ||
  469. input.type === "hidden" ||
  470. input.getAttribute(Config.ATTRIBUTES.PROCESSED) === mode
  471. ) {
  472. return;
  473. }
  474.  
  475. if (mode === Config.MODES.ALWAYS_SHOW) {
  476. input.type = "text";
  477. } else {
  478. if (input.type !== "password") {
  479. input.type = "password";
  480. }
  481. }
  482. input.setAttribute(Config.ATTRIBUTES.PROCESSED, mode);
  483. },
  484.  
  485. findAndProcessNewInputs(rootNode, mode) {
  486. if (!rootNode || typeof rootNode.querySelectorAll !== "function") return;
  487. try {
  488. const query = `input[type="password"]:not([${Config.ATTRIBUTES.PROCESSED}="${mode}"]), input[${Config.ATTRIBUTES.PROCESSED}]:not([${Config.ATTRIBUTES.PROCESSED}="${mode}"])`;
  489. rootNode.querySelectorAll(query).forEach((input) => {
  490. this.processPasswordInput(input, mode);
  491. });
  492.  
  493. const elementsToCheckForShadow =
  494. rootNode === document ||
  495. rootNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE
  496. ? rootNode.querySelectorAll("*")
  497. : [rootNode];
  498.  
  499. elementsToCheckForShadow.forEach((el) => {
  500. if (
  501. el.shadowRoot &&
  502. typeof el.shadowRoot.querySelectorAll === "function"
  503. ) {
  504. this.findAndProcessNewInputs(el.shadowRoot, mode);
  505. }
  506. });
  507. } catch (e) {}
  508. },
  509.  
  510. applyCurrentModeToAllInputs() {
  511. try {
  512. this.findAndProcessNewInputs(document, State.currentMode);
  513. document.querySelectorAll("*").forEach((el) => {
  514. if (el.shadowRoot) {
  515. this.findAndProcessNewInputs(el.shadowRoot, State.currentMode);
  516. }
  517. });
  518. } catch (e) {}
  519. },
  520. };
  521.  
  522. const EventManager = {
  523. domMutationObserver: null,
  524.  
  525. handleShowPasswordOnHover(event) {
  526. const input = event.target;
  527. if (
  528. State.currentMode === Config.MODES.HOVER &&
  529. input instanceof HTMLInputElement &&
  530. input.matches(
  531. `input[type="password"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.HOVER}"]`
  532. )
  533. ) {
  534. input.type = "text";
  535. }
  536. },
  537.  
  538. handleHidePasswordOnLeave(event) {
  539. const input = event.target;
  540. if (
  541. State.currentMode === Config.MODES.HOVER &&
  542. input instanceof HTMLInputElement &&
  543. input.matches(
  544. `input[type="text"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.HOVER}"]`
  545. )
  546. ) {
  547. input.type = "password";
  548. }
  549. },
  550.  
  551. handleTogglePasswordOnDoubleClick(event) {
  552. const input = event.target;
  553. if (
  554. State.currentMode === Config.MODES.DBLCLICK &&
  555. input instanceof HTMLInputElement &&
  556. input.matches(
  557. `input[${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.DBLCLICK}"]`
  558. )
  559. ) {
  560. input.type = input.type === "password" ? "text" : "password";
  561. }
  562. },
  563.  
  564. handleFocusIn(event) {
  565. const input = event.target;
  566. if (
  567. State.currentMode === Config.MODES.FOCUS &&
  568. input instanceof HTMLInputElement &&
  569. input.matches(
  570. `input[type="password"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.FOCUS}"]`
  571. )
  572. ) {
  573. input.type = "text";
  574. }
  575. },
  576.  
  577. handleFocusOut(event) {
  578. const input = event.target;
  579. if (
  580. State.currentMode === Config.MODES.FOCUS &&
  581. input instanceof HTMLInputElement &&
  582. input.matches(
  583. `input[type="text"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.FOCUS}"]`
  584. )
  585. ) {
  586. input.type = "password";
  587. }
  588. },
  589.  
  590. handleKeyboardShortcut(event) {
  591. if (
  592. (event.ctrlKey || event.metaKey) &&
  593. event.altKey &&
  594. !event.shiftKey &&
  595. event.code === "KeyP"
  596. ) {
  597. event.preventDefault();
  598. event.stopPropagation();
  599.  
  600. const currentIndex = Config.VALID_MODES.indexOf(State.currentMode);
  601. const nextIndex = (currentIndex + 1) % Config.VALID_MODES.length;
  602. const nextMode = Config.VALID_MODES[nextIndex];
  603. ScriptManager.setModeAndUpdate(nextMode);
  604. }
  605. },
  606.  
  607. handleDOMMutation(mutationsList) {
  608. for (const mutation of mutationsList) {
  609. if (mutation.type === "childList") {
  610. mutation.addedNodes.forEach((node) => {
  611. if (node.nodeType !== Node.ELEMENT_NODE) return;
  612. try {
  613. InputManager.findAndProcessNewInputs(node, State.currentMode);
  614. } catch (e) {}
  615. });
  616. } else if (
  617. mutation.type === "attributes" &&
  618. mutation.attributeName === "type"
  619. ) {
  620. const targetInput = mutation.target;
  621. if (
  622. targetInput.nodeType === Node.ELEMENT_NODE &&
  623. targetInput.matches &&
  624. targetInput.matches('input[type="password"]') &&
  625. targetInput.getAttribute(Config.ATTRIBUTES.PROCESSED) !==
  626. State.currentMode
  627. ) {
  628. try {
  629. InputManager.processPasswordInput(targetInput, State.currentMode);
  630. } catch (e) {}
  631. }
  632. }
  633. }
  634. },
  635.  
  636. initializeDOMObserver() {
  637. if (this.domMutationObserver) return;
  638.  
  639. const observerOptions = {
  640. childList: true,
  641. subtree: true,
  642. attributes: true,
  643. attributeFilter: ["type"],
  644. };
  645. this.domMutationObserver = new MutationObserver(
  646. this.handleDOMMutation.bind(this)
  647. );
  648.  
  649. if (document.body) {
  650. try {
  651. this.domMutationObserver.observe(document.body, observerOptions);
  652. } catch (error) {
  653. this.domMutationObserver = null;
  654. }
  655. } else {
  656. document.addEventListener(
  657. "DOMContentLoaded",
  658. () => {
  659. if (document.body) {
  660. try {
  661. this.domMutationObserver.observe(
  662. document.body,
  663. observerOptions
  664. );
  665. } catch (error) {
  666. this.domMutationObserver = null;
  667. }
  668. }
  669. },
  670. { once: true }
  671. );
  672. }
  673. },
  674.  
  675. initializeGlobalEventListeners() {
  676. document.body.addEventListener(
  677. "mouseenter",
  678. this.handleShowPasswordOnHover.bind(this),
  679. true
  680. );
  681. document.body.addEventListener(
  682. "mouseleave",
  683. this.handleHidePasswordOnLeave.bind(this),
  684. true
  685. );
  686. document.body.addEventListener(
  687. "dblclick",
  688. this.handleTogglePasswordOnDoubleClick.bind(this)
  689. );
  690. document.addEventListener("focus", this.handleFocusIn.bind(this), true);
  691. document.addEventListener("blur", this.handleFocusOut.bind(this), true);
  692. document.addEventListener(
  693. "keydown",
  694. this.handleKeyboardShortcut.bind(this),
  695. true
  696. );
  697. },
  698.  
  699. init() {
  700. this.initializeGlobalEventListeners();
  701. this.initializeDOMObserver();
  702. },
  703. };
  704.  
  705. const ScriptManager = {
  706. init() {
  707. try {
  708. UserInterface.injectCoreStyles();
  709. State.loadAndSetInitialState();
  710. UserInterface.updateUserScriptMenuCommands();
  711. InputManager.applyCurrentModeToAllInputs();
  712. EventManager.init();
  713. } catch (error) {}
  714. },
  715.  
  716. setModeAndUpdate(newMode) {
  717. if (State.setMode(newMode)) {
  718. const alertMessageKey =
  719. Config.MODE_TO_ALERT_MESSAGE_KEY_MAP[State.currentMode];
  720. UserInterface.displayModeNotification(alertMessageKey);
  721. InputManager.applyCurrentModeToAllInputs();
  722. UserInterface.updateUserScriptMenuCommands();
  723. }
  724. },
  725. };
  726.  
  727. ScriptManager.init();
  728. })();

QingJ © 2025

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