您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为Via设计的第三方密码自动保存/填充工具
当前为
// ==UserScript== // @name 🔐 密码填充 // @namespace https://ez118.github.io/ // @version 0.2.0 // @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/[email protected]/dist/jquery.min.js // ==/UserScript== /* =====[ 变量存储 ]===== */ const ICONS = { 'del': '<svg viewBox="0 0 24 24" width="20px" height="20px"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></svg>' }; var savedAccount = []; /* ====================== */ 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" || input.type === "email") { 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 - 90; 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" || input.type === "email") && !usr) { usr = input; } else if (input.type === "password" && !psw) { psw = input; } }); return {password: psw.value, username: usr.value, psw: psw, usr: 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 = $('<div>', { class: 'userscript-pswmgrDlg', id: 'userscript-pswmgrDlg', style: 'display:none;' }).appendTo('body'); $optDlg.hide(); $optDlg.fadeIn(100); var listHtml = ''; $.each(newAccountList, (index, item) => { listHtml += ` <div class="list-item" acid="${item.id}"> <p class="item-title">${item.username} (${item.host})</p> <p class="item-delbtn" acid="${item.id}" title="移除">${ICONS.del}</p> </div>`; }); $optDlg.html(` <div style="height:fit-content; max-height:calc(80vh - 60px); overflow-x:hidden; overflow-y:auto;"> <h3>管理</h3> <div style="height:fit-content; margin:5px;"> <p class="subtitle">已保存的账户:</p> ` + listHtml + ` </div> </div> <div align="right"> <input type="button" value="取消" class="ctrlbtn" id="userscript-cancelBtn"> <input type="button" value="保存" class="ctrlbtn" id="userscript-saveBtn"> </div> `); $(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(form, cx, cy) { // 创建搜索栏元素并添加到页面 var $quickFill = $('<div>', { class: 'userscript-quickFill', id: 'userscript-quickFill' }).appendTo('body'); let html = ''; const host = getHost(); $.each(savedAccount, (index, item) => { if(item.host == host) { html += `<div class="item" acid="${item.id}">${item.username}</div>`; } }) // 设定快速填充栏HTML内容 $quickFill.append(` <font color="#333333" size="small"> 保存的密码:</font> ${html} <div class="hideBtn">[隐藏]</div> `); // 设置快速填充栏 位置 $("#userscript-quickFill") .css("left", cx + "px") .css("top", cy + "px"); // 选择保存过的第一个账号,自动填充到网页 const formdata = getFormData(form); let dataindex = findByKeyValue(savedAccount, 'host', host); if (dataindex !== -1){ formdata.psw.value = savedAccount[dataindex].password; formdata.usr.value = savedAccount[dataindex].username; } // 添加点击事件监听器 $(document).on('click', '#userscript-quickFill>.item', function(e) { const acid = $(e.target).attr("acid"); const formdata = getFormData(form); let dataindex = findByKeyValue(savedAccount, 'id', acid); formdata.psw.value = savedAccount[dataindex].password; formdata.usr.value = savedAccount[dataindex].username; }); $(document).on('click', '#userscript-quickFill>.hideBtn', function(e) { $quickFill.hide(); }); } function init() { let judgeRes = isLoginPage(); if(judgeRes.isLogin){ /* 存储初始化 */ console.log("【提示】检测到登录页面"); initEle(judgeRes.obj, 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("【提示】账号已保存!"); } }); } } /* =====[ 菜单注册 ]===== */ 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; } `); init(); setTimeout(() => { if($(".userscript-quickFill").length == 0){ init(); } }, 1000) })();