🔐 密码填充

为Via设计的第三方密码自动保存/填充工具

目前为 2024-10-05 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 🔐 密码填充
  3. // @namespace https://ez118.github.io/
  4. // @version 0.2.1
  5. // @description 为Via设计的第三方密码自动保存/填充工具
  6. // @author ZZY_WISU
  7. // @match *://*/*
  8. // @license GPLv3
  9. // @icon data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgLTk2MCA5NjAgOTYwIj4KICA8cGF0aCBmaWxsPSIjODg4IiBkPSJNMTYwLTQ0MHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1Wk04MC0yMDB2LTgwaDgwMHY4MEg4MFptNDAwLTI0MHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1Wm0zMjAgMHEtNTAgMC04NS0zNXQtMzUtODVxMC01MCAzNS04NXQ4NS0zNXE1MCAwIDg1IDM1dDM1IDg1cTAgNTAtMzUgODV0LTg1IDM1WiI+PC9wYXRoPgo8L3N2Zz4=
  10. // @run-at document-end
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_addStyle
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @require https://unpkg.com/jquery@3.7.1/dist/jquery.min.js
  16. // ==/UserScript==
  17.  
  18.  
  19. /* =====[ 变量存储 ]===== */
  20.  
  21. const ICONS = {
  22. '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>'
  23. };
  24.  
  25. var savedAccount = [];
  26.  
  27. /* ====================== */
  28. function hash(str) {
  29. let hash = 5381;
  30. for (let i = 0; i < str.length; i++) {
  31. hash = (hash * 33) ^ str.charCodeAt(i);
  32. }
  33. return hash >>> 0;
  34. }
  35.  
  36. function getHost() {
  37. return window.location.host;
  38. }
  39.  
  40. function isLoginPage() {
  41. let forms = document.getElementsByTagName("form");
  42. let isLogin = false;
  43. let formPosition = {x: 0, y: 0};
  44. let formobj = null;
  45.  
  46. Array.prototype.forEach.call(forms, (form) => {
  47. let hasTextInput = false;
  48. let hasPasswordInput = false;
  49.  
  50. // 获取所有 input 元素
  51. let inputs = form.getElementsByTagName("input");
  52.  
  53. // 检查每个 input 的类型
  54. Array.prototype.forEach.call(inputs, (input) => {
  55. if (input.type === "text" || input.type === "email") {
  56. hasTextInput = true;
  57. } else if (input.type === "password") {
  58. hasPasswordInput = true;
  59. }
  60. });
  61.  
  62. // 如果同时存在 text 和 password 类型的输入框,认为是登录(不可用)页面
  63. if (hasTextInput && hasPasswordInput) {
  64. isLogin = true;
  65.  
  66. let rectData = form.getClientRects()[0];
  67. formPosition.x = rectData.left + rectData.width / 2 - 90;
  68. formPosition.y = rectData.top + rectData.height - 15;
  69.  
  70. formobj = form;
  71. }
  72. });
  73.  
  74. return { isLogin, x: formPosition.x, y: formPosition.y, obj: formobj };
  75. }
  76.  
  77. function getFormData(ele){
  78. let inputs = ele.getElementsByTagName("input");
  79. let usr = null;
  80. let psw = null;
  81.  
  82. // 检查每个 input 的类型
  83. Array.prototype.forEach.call(inputs, (input) => {
  84. if ((input.type === "text" || input.type === "email") && !usr) {
  85. usr = input;
  86. } else if (input.type === "password" && !psw) {
  87. psw = input;
  88. }
  89. });
  90.  
  91. return {password: psw.value, username: usr.value, psw: psw, usr: usr};
  92. }
  93.  
  94.  
  95. function findByKeyValue(array, key, value) {
  96. /* 在JSON中,以键值匹配项 */
  97. return array.findIndex(item => item[key] === value);
  98. }
  99.  
  100. function showPswMgr() {
  101. if($("#userscript-pswmgrDlg").length > 0) { return; }
  102.  
  103. let newAccountList = savedAccount;
  104. let origAccountList = savedAccount.slice();
  105.  
  106. var $optDlg = $('<div>', {
  107. class: 'userscript-pswmgrDlg',
  108. id: 'userscript-pswmgrDlg',
  109. style: 'display:none;'
  110. }).appendTo('body');
  111.  
  112. $optDlg.hide();
  113. $optDlg.fadeIn(100);
  114.  
  115. var listHtml = '';
  116. $.each(newAccountList, (index, item) => {
  117. listHtml += `
  118. <div class="list-item" acid="${item.id}">
  119. <p class="item-title">${item.username} (${item.host})</p>
  120. <p class="item-delbtn" acid="${item.id}" title="移除">${ICONS.del}</p>
  121. </div>`;
  122. });
  123.  
  124. $optDlg.html(`
  125. <div style="height:fit-content; max-height:calc(80vh - 60px); overflow-x:hidden; overflow-y:auto;">
  126. <h3>管理</h3>
  127. <div style="height:fit-content; margin:5px;">
  128. <p class="subtitle">已保存的账户:</p>
  129. ` + listHtml + `
  130. </div>
  131. </div>
  132. <div align="right">
  133. <input type="button" value="取消" class="ctrlbtn" id="userscript-cancelBtn">
  134. <input type="button" value="保存" class="ctrlbtn" id="userscript-saveBtn">
  135. </div>
  136. `);
  137.  
  138. $(document).on('click', '.list-item>.item-delbtn', function(e) {
  139. let acid = $(e.target).parent().attr("acid");
  140. const index = findByKeyValue(newAccountList, 'id', acid);
  141. if (index !== -1) {
  142. newAccountList.splice(index, 1);
  143. $(`.list-item[acid="${acid}"]`).remove();
  144. }
  145. });
  146.  
  147. $(document).on('click', '#userscript-cancelBtn', function(e) {
  148. /* 取消按钮 */
  149. newAccountList = origAccountList;
  150.  
  151. $(document).off('click', '#userscript-saveBtn')
  152. $(document).off('click', '.list-item>.item-delbtn');
  153.  
  154. let $optDlg = $("#userscript-pswmgrDlg");
  155. $optDlg.fadeOut(100);
  156. setTimeout(() => {
  157. $optDlg.remove();
  158.  
  159. $(document).off('click', '#userscript-cancelBtn');
  160.  
  161. location.reload();
  162. }, 110);
  163. });
  164.  
  165. $(document).on('click', '#userscript-saveBtn', function(e) {
  166. /* 保存按钮 */
  167. GM_setValue('savedAccount', newAccountList);
  168. alert("【已保存】请刷新页面以应用更改");
  169.  
  170. $(document).off('click', '#userscript-cancelBtn');
  171. $(document).off('click', '.list-item>.item-delbtn');
  172.  
  173. let $optDlg = $("#userscript-pswmgrDlg");
  174. $optDlg.fadeOut(100);
  175. setTimeout(() => {
  176. $optDlg.remove();
  177.  
  178. $(document).off('click', '#userscript-saveBtn');
  179. }, 110);
  180. });
  181. }
  182.  
  183.  
  184. function initEle(form, cx, cy) {
  185. // 创建搜索栏元素并添加到页面
  186. var $quickFill = $('<div>', {
  187. class: 'userscript-quickFill',
  188. id: 'userscript-quickFill'
  189. }).appendTo('body');
  190.  
  191. let html = '';
  192. const host = getHost();
  193. $.each(savedAccount, (index, item) => {
  194. if(item.host == host) {
  195. html += `<div class="item" acid="${item.id}">${item.username}</div>`;
  196. }
  197. })
  198.  
  199. // 设定快速填充栏HTML内容
  200. $quickFill.append(`
  201. <font color="#333333" size="small">&nbsp;保存的密码:</font>
  202. ${html}
  203. <div class="hideBtn">[隐藏]</div>
  204. `);
  205.  
  206. // 设置快速填充栏 位置
  207. $("#userscript-quickFill")
  208. .css("left", cx + "px")
  209. .css("top", cy + "px");
  210.  
  211. // 选择保存过的第一个账号,自动填充到网页
  212. const formdata = getFormData(form);
  213. let dataindex = findByKeyValue(savedAccount, 'host', host);
  214. if (dataindex !== -1){
  215. formdata.psw.value = savedAccount[dataindex].password;
  216. formdata.usr.value = savedAccount[dataindex].username;
  217. }
  218.  
  219. // 添加点击事件监听器
  220. $(document).on('click', '#userscript-quickFill>.item', function(e) {
  221. const acid = $(e.target).attr("acid");
  222. const formdata = getFormData(form);
  223. let dataindex = findByKeyValue(savedAccount, 'id', acid);
  224. formdata.psw.value = savedAccount[dataindex].password;
  225. formdata.usr.value = savedAccount[dataindex].username;
  226. });
  227.  
  228. $(document).on('click', '#userscript-quickFill>.hideBtn', function(e) {
  229. $quickFill.hide();
  230. });
  231. }
  232.  
  233. function init() {
  234. let judgeRes = isLoginPage();
  235.  
  236. if(judgeRes.isLogin){
  237. /* 存储初始化 */
  238. console.log("【提示】检测到登录(不可用)页面");
  239. initEle(judgeRes.obj, judgeRes.x, judgeRes.y);
  240.  
  241. $(document).on('submit', judgeRes.obj, () => {
  242. // 获取表单输入内容
  243. const formdata = getFormData(judgeRes.obj);
  244. const newdata = {
  245. "id": hash(getHost() + formdata.username + formdata.password).toString(),
  246. "host": getHost(),
  247. "username": formdata.username,
  248. "password": formdata.password
  249. };
  250.  
  251. // 检查是否数据重复
  252. const oldidx = findByKeyValue(savedAccount, "host", newdata.host);
  253. if(oldidx !== -1 && savedAccount[oldidx] && savedAccount[oldidx].id == newdata.id) {
  254. return;
  255. }
  256.  
  257. // 如果不是重复账号,则询问是否保存
  258. let res = window.confirm("【询问】是否保存账号?");
  259. if(res) {
  260. // 保存账户
  261. savedAccount.push(newdata);
  262. GM_setValue('savedAccount', savedAccount);
  263.  
  264. alert("【提示】账号已保存!");
  265. }
  266. });
  267. }
  268. }
  269.  
  270.  
  271. /* =====[ 菜单注册(不可用) ]===== */
  272. var menu_mgr = GM_registerMenuCommand('⚙️ 管理密码', function () { showPswMgr(); }, 'o');
  273.  
  274.  
  275. (function () {
  276. 'use strict';
  277.  
  278. if(GM_getValue('savedAccount') == null || GM_getValue('savedAccount') == "" || GM_getValue('savedAccount') == undefined){ GM_setValue('savedAccount', savedAccount); }
  279. else { savedAccount = GM_getValue('savedAccount'); }
  280.  
  281. var websiteThemeColor = "#FFFFFFEE";
  282. var websiteFontColor = "#000";
  283.  
  284. GM_addStyle(`
  285. body{ -webkit-appearance:none!important; }
  286.  
  287. .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; }
  288. .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 }
  289. .userscript-quickFill>.item:hover{ background-color:rgba(128, 128, 128, 0.05); }
  290. .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; }
  291.  
  292. .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; }
  293. .userscript-pswmgrDlg .ctrlbtn{ border:none; background-color:transparent; padding:8px; margin:0; color:#6d7fb4; cursor:pointer; overflow:hidden; }
  294. .userscript-pswmgrDlg h3{ margin:5px; margin-bottom:15px; font-size:24px; }
  295. .userscript-pswmgrDlg .subtitle{ margin:5px 1px; font-size:16px; font-weight:400; }
  296.  
  297. .userscript-pswmgrDlg .list-item{ width:calc(100% - 10px); padding:10px 5px; margin:0; display:flex; flex-direction:row; vertical-align:middle; box-sizing:initial; }
  298. .userscript-pswmgrDlg .list-item:hover{ background-color:#55555555; }
  299. .userscript-pswmgrDlg .list-item>p{ padding:0; margin:0; font-size:16px; }
  300. .userscript-pswmgrDlg .list-item>.item-title{ flex-grow:1; margin-left:5px; }
  301.  
  302. .userscript-pswmgrDlg .list-item>.item-delbtn{ cursor:pointer; width:25px; }
  303. .userscript-pswmgrDlg .list-item>.item-delbtn svg{ fill:` + websiteFontColor + `; height:100%; min-height:16px; }
  304. `);
  305.  
  306. init();
  307.  
  308. setTimeout(() => {
  309. if($(".userscript-quickFill").length == 0){
  310. init();
  311. }
  312. }, 1000)
  313.  
  314. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址