Greasy Fork

Greasy Fork is available in English.

共享账号搜索

获取共享账号并自动填写

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         共享账号搜索
// @description  获取共享账号并自动填写
// @namespace    http://tampermonkey.net/
// @connect        bugmenot.com
// @connect        freeaccount.biz
// @connect        password-login.com
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_setClipboard
// @version      1.7
// @author       Hồng Minh Tâm & Gemini
// @license      GPLv3
// @icon        data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAA6lBMVEVHcEz9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH7SD/9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkH9SkEk7mAFAAAATXRSTlMABrJ4kx/v8ffNAVx0RPn4qGVZv/uIPa7EdiAOYIqh3DjX4VdjuPPoE+5YTtSHYsB5gMj6FDsjpEDTZjzdmsIanuT0BdqLnAsERlPskaOVQUwAAACxSURBVBjTbc/VDsJQEATQKdTQFooVK+7u7g77/7/D7SUBEpiHTc68TBb4m2wGyBe/ikJOLlVrH1fKxKKM3kWdeAIvBUVh0LC9WAtikBUxWk615nAeJutEMVZIphqaAIKDQpopAXFpv9twO8P+sxSHoh7Iw40rWaoCMX2kADdu9EiL9s52xQ39fmE3kYyySdszomgygRTJ3jG5up026V6ZUogYPrg9PX/f1XLDZ0R+Hn8C9iYYIKWYFpsAAAAASUVORK5CYII=
// ==/UserScript==

(function () {
  'use strict';

  const icons = {
    mail: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"></path></svg>'
  }

  GM_addStyle([
    '.bmn-list { display:none; list-style: none; border: 1px solid rgba(128, 128, 128, 0.5); padding: 0; margin: 0; background-color: rgb(58, 58, 58); color: rgb(203, 203, 203); position: fixed; cursor: default; z-index: 9999999999; box-sizing: border-box; overflow: auto; text-align: left; width: 300px; }',
    '.bmn-list.show { display:block; }',
    // --- 账号数据项样式 ---
    '.bmn-list .bmn-item { position: relative; padding: 8px 10px 8px 15px; margin: 0; cursor: pointer; border-bottom: 1px solid rgba(128, 128, 128, 0.2); font-size: 9pt; line-height: 1.4;}',
    '.bmn-list .bmn-item:last-child { border-bottom: 0; }',
    '.bmn-list .bmn-item:hover { background-color: rgba(255, 255, 255, 0.1); }',
    '.bmn-list .bmn-item:before { position: absolute; content: ""; width: 5px; top: 0; left: 0; bottom: 0; background-color: #f7704f; }',
    '.bmn-list .bmn-item .bmn-label { font-weight: 600; }',
    '.bmn-list .bmn-item .bmn-username { color: rgb(246, 182, 78); }',
    '.bmn-list .bmn-item .bmn-password { color: rgb(118, 202, 83); }',
    '.bmn-list .bmn-item .bmn-email-entry { cursor: pointer; }',
    '.bmn-list .bmn-item .bmn-email-entry .bmn-value { color: rgb(138, 180, 248); text-decoration: none; }',
    '.bmn-list .bmn-item .bmn-email-entry:hover .bmn-value { text-decoration: underline; }',
    '.bmn-list .bmn-item .bmn-success-rate { float: right; font-weight: 700; margin-left: 10px; }',
    '.bmn-list .bmn-item.bmn-success-100 .bmn-success-rate { color: rgb(0,198,0); }',
    '.bmn-list .bmn-item.bmn-success-100:before { background-color: rgb(0,198,0); }',
    '.bmn-list .bmn-item.bmn-success-90 .bmn-success-rate { color: rgb(50,180,0); }',
    '.bmn-list .bmn-item.bmn-success-90:before { background-color: rgb(50,180,0); }',
    '.bmn-list .bmn-item.bmn-success-80 .bmn-success-rate { color: rgb(99,164,0); }',
    '.bmn-list .bmn-item.bmn-success-80:before { background-color: rgb(99,164,0); }',
    '.bmn-list .bmn-item.bmn-success-70 .bmn-success-rate { color: rgb(149,146,0); }',
    '.bmn-list .bmn-item.bmn-success-70:before { background-color: rgb(149,146,0); }',
    '.bmn-list .bmn-item.bmn-success-60 .bmn-success-rate { color: rgb(199,129,0); }',
    '.bmn-list .bmn-item.bmn-success-60:before { background-color: rgb(199,129,0); }',
    '.bmn-list .bmn-item.bmn-success-50 .bmn-success-rate { color: rgb(247,112,0); }',
    '.bmn-list .bmn-item.bmn-success-50:before { background-color: rgb(247,112,0); }',
    '.bmn-list .bmn-item.bmn-success-40 .bmn-success-rate { color: rgb(247,90,0); }',
    '.bmn-list .bmn-item.bmn-success-40:before { background-color: rgb(247,90,0); }',
    '.bmn-list .bmn-item.bmn-success-30 .bmn-success-rate { color: rgb(247,67,0); }',
    '.bmn-list .bmn-item.bmn-success-30:before { background-color: rgb(247,67,0); }',
    '.bmn-list .bmn-item.bmn-success-20 .bmn-success-rate { color: rgb(247,45,0); }',
    '.bmn-list .bmn-item.bmn-success-20:before { background-color: rgb(247,45,0); }',
    '.bmn-list .bmn-item.bmn-success-10 .bmn-success-rate { color: rgb(247,22,0); }',
    '.bmn-list .bmn-item.bmn-success-10:before { background-color: rgb(247,22,0); }',
    '.bmn-list .bmn-no-logins-found, .bmn-list .bmn-loading, .bmn-list .bmn-error { padding: 10px 15px; margin: 0; cursor: default; text-align: center; color: #fff; font-size: 9pt; }',
    '.bmn-list .bmn-no-logins-found { background-color: #555; }',
    '.bmn-list .bmn-loading { background-color: #007bff; }',
    '.bmn-list .bmn-error { background-color: #a90000; }',

    // --- 分隔行样式 ---
    '.bmn-separator { background-color: #2C2C2C; padding: 6px 10px; font-size: 9pt; line-height: 1.4; border-bottom: 1px solid rgba(128, 128, 128, 0.2); display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; }',
    '.bmn-separator:hover { background-color: #444; }',
    '.bmn-separator a { color: rgb(178, 139, 247) !important; text-decoration: none; cursor: pointer; }',
    '.bmn-separator a:hover { text-decoration: underline; }',
    '.bmn-separator .bmn-count { color: #aaa; font-size: 9pt; }',

    // --- 顶部关闭栏样式 ---
    '.bmn-header { position: sticky; top: 0; z-index: 1000; background-color: #242424; padding: 8px 10px; border-bottom: 1px solid #555; display: flex; justify-content: space-between; align-items: center; font-size: 10pt; font-weight: bold; color: #fff; }',
    '.bmn-close-btn { cursor: pointer; font-size: 14pt; line-height: 1; color: #aaa; padding: 0 5px; }',
    '.bmn-close-btn:hover { color: #fff; }',

    // --- 分组容器 ---
    '.bmn-group-container { display: none; }',
    '.bmn-group-container.expanded { display: block; }',

    // --- 按钮样式 ---
    '.bmn-floating-button { position: fixed !important; background: transparent !important; border: none !important; cursor: pointer !important; padding: 2px !important; display: none; align-items: center; justify-content: center; z-index: 999999 !important; opacity: 0.5; transition: opacity 0.2s ease, transform 0.2s ease !important; color: grey !important; pointer-events: auto !important; margin: 0 !important; width: 24px; height: 24px; box-sizing: border-box !important; }',
    '.bmn-floating-button:hover { opacity: 1 !important; transform: scale(1.1); }',
    '.bmn-floating-button svg { width: 18px; height: 18px; display: block; }',
    '.bmn-list::-webkit-scrollbar { width: 8px; }',
    '.bmn-list::-webkit-scrollbar-track { background: rgb(44, 44, 44); }',
    '.bmn-list::-webkit-scrollbar-thumb { background-color: rgb(159, 159, 159); border-radius: 4px; }',
    '.bmn-list::-webkit-scrollbar-thumb:hover { background-color: rgb(190, 190, 190); }',

    // --- Toast 提示样式 ---
    '.bmn-toast { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.8); color: white; padding: 8px 16px; border-radius: 4px; font-size: 14px; z-index: 9999999999; opacity: 0; transition: opacity 0.3s; pointer-events: none; }',
    '.bmn-toast.show { opacity: 1; }'
  ].join(''));

  // --- 工具函数 ---

  Object.defineProperty(String.prototype, 'toDOM', {
    value: function (isFull) {
      var parser = new DOMParser(),
        dom = parser.parseFromString(this, 'text/html');
      return isFull ? dom : dom.body.childNodes[0];
    },
    enumerable: false
  });

  function debounce(func, wait) {
    let timeout;
    return function(...args) {
      const context = this;
      clearTimeout(timeout);
      timeout = setTimeout(() => func.apply(context, args), wait);
    };
  }

  function setValueInput(input, value, isInputSimulate) {
    var setValue = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
    setValue.call(input, value);
    if (isInputSimulate) {
      var e = new Event('input', {
        bubbles: true
      });
      input.dispatchEvent(e);
    }
  }

  function getOffset(element) {
    var elementRect = element.getBoundingClientRect();
    return {
      left: elementRect.left,
      right: elementRect.right,
      top: elementRect.top,
      bottom: elementRect.bottom,
      width: elementRect.width,
      height: elementRect.height
    };
  }

  function handleEvent(func, data) {
    return function (event) {
      func.bind(this)(event, data);
    };
  }

  function isVisible(el) {
      return !!(el && (el.offsetWidth || el.offsetHeight || el.getClientRects().length));
  }

  function getRootDomain(hostname) {
    const parts = hostname.split('.');
    if (parts.length <= 2) return hostname;

    const sld = parts[parts.length - 2];
    const secondLevelDomains = ['com', 'co', 'net', 'org', 'edu', 'gov', 'mil', 'ac'];

    if (secondLevelDomains.includes(sld)) {
        return parts.slice(-3).join('.');
    }
    return parts.slice(-2).join('.');
  }

  function cfDecodeEmail(encodedString) {
      var email = "", r = parseInt(encodedString.substr(0, 2), 16), n, i;
      for (n = 2; encodedString.length - n; n += 2) {
          i = parseInt(encodedString.substr(n, 2), 16) ^ r;
          email += String.fromCharCode(i);
      }
      return email;
  }

  function getTextContentWithCF(cell) {
      if (!cell) return '';
      const cfElem = cell.querySelector('[data-cfemail]');
      if (cfElem) {
          return cfDecodeEmail(cfElem.getAttribute('data-cfemail'));
      }
      return cell.innerText.trim();
  }

  function copyToClipboard(text) {
      if (!text) return;
      GM_setClipboard(text);
      showToast(`已复制: ${text.length > 20 ? text.substring(0, 20) + '...' : text}`);
  }

  function showToast(message) {
      let toast = document.querySelector('.bmn-toast');
      if (!toast) {
          toast = document.createElement('div');
          toast.className = 'bmn-toast';
          document.body.appendChild(toast);
      }
      toast.innerText = message;
      toast.classList.add('show');
      setTimeout(() => {
          toast.classList.remove('show');
      }, 2000);
  }

  // [新增] Shadow DOM 穿透辅助函数
  function diveIntoShadow(element) {
      let current = element;
      let depth = 0;
      const maxDepth = 20;

      while (current && current.shadowRoot && depth < maxDepth) {
          const internal = current.shadowRoot.querySelector('input, textarea, button, a, select, [role="button"], [tabindex]:not([tabindex="-1"])');
          if (internal) {
              current = internal;
              depth++;
          } else {
              break;
          }
      }
      return current;
  }

  // [新增] 递归查找所有 Input (支持 Shadow DOM)
  function queryAllInputs(root = document) {
      let inputs = Array.from(root.querySelectorAll('input'));

      const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, {
          acceptNode: node => node.shadowRoot ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
      });

      while (walker.nextNode()) {
          inputs = inputs.concat(queryAllInputs(walker.currentNode.shadowRoot));
      }
      return inputs;
  }

  // --- 来源配置 ---

  const SOURCES = [
      {
          key: 'bugmenot',
          name: 'BugMeNot',
          url: (domain) => `https://bugmenot.com/view/${domain}`,
          parser: parseBugMeNot
      },
      {
          key: 'freeaccount',
          name: 'FreeAccount',
          url: (domain) => `https://freeaccount.biz/accounts/${domain}`,
          parser: parseFreeAccount
      },
      {
          key: 'passwordlogin',
          name: 'Password-login',
          url: (domain) => `https://password-login.com/passwords/${domain}`,
          parser: parsePasswordLogin
      }
  ];

  // --- 解析函数 ---

  function parseBugMeNot(responseText) {
      var bmnEl = responseText.toDOM(true);
      var accountEls = bmnEl.getElementsByClassName('account');
      const list = [];
      for (var i = 0; i < accountEls.length; i++) {
          var accountEl = accountEls[i];
          var infoEl = accountEl.getElementsByTagName('kbd');
          var statsEl = accountEl.getElementsByClassName('stats')[1].getElementsByTagName('li');

          if (infoEl.length < 2) continue;

          list.push({
              username: infoEl[0].innerHTML || '',
              password: infoEl[1].innerHTML || '',
              email: (infoEl[2] && infoEl[2].innerHTML) ? infoEl[2].innerHTML : '',
              success: parseInt(statsEl[0].innerHTML.match(/\d+(?=%)/) ? statsEl[0].innerHTML.match(/\d+(?=%)/)[0] : 0),
              time: statsEl[2].innerHTML
          });
      }
      return list;
  }

  function parseFreeAccount(responseText) {
      const doc = responseText.toDOM(true);
      const tables = doc.querySelectorAll('table.prettyTable');
      const list = [];

      tables.forEach(tbl => {
          const successSpan = tbl.querySelector('span[id^="succ"]');
          const successRate = successSpan ? parseInt(successSpan.innerText.replace('%', '')) : 0;
          const loginPassCells = tbl.querySelectorAll('td.loginpass');

          if (loginPassCells.length >= 2) {
              const username = getTextContentWithCF(loginPassCells[0]);
              const password = getTextContentWithCF(loginPassCells[1]);

              if (username && password) {
                  list.push({
                      username: username,
                      password: password,
                      email: '',
                      success: isNaN(successRate) ? 0 : successRate,
                      time: ''
                  });
              }
          }
      });
      return list;
  }

  function parsePasswordLogin(responseText) {
      const doc = responseText.toDOM(true);
      const tables = doc.querySelectorAll('table[width="100%"]');
      const list = [];

      tables.forEach(tbl => {
          const font = tbl.querySelector('td[align="center"] font');
          if (!font) return;

          const successRate = parseInt(font.innerText.replace('%', '').trim()) || 0;

          const innerTable = tbl.querySelector('td[width="400"] table');
          if (!innerTable) return;

          const rows = innerTable.querySelectorAll('tr');
          if (rows.length >= 3) {
              const userCell = rows[0].querySelectorAll('td')[1];
              const passCell = rows[1].querySelectorAll('td')[1];
              const commentCell = rows[2].querySelectorAll('td')[1];

              if (userCell && passCell) {
                  list.push({
                      // [修改] 使用 getTextContentWithCF 以支持 CF 邮箱解密
                      username: getTextContentWithCF(userCell),
                      password: getTextContentWithCF(passCell),
                      email: getTextContentWithCF(commentCell),
                      success: successRate,
                      time: ''
                  });
              }
          }
      });
      return list;
  }

  // --- 核心变量 ---

  var sourceData = {};
  var sourceStatus = {};

  var hasFetchedData = false;
  var firstGroupExpanded = false;

  var inputUsernameCurrentEl, inputPasswordCurrentEl;
  var listBMNEl = null;

  var sharedButton = null;
  var currentTargetInput = null;
  var hideTimer = null;

  var isMenuMode = false;

  // --- 核心逻辑 ---

  function initListContainer() {
    if (listBMNEl) return;
    listBMNEl = document.createElement('ul');
    listBMNEl.classList.add('bmn-list');

    // [新增] 事件隔离:防止事件冒泡到宿主网页
    ['click', 'mousedown', 'keydown', 'keyup', 'contextmenu', 'focus', 'focusin', 'wheel'].forEach(evtName => {
        listBMNEl.addEventListener(evtName, function(e) {
            e.stopPropagation();
        }, false);
    });

    document.body.appendChild(listBMNEl);
  }

  function resetData() {
      sourceData = {};
      sourceStatus = {};
      SOURCES.forEach(source => {
          sourceData[source.key] = [];
          sourceStatus[source.key] = { pending: 0 };
      });
      hasFetchedData = false;
  }

  function initSourceSkeleton() {
      if (!listBMNEl) return;
      listBMNEl.innerHTML = '';

      // [新增] 重置展开标记
      firstGroupExpanded = false;

      if (isMenuMode) {
          const header = document.createElement('li');
          header.className = 'bmn-header';
          header.innerHTML = `
            <span>共享账号</span>
            <span class="bmn-close-btn" title="关闭">×</span>
          `;
          header.querySelector('.bmn-close-btn').onclick = function(e) {
              e.stopPropagation();
              hideListBMNEl();
          };
          listBMNEl.appendChild(header);
      }

      SOURCES.forEach((source, index) => {
          if (!sourceData[source.key]) {
              sourceData[source.key] = [];
              sourceStatus[source.key] = { pending: 0 };
          }

          const separator = document.createElement('li');
          separator.className = 'bmn-separator';
          separator.dataset.source = source.key;

          const link = document.createElement('a');
          link.href = source.url(location.hostname);
          link.target = '_blank';
          link.innerText = source.name;
          link.onclick = (e) => e.stopPropagation();

          const countSpan = document.createElement('span');
          countSpan.className = 'bmn-count';
          countSpan.innerText = '等待...';
          countSpan.id = `bmn-count-${source.key}`;
          countSpan.style.color = '#aaa';

          separator.appendChild(link);
          separator.appendChild(countSpan);

          separator.onclick = () => toggleGroup(source.key);

          const container = document.createElement('li');
          container.className = 'bmn-group-container';
          container.id = `bmn-container-${source.key}`;

          // [修改] 移除默认展开第一个分组的逻辑
          // if (index === 0) {
          //    container.classList.add('expanded');
          // }

          listBMNEl.appendChild(separator);
          listBMNEl.appendChild(container);
      });
  }

  function toggleGroup(sourceKey) {
      const container = document.getElementById(`bmn-container-${sourceKey}`);
      if (container) {
          container.classList.toggle('expanded');
      }
  }

  function updateSourceUI(sourceKey) {
      const container = document.getElementById(`bmn-container-${sourceKey}`);
      const countSpan = document.getElementById(`bmn-count-${sourceKey}`);
      if (!container || !countSpan) return;

      if (sourceStatus[sourceKey].pending > 0) {
          countSpan.innerText = '加载中...';
          countSpan.style.color = '#17a2b8';
      } else {
          const count = sourceData[sourceKey].length;
          countSpan.innerText = count;
          countSpan.style.color = count > 0 ? '#76CA53' : '#888';
      }

      if (sourceStatus[sourceKey].pending > 0 && sourceData[sourceKey].length === 0) {
          if (container.innerHTML === '') {
               container.innerHTML = '<div class="bmn-loading">正在加载...</div>';
          }
          return;
      }

      container.innerHTML = '';

      const accounts = sourceData[sourceKey];

      if (accounts.length === 0) {
          if (sourceStatus[sourceKey].pending === 0) {
             const msg = document.createElement('div');
             msg.className = 'bmn-no-logins-found';
             msg.innerText = '未找到账号';
             container.appendChild(msg);
          }
          return;
      }

      // [新增] 如果有数据且当前没有分组被展开 则展开此分组
      if (!firstGroupExpanded) {
          container.classList.add('expanded');
          firstGroupExpanded = true;
      }

      accounts.forEach(account => {
          var itemBMNEl = document.createElement('div');
          itemBMNEl.classList.add('bmn-item');
          itemBMNEl.classList.add(getClassSuccess(account.success));

          var emailHTML = '';
          if (account.email && account.email.trim() !== '') {
            emailHTML = `
              <div class="bmn-email-entry">
                <span class="bmn-label">信息:</span>
                <span class="bmn-value">${account.email}</span>
              </div>
            `;
          }

          var itemBMNElHTML = `
            <div>
              <span class="bmn-success-rate">${account.success}%</span>
              <span class="bmn-label">账号:</span>
              <span class="bmn-value bmn-username">${account.username}</span>
            </div>
            <div>
              <span class="bmn-label">密码:</span>
              <span class="bmn-value bmn-password">${account.password}</span>
            </div>
            ${emailHTML}
          `;
          itemBMNEl.innerHTML = itemBMNElHTML;

          itemBMNEl.onmousedown = handleEvent(onMouseDownItem);
          itemBMNEl.onclick = handleEvent(onClickItem, account);
          itemBMNEl.onmouseover = handleEvent(onMouseOverItem, account);
          itemBMNEl.onmouseout = handleEvent(onMouseOutItem);

          var emailEl = itemBMNEl.querySelector('.bmn-email-entry');
          if (emailEl) {
              emailEl.onmousedown = handleEvent(onMouseDownItem);
              emailEl.onclick = handleEvent(onClickEmailItem, account);
              emailEl.onmouseover = handleEvent(onMouseOverEmailItem, account);
          }

          container.appendChild(itemBMNEl);
      });
  }

  function fetchSourceDomain(source, domain) {
      sourceStatus[source.key].pending++;
      updateSourceUI(source.key);

      GM_xmlhttpRequest({
          method: 'GET',
          url: source.url(domain),
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          onload: function(response) {
              try {
                  const newAccounts = source.parser(response.responseText);
                  if (newAccounts && newAccounts.length > 0) {
                      const existing = sourceData[source.key];
                      newAccounts.forEach(acc => {
                          const exists = existing.some(ex => ex.username === acc.username && ex.password === acc.password);
                          if (!exists) {
                              existing.push(acc);
                          }
                      });
                      existing.sort((a, b) => b.success - a.success);
                  }
              } catch (e) {
                  console.error(`Error parsing ${source.name} for ${domain}`, e);
              } finally {
                  sourceStatus[source.key].pending--;
                  updateSourceUI(source.key);
              }
          },
          onerror: function() {
              sourceStatus[source.key].pending--;
              updateSourceUI(source.key);
          }
      });
  }

  function fetchData() {
    if (hasFetchedData) {
        initSourceSkeleton();
        showListBMNEl();
        addStyleListBMNEl(inputUsernameCurrentEl);

        SOURCES.forEach(source => {
            updateSourceUI(source.key);
        });
        return;
    }

    resetData();
    initSourceSkeleton();
    showListBMNEl();
    addStyleListBMNEl(inputUsernameCurrentEl);

    const hostname = location.hostname;
    const rootDomain = getRootDomain(hostname);

    const domainsToCheck = [hostname];
    if (hostname !== rootDomain) {
        domainsToCheck.push(rootDomain);
    }

    SOURCES.forEach(source => {
        domainsToCheck.forEach(domain => {
            fetchSourceDomain(source, domain);
        });
    });

    hasFetchedData = true;
  }

  // --- 交互逻辑 ---

  function closeOnClickOutside(event) {
    if (isMenuMode) return;

    if (listBMNEl && !listBMNEl.contains(event.target) && !event.target.closest('.bmn-floating-button')) {
      hideListBMNEl();
    }
  }

  function showListBMNEl() {
    if (listBMNEl) {
      listBMNEl.classList.add('show');
      document.addEventListener('mousedown', closeOnClickOutside, true);
    }
  }

  function hideListBMNEl() {
    if (listBMNEl) {
      listBMNEl.classList.remove('show');
      document.removeEventListener('mousedown', closeOnClickOutside, true);
    }
  }

  var enableMouseOut = true;

  function setValueInputItem(inputUsernameEl, inputPasswordEl, username, password, isInputSimulate) {
    setValueInput(inputUsernameEl, username, isInputSimulate);
    setValueInput(inputPasswordEl, password, isInputSimulate);
  }

  function onMouseDownItem(event) {
    event.stopPropagation();
    event.preventDefault();
  }

  function onClickItem(event, account) {
    event.stopPropagation();

    if (isMenuMode) {
        const target = event.target;
        if (target.closest('.bmn-username')) {
            copyToClipboard(account.username);
        } else if (target.closest('.bmn-password')) {
            copyToClipboard(account.password);
        } else if (target.closest('.bmn-email-entry')) {
            copyToClipboard(account.email);
        } else {
            copyToClipboard(account.password);
        }
        return;
    }

    enableMouseOut = false;
    if (inputUsernameCurrentEl && inputPasswordCurrentEl) {
      setValueInputItem(inputUsernameCurrentEl, inputPasswordCurrentEl, account.username, account.password, true);
      inputUsernameCurrentEl.setAttribute('value', account.username);
      hideListBMNEl();
    }
  }

  function onClickEmailItem(event, account) {
    event.stopPropagation();

    if (isMenuMode) {
        copyToClipboard(account.email);
        return;
    }

    enableMouseOut = false;
    if (inputUsernameCurrentEl && inputPasswordCurrentEl) {
      setValueInputItem(inputUsernameCurrentEl, inputPasswordCurrentEl, account.email, account.password, true);
      inputUsernameCurrentEl.setAttribute('value', account.email);
      hideListBMNEl();
    }
  }

  function onMouseOverItem(event, account) {
    if (!isMenuMode && inputUsernameCurrentEl && inputPasswordCurrentEl) {
      setValueInputItem(inputUsernameCurrentEl, inputPasswordCurrentEl, account.username, account.password);
    }
  }

  function onMouseOverEmailItem(event, account) {
    event.stopPropagation();
    if (!isMenuMode && inputUsernameCurrentEl && inputPasswordCurrentEl) {
      setValueInputItem(inputUsernameCurrentEl, inputPasswordCurrentEl, account.email, account.password);
    }
  }

  function onMouseOutItem(event) {
    if (isMenuMode) return;

    if (!enableMouseOut) {
      enableMouseOut = true;
      return;
    }
    if (inputUsernameCurrentEl && inputPasswordCurrentEl) {
      setValueInputItem(inputUsernameCurrentEl, inputPasswordCurrentEl, '', '');
    }
  }

  function getClassSuccess(success) {
    if (success > 91) return 'bmn-success-100';
    else if (success > 81) return 'bmn-success-90';
    else if (success > 71) return 'bmn-success-80';
    else if (success > 61) return 'bmn-success-70';
    else if (success > 51) return 'bmn-success-60';
    else if (success > 31) return 'bmn-success-50';
    else if (success > 21) return 'bmn-success-30';
    else if (success > 11) return 'bmn-success-20';
    else return 'bmn-success-10';
  }

  function addStyleListBMNEl(inputEl) {
    if (!listBMNEl) return;

    if (isMenuMode || !inputEl) {
        listBMNEl.style.top = '10px';
        listBMNEl.style.right = '10px';
        listBMNEl.style.left = '';
        listBMNEl.style.bottom = '';
        listBMNEl.style.maxHeight = (window.innerHeight - 20) + 'px';
        return;
    }

    const offsetTarget = getOffset(inputEl);
    const windowHeight = document.documentElement.clientHeight;
    const windowWidth = document.documentElement.clientWidth;
    const listWidth = listBMNEl.offsetWidth || 300;
    const gap = 5;

    listBMNEl.style.top = offsetTarget.top + 'px';
    listBMNEl.style.bottom = '';
    listBMNEl.style.maxHeight = (windowHeight - offsetTarget.top - 10) + 'px';

    const spaceOnRight = windowWidth - offsetTarget.right;
    const spaceOnLeft = offsetTarget.left;

    if (spaceOnRight >= listWidth + gap) {
      listBMNEl.style.left = (offsetTarget.right + gap) + 'px';
      listBMNEl.style.right = '';
    }
    else if (spaceOnLeft >= listWidth + gap) {
      listBMNEl.style.left = (offsetTarget.left - listWidth - gap) + 'px';
      listBMNEl.style.right = '';
    }
    else {
      listBMNEl.style.right = gap + 'px';
      listBMNEl.style.left = '';
    }
  }

  // --- 共享按钮逻辑 ---

  function initSharedButton() {
      if (sharedButton) return;

      sharedButton = document.createElement('button');
      sharedButton.className = 'bmn-floating-button';
      sharedButton.type = 'button';
      sharedButton.title = '获取共享账号';
      sharedButton.innerHTML = icons.mail;

      // [新增] 事件隔离:防止事件冒泡
      ['click', 'mousedown', 'keydown', 'keyup', 'contextmenu', 'focus', 'focusin', 'wheel'].forEach(evtName => {
          sharedButton.addEventListener(evtName, function(e) {
              e.stopPropagation();
          }, false);
      });

      sharedButton.onmousedown = function(e) {
          e.preventDefault();
      };

      sharedButton.onclick = function(e) {
          e.preventDefault();
          e.stopPropagation();

          if (currentTargetInput) {
              const passwordInput = currentTargetInput._bmnPasswordEl;
              const data = {
                  inputUsernameEl: currentTargetInput,
                  inputPasswordEl: passwordInput
              };
              onButtonClick(e, data);
          }
      };

      document.body.appendChild(sharedButton);
  }

  function updateSharedButtonPosition() {
    if (!currentTargetInput || !sharedButton || sharedButton.style.display === 'none') return;

    // [修改] 使用 isConnected 替代 document.body.contains
    // document.body.contains 无法检测 Shadow DOM 中的元素 会导致图标在 Reddit 等网站自动消失
    if (!currentTargetInput.isConnected || !isVisible(currentTargetInput)) {
        hideSharedButton();
        return;
    }

    const rect = getOffset(currentTargetInput);
    const buttonSize = 24;
    const rightMargin = 10;

    sharedButton.style.top = (rect.top + (rect.height - buttonSize) / 2) + 'px';
    sharedButton.style.left = (rect.right - buttonSize - rightMargin) + 'px';
  }

  function showSharedButton(inputEl) {
      clearTimeout(hideTimer);
      initSharedButton();
      currentTargetInput = inputEl;
      sharedButton.style.display = 'flex';
      sharedButton.style.opacity = '0.8';
      updateSharedButtonPosition();
  }

  function hideSharedButton() {
      hideTimer = setTimeout(() => {
          if (sharedButton) {
              sharedButton.style.display = 'none';
              currentTargetInput = null;
          }
      }, 200);
  }

  function onButtonClick(event, data) {
    if (listBMNEl && listBMNEl.classList.contains('show')) {
        hideListBMNEl();
        return;
    }

    isMenuMode = false;

    inputUsernameCurrentEl = data.inputUsernameEl;
    inputPasswordCurrentEl = data.inputPasswordEl;

    initListContainer();
    addStyleListBMNEl(inputUsernameCurrentEl);

    fetchData();
  }

  function onMenuClick() {
      if (listBMNEl && listBMNEl.classList.contains('show')) {
          hideListBMNEl();
          return;
      }

      isMenuMode = true;
      inputUsernameCurrentEl = null;
      inputPasswordCurrentEl = null;

      initListContainer();
      addStyleListBMNEl(null);

      fetchData();
  }

  GM_registerMenuCommand('获取账号', onMenuClick);

  function onInputInput(event) {
    enableMouseOut = false;
    hideListBMNEl();
  }

  function onInputFocus(event) {
    const input = event.target;
    if (input.dataset.bmnChecked) {
        showSharedButton(input);
    }
  }

  function onInputBlur(event) {
    hideSharedButton();
  }

  function isValidUsernameInput(el) {
      if (!el || !(el instanceof Element) || el.dataset.bmnChecked) {
          return false;
      }
      const isPassword = el.type === 'password';
      const isInput = el.tagName.toLowerCase() === 'input';
      const isValidType = ['text', 'email', 'tel', 'url', 'number', undefined, ''].includes(el.type);
      return isInput && !isPassword && isValidType && isVisible(el);
  }

  function attachButton(inputUsernameEl, inputPasswordEl) {
      inputUsernameEl.dataset.bmnChecked = true;
      inputPasswordEl.dataset.bmnChecked = true;

      inputUsernameEl._bmnPasswordEl = inputPasswordEl;

      inputUsernameEl.addEventListener('input', onInputInput);
      inputUsernameEl.addEventListener('focus', onInputFocus);
      inputUsernameEl.addEventListener('blur', onInputBlur);

      initSharedButton();

      // [修改] 立即显示逻辑:支持 Shadow DOM 中的 activeElement
      const activeEl = diveIntoShadow(document.activeElement);
      if (activeEl === inputUsernameEl) {
          showSharedButton(inputUsernameEl);
      }
  }

  function checkAndEventToInput() {
      // 获取页面上所有的 input (包含 Shadow DOM 中的)
      const allInputs = queryAllInputs(document);

      // 过滤出所有密码框
      const passwordInputs = allInputs.filter(el => el.type === 'password' && !el.dataset.bmnChecked);

      if (passwordInputs.length === 0) return;

      passwordInputs.forEach(passwordInput => {
          if (!isVisible(passwordInput)) return;

          let foundUsernameInput = null;
          const currentRoot = passwordInput.getRootNode();

          // --- 策略 1: 尝试在同一个 Shadow Root/Document 下查找 (原有逻辑) ---

          // 1.1 查找紧邻的前一个输入框
          let siblingCandidate = passwordInput.previousElementSibling;
          for (let i = 0; i < 3 && siblingCandidate; i++) {
              if (isValidUsernameInput(siblingCandidate)) {
                  foundUsernameInput = siblingCandidate;
                  break;
              }
              siblingCandidate = siblingCandidate.previousElementSibling;
          }

          // 1.2 在当前 Root 中向前查找
          if (!foundUsernameInput) {
              // 仅在当前 Root 下查找潜在输入框
              const potentialInputsInRoot = Array.from(currentRoot.querySelectorAll(
                  'input:not([type]), input[type=text], input[type=email], input[type=tel], input[type=password]'
              ));

              const passwordDomIndex = potentialInputsInRoot.indexOf(passwordInput);
              if (passwordDomIndex > 0) {
                  for (let i = passwordDomIndex - 1; i >= 0; i--) {
                      const globalCandidate = potentialInputsInRoot[i];
                      if (isValidUsernameInput(globalCandidate)) {
                          foundUsernameInput = globalCandidate;
                          break;
                      }
                  }
              }
          }

          // --- 策略 2 (新增): 跨 Shadow DOM 查找 (针对 Reddit 等网站) ---
          // 如果在当前小圈子里没找到 就在全局大列表里找位于密码框之前的那个输入框
          if (!foundUsernameInput) {
              const passwordGlobalIndex = allInputs.indexOf(passwordInput);
              if (passwordGlobalIndex > 0) {
                  // 向前回溯查找 限制回溯范围以防误判(例如回溯最近的 5 个输入框)
                  for (let i = passwordGlobalIndex - 1; i >= 0 && i >= passwordGlobalIndex - 5; i--) {
                      const candidate = allInputs[i];
                      // 确保找到的输入框是可见的 且不是密码框
                      if (isValidUsernameInput(candidate)) {
                          foundUsernameInput = candidate;
                          break;
                      }
                  }
              }
          }

          // --- 策略 3: 通过 name/autocomplete 查找 (原有逻辑的补充) ---
          if (!foundUsernameInput) {
              const explicitUserInputs = currentRoot.querySelectorAll('input[name="username"], input[autocomplete="username"]');
              for (let i = explicitUserInputs.length - 1; i >= 0; i--) {
                  const candidate = explicitUserInputs[i];
                  if (isVisible(candidate) && (candidate.compareDocumentPosition(passwordInput) & Node.DOCUMENT_POSITION_FOLLOWING)) {
                      foundUsernameInput = candidate;
                      break;
                  }
              }
          }

          if (foundUsernameInput) {
              attachButton(foundUsernameInput, passwordInput);
          }
      });
  }

  // --- 启动与监视 ---

  function initialScan() {
      checkAndEventToInput();
      setTimeout(checkAndEventToInput, 500);
  }

  if (document.readyState === 'complete') {
      initialScan();
  } else {
      window.addEventListener('load', initialScan);
  }

  const debouncedUpdatePosition = debounce(updateSharedButtonPosition, 10);

  window.addEventListener('scroll', debouncedUpdatePosition, true);
  window.addEventListener('resize', debouncedUpdatePosition);

  const observer = new MutationObserver(() => {
      checkAndEventToInput();
      updateSharedButtonPosition();
  });

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

})();