密码显示助手

通过鼠标悬浮或双击来显示密码框内容 可通过脚本菜单切换触发方式

当前为 2025-04-15 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name               Password Revealer
// @name:zh-CN         密码显示助手
// @name:zh-TW         密碼顯示助手
// @description        Reveal Passwords By Hovering Or DoubleClicking The Input Field Switch Modes Via The Tampermonkey Menu
// @description:zh-CN  通过鼠标悬浮或双击来显示密码框内容 可通过脚本菜单切换触发方式
// @description:zh-TW  透過滑鼠懸浮或雙擊來顯示密碼框內容 可透過腳本選單切換觸發方式
// @version            1.1.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_addStyle
// ==/UserScript==

(function () {
  "use strict";

  const ModeKey = "PasswordDisplayMode";
  const ModeHover = "Hover";
  const ModeDBClick = "DoubleClick";
  const NotificationId = "password-revealer-notification";
  const NotificationTimeout = 2000;
  const AnimationDuration = 300;
  const ScriptIconUrl =
    "https://raw.githubusercontent.com/MiPoNianYou/UserScripts/refs/heads/main/Icons/PasswordRevealerIcon.svg";

  const ScriptTitles = {
    "en-US": "Password Revealer",
    "zh-CN": "密码显示助手",
    "zh-TW": "密碼顯示助手",
  };

  const MenuCommandTexts = {
    "en-US": "Toggle Password Display Mode",
    "zh-CN": "切换密码显示模式",
    "zh-TW": "切換密碼顯示模式",
  };

  const AlertMessages = {
    "en-US": {
      [ModeHover]: "Mode Switched To 「Hover」",
      [ModeDBClick]: "Mode Switched To 「Double Click」",
    },
    "zh-CN": {
      [ModeHover]: "模式已切换为:悬浮显示",
      [ModeDBClick]: "模式已切换为:双击切换",
    },
    "zh-TW": {
      [ModeHover]: "模式已切換為:懸浮顯示",
      [ModeDBClick]: "模式已切換為:雙擊切換",
    },
  };

  function InjectNotificationStyles() {
    GM_addStyle(`
      #${NotificationId} {
        position: fixed;
        top: 20px;
        right: -400px;
        width: 300px;
        background-color: rgba(240, 240, 240, 0.9);
        color: #333;
        padding: 10px;
        border-radius: 10px;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
        z-index: 99999;
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
        display: flex;
        align-items: flex-start;
        opacity: 0;
        transition: right ${AnimationDuration}ms ease-out, opacity ${
      AnimationDuration * 0.8
    }ms ease-out;
        box-sizing: border-box;
        backdrop-filter: blur(8px);
        -webkit-backdrop-filter: blur(8px);
      }
      #${NotificationId}.visible {
        right: 20px;
        opacity: 1;
      }
      #${NotificationId} .pr-icon {
        width: 32px;
        height: 32px;
        margin-right: 10px;
        flex-shrink: 0;
      }
      #${NotificationId} .pr-content {
        display: flex;
        flex-direction: column;
        flex-grow: 1;
        min-width: 0;
      }
      #${NotificationId} .pr-title {
        font-size: 13px;
        font-weight: 600;
        margin-bottom: 2px;
        color: #111;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      #${NotificationId} .pr-message {
        font-size: 12px;
        line-height: 1.3;
        color: #444;
         word-wrap: break-word;
         overflow-wrap: break-word;
      }
      @media (prefers-color-scheme: dark) {
          #${NotificationId} {
              background-color: rgba(50, 50, 50, 0.85);
              color: #eee;
              box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
          }
          #${NotificationId} .pr-title {
              color: #f0f0f0;
          }
          #${NotificationId} .pr-message {
               color: #ccc;
          }
      }
    `);
  }

  let NotificationTimer = null;
  let RemovalTimer = null;

  function ShowNotification(Message) {
    if (NotificationTimer) clearTimeout(NotificationTimer);
    if (RemovalTimer) clearTimeout(RemovalTimer);

    const ExistingNotification = document.getElementById(NotificationId);
    if (ExistingNotification) {
      ExistingNotification.remove();
    }

    const NotificationElement = document.createElement("div");
    NotificationElement.id = NotificationId;

    const LocalizedTitle = GetLocalizedScriptTitle();

    NotificationElement.innerHTML = `
      <img src="${ScriptIconUrl}" alt="Icon" class="pr-icon">
      <div class="pr-content">
        <div class="pr-title">${LocalizedTitle}</div>
        <div class="pr-message">${Message}</div>
      </div>
    `;

    document.body.appendChild(NotificationElement);

    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        NotificationElement.classList.add("visible");
      });
    });

    NotificationTimer = setTimeout(() => {
      NotificationElement.classList.remove("visible");
      RemovalTimer = setTimeout(() => {
        if (NotificationElement.parentNode) {
          NotificationElement.remove();
        }
        NotificationTimer = null;
        RemovalTimer = null;
      }, AnimationDuration);
    }, NotificationTimeout);
  }

  function GetLanguageKey() {
    const Lang = navigator.language;
    if (Lang.startsWith("zh")) {
      if (Lang === "zh-TW" || Lang === "zh-HK" || Lang === "zh-Hant") {
        return "zh-TW";
      } else {
        return "zh-CN";
      }
    } else {
      if (Lang.startsWith("en")) {
        return "en-US";
      }
      return "en-US";
    }
  }

  function GetLocalizedScriptTitle() {
    const LangKey = GetLanguageKey();
    return ScriptTitles[LangKey] || ScriptTitles["en-US"];
  }

  function GetLocalizedMenuCommandText() {
    const LangKey = GetLanguageKey();
    return MenuCommandTexts[LangKey] || MenuCommandTexts["en-US"];
  }

  function GetLocalizedAlertMessage(Mode) {
    const LangKey = GetLanguageKey();
    const LangMessages = AlertMessages[LangKey] || AlertMessages["en-US"];
    return LangMessages[Mode] || `Mode: ${Mode}`;
  }

  let CurrentMode = GM_getValue(ModeKey, ModeHover);
  const LocalizedCommandText = GetLocalizedMenuCommandText();

  function ShowPasswordOnHover() {
    this.type = "text";
  }

  function HidePasswordOnLeave() {
    this.type = "password";
  }

  function TogglePasswordOnDoubleClick() {
    this.type = this.type === "password" ? "text" : "password";
  }

  function ApplyHoverBehavior(Input) {
    Input.addEventListener("mouseenter", ShowPasswordOnHover);
    Input.addEventListener("mouseleave", HidePasswordOnLeave);
    Input.removeEventListener("dblclick", TogglePasswordOnDoubleClick);
  }

  function RemoveHoverBehavior(Input) {
    Input.removeEventListener("mouseenter", ShowPasswordOnHover);
    Input.removeEventListener("mouseleave", HidePasswordOnLeave);
  }

  function ApplyDoubleClickBehavior(Input) {
    Input.addEventListener("dblclick", TogglePasswordOnDoubleClick);
    Input.removeEventListener("mouseenter", ShowPasswordOnHover);
    Input.removeEventListener("mouseleave", HidePasswordOnLeave);
  }

  function RemoveDoubleClickBehavior(Input) {
    Input.removeEventListener("dblclick", TogglePasswordOnDoubleClick);
  }

  function ProcessPasswordInput(Input, Mode) {
    RemoveHoverBehavior(Input);
    RemoveDoubleClickBehavior(Input);

    if (Mode === ModeHover) {
      ApplyHoverBehavior(Input);
    } else if (Mode === ModeDBClick) {
      ApplyDoubleClickBehavior(Input);
    }
    Input.dataset.passwordProcessed = Mode;
  }

  function ToggleMode() {
    const OldMode = CurrentMode;
    const NewMode = OldMode === ModeHover ? ModeDBClick : ModeHover;
    GM_setValue(ModeKey, NewMode);
    CurrentMode = NewMode;

    const AlertMessage = GetLocalizedAlertMessage(NewMode);
    ShowNotification(AlertMessage);

    document.querySelectorAll('input[type="password"]').forEach((Input) => {
      ProcessPasswordInput(Input, NewMode);
    });
  }

  InjectNotificationStyles();

  document
    .querySelectorAll('input[type="password"]')
    .forEach((Input) => ProcessPasswordInput(Input, CurrentMode));

  const Observer = new MutationObserver((Mutations) => {
    Mutations.forEach((Mutation) => {
      if (Mutation.addedNodes && Mutation.addedNodes.length > 0) {
        Mutation.addedNodes.forEach((Node) => {
          if (
            Node.nodeType === Node.ELEMENT_NODE &&
            Node.tagName === "INPUT" &&
            Node.type === "password" &&
            !Node.dataset.passwordProcessed
          ) {
            ProcessPasswordInput(Node, CurrentMode);
          } else if (
            Node.nodeType === Node.ELEMENT_NODE &&
            Node.querySelectorAll
          ) {
            const PasswordInputs = Node.querySelectorAll(
              'input[type="password"]:not([data-password-processed])'
            );
            PasswordInputs.forEach((Input) =>
              ProcessPasswordInput(Input, CurrentMode)
            );
          }
        });
      }
    });
  });

  Observer.observe(document.body, {
    childList: true,
    subtree: true,
  });

  GM_registerMenuCommand(LocalizedCommandText, ToggleMode);
})();