// ==UserScript== // @name Password Revealer // @name:zh-CN 密码显示助手 // @name:zh-TW 密碼顯示助手 // @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) // @description:zh-CN 密码输入框内容可聚焦即显 / 悬浮即览 / 双击切换 / 始终可见 | 通过菜单或快捷键(Meta/Ctrl+Alt+P)切换显示模式 // @description:zh-TW 密碼輸入框內容可聚焦即顯 / 懸停即覽 / 雙擊切換 / 始終可見 | 透過選單或快速鍵(Meta/Ctrl+Alt+P)切換顯示模式 // @version 1.4.0 // @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/PasswordRevealerIcon.svg // @author 念柚 // @namespace https://github.com/MiPoNianYou/UserScripts // @supportURL https://github.com/MiPoNianYou/UserScripts/issues // @license GPL-3.0 // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_addStyle // @run-at document-idle // @downloadURL none // ==/UserScript== (function () { "use strict"; const SCRIPT_SETTINGS = { UI_FONT_STACK: "-apple-system, BlinkMacSystemFont, system-ui, sans-serif", ANIMATION_DURATION_MS: 300, NOTIFICATION_VISIBILITY_DURATION_MS: 2000, }; const MODES = { FOCUS: "Focus", HOVER: "Hover", DBLCLICK: "DoubleClick", ALWAYS_SHOW: "AlwaysShow", }; const VALID_MODES = [ MODES.FOCUS, MODES.HOVER, MODES.DBLCLICK, MODES.ALWAYS_SHOW, ]; const ELEMENT_IDS = { MODE_NOTIFICATION: "PasswordRevealerModeNotification", }; const CSS_CLASSES = { MODE_NOTIFICATION_VISIBLE: "pr-mode-notification--visible", MODE_NOTIFICATION_ICON: "pr-mode-notification-icon", MODE_NOTIFICATION_CONTENT: "pr-mode-notification-content", MODE_NOTIFICATION_TITLE: "pr-mode-notification-title", MODE_NOTIFICATION_MESSAGE: "pr-mode-notification-message", }; const STORAGE_KEYS = { MODE_KEY: "PasswordDisplayMode", }; const SVG_ICON_MARKUP = ` `.trim(); const ATTRIBUTES = { PROCESSED: "data-password-revealer-processed", }; const UI_TEXTS = { "zh-CN": { SCRIPT_TITLE: "密码显示助手", MENU_CMD_FOCUS: "「聚焦即显」模式", MENU_CMD_HOVER: "「悬浮即览」模式", MENU_CMD_DBLCLICK: "「双击切换」模式", MENU_CMD_ALWAYS_SHOW: "「始终可见」模式", ALERT_MSG_FOCUS: "模式已切换为「聚焦即显」", ALERT_MSG_HOVER: "模式已切换为「悬浮即览」", ALERT_MSG_DBLCLICK: "模式已切换为「双击切换」", ALERT_MSG_ALWAYS_SHOW: "模式已切换为「始终可见」", }, "zh-TW": { SCRIPT_TITLE: "密碼顯示助手", MENU_CMD_FOCUS: "「聚焦即顯」模式", MENU_CMD_HOVER: "「懸停即覽」模式", MENU_CMD_DBLCLICK: "「雙擊切換」模式", MENU_CMD_ALWAYS_SHOW: "「始終可見」模式", ALERT_MSG_FOCUS: "模式已切換為「聚焦即顯」", ALERT_MSG_HOVER: "模式已切換為「懸停即覽」", ALERT_MSG_DBLCLICK: "模式已切換為「雙擊切換」", ALERT_MSG_ALWAYS_SHOW: "模式已切換為「始終可見」", }, "en-US": { SCRIPT_TITLE: "Password Revealer", MENU_CMD_FOCUS: "「Reveal On Focus」Mode", MENU_CMD_HOVER: "「Preview On Hover」Mode", MENU_CMD_DBLCLICK: "「Toggle On Double-Click」Mode", MENU_CMD_ALWAYS_SHOW: "「Always Visible」Mode", ALERT_MSG_FOCUS: "Mode Switched To 「Reveal On Focus」", ALERT_MSG_HOVER: "Mode Switched To 「Preview On Hover」", ALERT_MSG_DBLCLICK: "Mode Switched To 「Toggle On Double-Click」", ALERT_MSG_ALWAYS_SHOW: "Mode Switched To 「Always Visible」", }, }; const MODE_MENU_TEXT_KEYS = { [MODES.FOCUS]: "MENU_CMD_FOCUS", [MODES.HOVER]: "MENU_CMD_HOVER", [MODES.DBLCLICK]: "MENU_CMD_DBLCLICK", [MODES.ALWAYS_SHOW]: "MENU_CMD_ALWAYS_SHOW", }; const MODE_ALERT_TEXT_KEYS = { [MODES.FOCUS]: "ALERT_MSG_FOCUS", [MODES.HOVER]: "ALERT_MSG_HOVER", [MODES.DBLCLICK]: "ALERT_MSG_DBLCLICK", [MODES.ALWAYS_SHOW]: "ALERT_MSG_ALWAYS_SHOW", }; let currentMode = MODES.HOVER; let currentLocale = "en-US"; let localizedStrings = UI_TEXTS["en-US"]; let registeredMenuCommandIds = []; let notificationTimer = null; let notificationRemovalTimer = null; let domMutationObserver = null; function injectCoreUIStyles() { const appleEaseOutQuint = "cubic-bezier(0.23, 1, 0.32, 1)"; const animationDuration = SCRIPT_SETTINGS.ANIMATION_DURATION_MS; const baseCSS = ` :root { --ctp-frappe-rosewater: #f2d5cf; --ctp-frappe-flamingo: #eebebe; --ctp-frappe-pink: #f4b8e4; --ctp-frappe-mauve: #ca9ee6; --ctp-frappe-red: #e78284; --ctp-frappe-maroon: #ea999c; --ctp-frappe-peach: #ef9f76; --ctp-frappe-yellow: #e5c890; --ctp-frappe-green: #a6d189; --ctp-frappe-teal: #81c8be; --ctp-frappe-sky: #99d1db; --ctp-frappe-sapphire: #85c1dc; --ctp-frappe-blue: #8caaee; --ctp-frappe-lavender: #babbf1; --ctp-frappe-text: #c6d0f5; --ctp-frappe-subtext1: #b5bfe2; --ctp-frappe-subtext0: #a5adce; --ctp-frappe-overlay2: #949cbb; --ctp-frappe-overlay1: #838ba7; --ctp-frappe-overlay0: #737994; --ctp-frappe-surface2: #626880; --ctp-frappe-surface1: #51576d; --ctp-frappe-surface0: #414559; --ctp-frappe-base: #303446; --ctp-frappe-mantle: #292c3c; --ctp-frappe-crust: #232634; --ctp-latte-rosewater: #dc8a78; --ctp-latte-flamingo: #dd7878; --ctp-latte-pink: #ea76cb; --ctp-latte-mauve: #8839ef; --ctp-latte-red: #d20f39; --ctp-latte-maroon: #e64553; --ctp-latte-peach: #fe640b; --ctp-latte-yellow: #df8e1d; --ctp-latte-green: #40a02b; --ctp-latte-teal: #179299; --ctp-latte-sky: #04a5e5; --ctp-latte-sapphire: #209fb5; --ctp-latte-blue: #1e66f5; --ctp-latte-lavender: #7287fd; --ctp-latte-text: #4c4f69; --ctp-latte-subtext1: #5c5f77; --ctp-latte-subtext0: #6c6f85; --ctp-latte-overlay2: #7c7f93; --ctp-latte-overlay1: #8c8fa1; --ctp-latte-overlay0: #9ca0b0; --ctp-latte-surface2: #acb0be; --ctp-latte-surface1: #bcc0cc; --ctp-latte-surface0: #ccd0da; --ctp-latte-base: #eff1f5; --ctp-latte-mantle: #e6e9ef; --ctp-latte-crust: #dce0e8; --pr-notify-bg-dark: rgba(48, 52, 70, 0.88); --pr-notify-text-dark: var(--ctp-frappe-text); --pr-notify-title-dark: var(--ctp-frappe-text); --pr-icon-color-dark: var(--ctp-frappe-text); --pr-border-dark: rgba(98, 104, 128, 0.2); --pr-notify-bg-light: rgba(239, 241, 245, 0.88); --pr-notify-text-light: var(--ctp-latte-text); --pr-notify-title-light: var(--ctp-latte-text); --pr-icon-color-light: var(--ctp-latte-text); --pr-border-light: rgba(172, 176, 190, 0.2); --pr-shadow-dark: 0 1px 3px rgba(0, 0, 0, 0.15), 0 8px 15px rgba(0, 0, 0, 0.25); --pr-shadow-light: 0 1px 3px rgba(90, 90, 90, 0.08), 0 8px 15px rgba(90, 90, 90, 0.12); } #${ELEMENT_IDS.MODE_NOTIFICATION} { position: fixed; top: 20px; right: -400px; z-index: 2147483646; display: flex; align-items: center; width: 310px; padding: 14px 18px; border: 1px solid var(--pr-border-dark); border-radius: 14px; background-color: var(--pr-notify-bg-dark); color: var(--pr-notify-text-dark); box-shadow: var(--pr-shadow-dark); box-sizing: border-box; opacity: 0; font-family: ${SCRIPT_SETTINGS.UI_FONT_STACK}; text-align: left; backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); transition: right ${animationDuration}ms ${appleEaseOutQuint}, opacity ${animationDuration * 0.8}ms ${appleEaseOutQuint}; } #${ELEMENT_IDS.MODE_NOTIFICATION}.${ CSS_CLASSES.MODE_NOTIFICATION_VISIBLE } { right: 20px; opacity: 1; } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_ICON } { display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; margin-right: 14px; flex-shrink: 0; color: var(--pr-icon-color-dark); } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_ICON } svg { display: block; width: 100%; height: 100%; } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_CONTENT } { display: flex; flex-direction: column; flex-grow: 1; min-width: 0; } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_TITLE } { margin-bottom: 4px; color: var(--pr-notify-title-dark); font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_MESSAGE } { color: var(--pr-notify-text-dark); font-size: 13px; line-height: 1.45; word-wrap: break-word; overflow-wrap: break-word; } @media (prefers-color-scheme: light) { #${ELEMENT_IDS.MODE_NOTIFICATION} { border: 1px solid var(--pr-border-light); background-color: var(--pr-notify-bg-light); color: var(--pr-notify-text-light); box-shadow: var(--pr-shadow-light); } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_ICON } { color: var(--pr-icon-color-light); } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_TITLE } { color: var(--pr-notify-title-light); } #${ELEMENT_IDS.MODE_NOTIFICATION} .${ CSS_CLASSES.MODE_NOTIFICATION_MESSAGE } { color: var(--pr-notify-text-light); } } `; try { GM_addStyle(baseCSS); } catch (e) {} } function detectUserLanguage() { const languages = navigator.languages || [navigator.language]; for (const lang of languages) { const langLower = lang.toLowerCase(); if (langLower === "zh-cn") return "zh-CN"; if ( langLower === "zh-tw" || langLower === "zh-hk" || langLower === "zh-mo" || langLower === "zh-hant" ) return "zh-TW"; if (langLower === "en-us") return "en-US"; if (langLower.startsWith("zh-")) return "zh-CN"; if (langLower.startsWith("en-")) return "en-US"; } for (const lang of languages) { const langLower = lang.toLowerCase(); if (langLower.startsWith("zh")) return "zh-CN"; if (langLower.startsWith("en")) return "en-US"; } return "en-US"; } function getLocalizedString(key, fallbackLang = "en-US") { const primaryLangData = localizedStrings || UI_TEXTS[fallbackLang]; const fallbackLangData = UI_TEXTS[fallbackLang]; return primaryLangData[key] ?? fallbackLangData[key] ?? `${key}?`; } function loadDisplayMode() { let storedValue; try { storedValue = GM_getValue(STORAGE_KEYS.MODE_KEY, MODES.HOVER); } catch (e) { storedValue = MODES.HOVER; } if (!VALID_MODES.includes(storedValue)) { storedValue = MODES.HOVER; } currentMode = storedValue; } function saveDisplayMode() { try { GM_setValue(STORAGE_KEYS.MODE_KEY, currentMode); } catch (e) {} } function displayModeNotification(messageKey) { if (notificationTimer) clearTimeout(notificationTimer); if (notificationRemovalTimer) clearTimeout(notificationRemovalTimer); notificationTimer = null; notificationRemovalTimer = null; const title = getLocalizedString("SCRIPT_TITLE"); const message = getLocalizedString(messageKey) || messageKey; const renderNotification = () => { let notificationElement = document.getElementById( ELEMENT_IDS.MODE_NOTIFICATION ); if (!notificationElement && document.body) { notificationElement = document.createElement("div"); notificationElement.id = ELEMENT_IDS.MODE_NOTIFICATION; notificationElement.innerHTML = `