// ==UserScript== // @name 🔐 密码填充 // @namespace https://ez118.github.io/ // @version 0.1 // @description 为Via设计的第三方密码自动保存/填充工具 // @author ZZY_WISU // @match *://*/* // @license GPLv3 // @icon data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTk2MCA5NjAgOTYwIj4KICA8cGF0aCBmaWxsPSIjODg4IiBkPSJNMTYwLTQ0MHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1Wk04MC0yMDB2LTgwaDgwMHY4MEg4MFptNDAwLTI0MHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1Wm0zMjAgMHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1WiI+PC9wYXRoPgo8L3N2Zz4= // @run-at document-end // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @require https://unpkg.com/jquery@3.7.1/dist/jquery.min.js // @downloadURL none // ==/UserScript== /* =====[ 用户自定义 ]===== */ var savedAccount = []; /* =====[ 变量存储 ]===== */ const ICONS = { 'del': '' }; /* ====================== */ function hash(str) { let hash = 5381; for (let i = 0; i < str.length; i++) { hash = (hash * 33) ^ str.charCodeAt(i); } return hash >>> 0; } function getHost() { return window.location.host; } function isLoginPage() { let forms = document.getElementsByTagName("form"); let isLogin = false; let formPosition = {x: 0, y: 0}; let formobj = null; Array.prototype.forEach.call(forms, (form) => { let hasTextInput = false; let hasPasswordInput = false; // 获取所有 input 元素 let inputs = form.getElementsByTagName("input"); // 检查每个 input 的类型 Array.prototype.forEach.call(inputs, (input) => { if (input.type === "text") { hasTextInput = true; } else if (input.type === "password") { hasPasswordInput = true; } }); // 如果同时存在 text 和 password 类型的输入框,认为是登录页面 if (hasTextInput && hasPasswordInput) { isLogin = true; let rectData = form.getClientRects()[0]; formPosition.x = rectData.left + rectData.width / 2 - 150; formPosition.y = rectData.top + rectData.height - 15; formobj = form; } }); return { isLogin, x: formPosition.x, y: formPosition.y, obj: formobj }; } function getFormData(ele){ let inputs = ele.getElementsByTagName("input"); let usr = null; let psw = null; // 检查每个 input 的类型 Array.prototype.forEach.call(inputs, (input) => { if (input.type === "text" && !usr) { usr = input.value; } else if (input.type === "password" && !psw) { psw = input.value; } }); return {password:psw, username:usr}; } function findByKeyValue(array, key, value) { /* 在JSON中,以键值匹配项 */ return array.findIndex(item => item[key] === value); } function showPswMgr() { if($("#userscript-pswmgrDlg").length > 0) { return; } let newAccountList = savedAccount; let origAccountList = savedAccount.slice(); var $optDlg = $('
', { class: 'userscript-pswmgrDlg', id: 'userscript-pswmgrDlg', style: 'display:none;' }).appendTo('body'); $optDlg.hide(); $optDlg.fadeIn(100); var listHtml = ''; $.each(newAccountList, (index, item) => { listHtml += `

${item.username} - ${item.password}

${ICONS.del}

`; }); $optDlg.html(`

管理

已保存的密钥:

` + listHtml + `
`); $(document).on('click', '.list-item>.item-delbtn', function(e) { let acid = $(e.target).parent().attr("acid"); const index = findByKeyValue(newAccountList, 'id', acid); if (index !== -1) { newAccountList.splice(index, 1); $(`.list-item[acid="${acid}"]`).remove(); } }); $(document).on('click', '#userscript-cancelBtn', function(e) { /* 取消按钮 */ newAccountList = origAccountList; $(document).off('click', '#userscript-saveBtn') $(document).off('click', '.list-item>.item-delbtn'); let $optDlg = $("#userscript-pswmgrDlg"); $optDlg.fadeOut(100); setTimeout(() => { $optDlg.remove(); $(document).off('click', '#userscript-cancelBtn'); location.reload(); }, 110); }); $(document).on('click', '#userscript-saveBtn', function(e) { /* 保存按钮 */ GM_setValue('savedAccount', newAccountList); alert("【已保存】请刷新页面以应用更改"); $(document).off('click', '#userscript-cancelBtn'); $(document).off('click', '.list-item>.item-delbtn'); let $optDlg = $("#userscript-pswmgrDlg"); $optDlg.fadeOut(100); setTimeout(() => { $optDlg.remove(); $(document).off('click', '#userscript-saveBtn'); }, 110); }); } function initEle(cx, cy) { // 创建搜索栏元素并添加到页面 var $quickFill = $('
', { class: 'userscript-quickFill', id: 'userscript-quickFill' }).appendTo('body'); let html = ''; const host = getHost(); $.each(savedAccount, (index, item) => { if(item.host == host) { html += `
${item.username}
`; } }) // HTML 内容并插入到快速栏 $quickFill.append(`  保存的密码: ${html}
[隐藏]
`); $("#userscript-quickFill") .css("left", cx + "px") .css("top", cy + "px"); // 添加点击事件监听器 $(document).on('click', '#userscript-quickFill>.item', function(e) { var acid = $(e.target).attr("acid"); alert(acid); }); $(document).on('click', '#userscript-quickFill>.hideBtn', function(e) { $quickFill.hide(); }); } /* =====[ 菜单注册 ]===== */ var menu_mgr = GM_registerMenuCommand('⚙️ 管理密码', function () { showPswMgr(); }, 'o'); (function () { 'use strict'; if(GM_getValue('savedAccount') == null || GM_getValue('savedAccount') == "" || GM_getValue('savedAccount') == undefined){ GM_setValue('savedAccount', savedAccount); } else { savedAccount = GM_getValue('savedAccount'); } var websiteThemeColor = "#FFFFFFEE"; var websiteFontColor = "#000"; GM_addStyle(` body{ -webkit-appearance:none!important; } .userscript-quickFill{ user-select:none; background-color:` + websiteThemeColor + `; color:` + websiteFontColor + `; border:1px solid #99999999; padding:2px; font-size:12px; line-height:20px; width:180px; height:fit-content; position:absolute; display:flex; flex-direction:column; overflow:hidden auto; box-sizing:border-box; z-index:100000; font-family:"Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; border-radius:5px; box-shadow:0px 0px 5px #666; } .userscript-quickFill>.item{ margin:1px 0px; border-radius:20px; padding:5px 9px; width:100%; flex-basis:fit-content; flex-shrink:0; cursor:pointer; background-color:transparent; box-sizing:border-box } .userscript-quickFill>.item:hover{ background-color:rgba(128, 128, 128, 0.05); } .userscript-quickFill>.hideBtn{ margin:1px 0px; padding:5px 9px; width:100%; flex-basis:fit-content; flex-shrink:0; color:` + websiteFontColor + `; opacity:0.6; font-size:12px; font-weight:bold; box-sizing:border-box; cursor:pointer; } .userscript-pswmgrDlg{ user-select:none; background-color:` + websiteThemeColor + `; color:` + websiteFontColor + `; border:1px solid #99999999; position:fixed; top:50%; height:fit-content; left:50%; transform:translateX(-50%) translateY(-50%); width:92vw; max-width:300px; max-height:92vh; padding:15px; border-radius:15px; box-sizing:initial; z-index:100000; box-shadow:0 1px 10px #00000088; font-family:"Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif; } .userscript-pswmgrDlg .ctrlbtn{ border:none; background-color:transparent; padding:8px; margin:0; color:#6d7fb4; cursor:pointer; overflow:hidden; } .userscript-pswmgrDlg h3{ margin:5px; margin-bottom:15px; font-size:24px; } .userscript-pswmgrDlg .subtitle{ margin:5px 1px; font-size:16px; font-weight:400; } .userscript-pswmgrDlg .list-item{ width:calc(100% - 10px); padding:10px 5px; margin:0; display:flex; flex-direction:row; vertical-align:middle; box-sizing:initial; } .userscript-pswmgrDlg .list-item:hover{ background-color:#55555555; } .userscript-pswmgrDlg .list-item>p{ padding:0; margin:0; font-size:16px; } .userscript-pswmgrDlg .list-item>.item-title{ flex-grow:1; margin-left:5px; } .userscript-pswmgrDlg .list-item>.item-delbtn{ cursor:pointer; width:25px; } .userscript-pswmgrDlg .list-item>.item-delbtn svg{ fill:` + websiteFontColor + `; height:100%; min-height:16px; } `); let judgeRes = isLoginPage(); if(judgeRes.isLogin){ /* 存储初始化 */ console.log("【提示】检测到是满足要求的登录页面"); initEle(judgeRes.x, judgeRes.y); $(document).on('submit', judgeRes.obj, () => { // 获取表单输入内容 const formdata = getFormData(judgeRes.obj); const newdata = { "id": hash(getHost() + formdata.username + formdata.password).toString(), "host": getHost(), "username": formdata.username, "password": formdata.password }; // 检查是否数据重复 const oldidx = findByKeyValue(savedAccount, "host", newdata.host); if(oldidx !== -1 && savedAccount[oldidx] && savedAccount[oldidx].id == newdata.id) { return; } // 如果不是重复账号,则询问是否保存 let res = window.confirm("【询问】是否保存账号?"); if(res) { // 保存账户 savedAccount.push(newdata); GM_setValue('savedAccount', savedAccount); alert("账号已保存!"); } }); } })();