Greasy Fork is available in English.
在线随机密码生成器,可配置长度、字符集,一键复制
// ==UserScript==
// @name 随机密码生成器
// @version 1.0
// @description 在线随机密码生成器,可配置长度、字符集,一键复制
// @author You
// @match *://*/*
// @grant GM_setClipboard
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @namespace http://greasyfork.icu/users/1592203
// ==/UserScript==
(function () {
'use strict';
// ========== 默认配置 ==========
const DEFAULT_CONFIG = {
length: 16,
uppercase: true,
lowercase: true,
numbers: true,
symbols: true,
excludeAmbiguous: false, // 排除易混淆字符
};
const CHARSETS = {
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
lowercase: 'abcdefghijklmnopqrstuvwxyz',
numbers: '0123456789',
symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?/~`',
};
const AMBIGUOUS_CHARS = 'Il1O0o';
// ========== 加载/保存配置 ==========
function loadConfig() {
const saved = GM_getValue('pwdGenConfig', null);
return saved ? { ...DEFAULT_CONFIG, ...JSON.parse(saved) } : { ...DEFAULT_CONFIG };
}
function saveConfig(config) {
GM_setValue('pwdGenConfig', JSON.stringify(config));
}
// ========== 核心逻辑 ==========
function buildCharset(config) {
let chars = '';
if (config.uppercase) chars += CHARSETS.uppercase;
if (config.lowercase) chars += CHARSETS.lowercase;
if (config.numbers) chars += CHARSETS.numbers;
if (config.symbols) chars += CHARSETS.symbols;
if (config.excludeAmbiguous) {
chars = chars.split('').filter(c => !AMBIGUOUS_CHARS.includes(c)).join('');
}
return chars;
}
function generatePassword(config) {
const charset = buildCharset(config);
if (!charset) return '⚠ 请至少选择一种字符类型';
const array = new Uint32Array(config.length);
crypto.getRandomValues(array);
let password = '';
for (let i = 0; i < config.length; i++) {
password += charset[array[i] % charset.length];
}
return password;
}
// 计算密码强度
function getStrength(password) {
let score = 0;
if (password.length >= 8) score++;
if (password.length >= 12) score++;
if (password.length >= 16) score++;
if (/[a-z]/.test(password)) score++;
if (/[A-Z]/.test(password)) score++;
if (/[0-9]/.test(password)) score++;
if (/[^a-zA-Z0-9]/.test(password)) score++;
return Math.min(score, 7);
}
function getStrengthLabel(score) {
if (score <= 2) return { text: '弱', color: '#ef4444' };
if (score <= 4) return { text: '中', color: '#f59e0b' };
if (score <= 5) return { text: '强', color: '#22c55e' };
return { text: '极强', color: '#10b981' };
}
// ========== UI 样式 ==========
GM_addStyle(`
#pwd-gen-toggle {
position: fixed;
bottom: 24px;
right: 24px;
width: 48px;
height: 48px;
border-radius: 50%;
background: #6366f1;
color: #fff;
border: none;
cursor: pointer;
font-size: 22px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 14px rgba(99,102,241,0.4);
z-index: 2147483647;
transition: transform 0.2s, box-shadow 0.2s;
user-select: none;
}
#pwd-gen-toggle:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(99,102,241,0.5);
}
#pwd-gen-panel {
position: fixed;
bottom: 84px;
right: 24px;
width: 340px;
background: #1e1e2e;
border-radius: 16px;
padding: 24px;
z-index: 2147483646;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #cdd6f4;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
display: none;
animation: pwdGenSlideUp 0.25s ease-out;
}
@keyframes pwdGenSlideUp {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
#pwd-gen-panel.visible { display: block; }
.pg-title {
font-size: 16px;
font-weight: 700;
color: #fff;
margin: 0 0 18px 0;
letter-spacing: 0.5px;
}
.pg-output-wrap {
display: flex;
gap: 8px;
margin-bottom: 14px;
}
.pg-output {
flex: 1;
background: #313244;
border: 1px solid #45475a;
border-radius: 10px;
padding: 12px 14px;
font-family: 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
font-size: 15px;
color: #a6e3a1;
word-break: break-all;
line-height: 1.5;
min-height: 44px;
user-select: all;
}
.pg-copy-btn {
background: #6366f1;
color: #fff;
border: none;
border-radius: 10px;
padding: 0 16px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
white-space: nowrap;
transition: background 0.2s;
}
.pg-copy-btn:hover { background: #818cf8; }
.pg-copy-btn.copied { background: #22c55e; }
.pg-strength-bar {
height: 4px;
border-radius: 2px;
background: #313244;
margin-bottom: 18px;
overflow: hidden;
}
.pg-strength-fill {
height: 100%;
border-radius: 2px;
transition: width 0.3s, background 0.3s;
}
.pg-strength-label {
font-size: 12px;
margin-bottom: 6px;
color: #a6adc8;
}
.pg-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.pg-label {
font-size: 13px;
color: #a6adc8;
}
.pg-length-input {
width: 64px;
background: #313244;
border: 1px solid #45475a;
border-radius: 8px;
padding: 6px 10px;
color: #cdd6f4;
font-size: 14px;
text-align: center;
outline: none;
}
.pg-length-input:focus { border-color: #6366f1; }
.pg-slider {
width: 140px;
accent-color: #6366f1;
}
/* 自定义开关 */
.pg-switch {
position: relative;
width: 40px;
height: 22px;
cursor: pointer;
}
.pg-switch input { display: none; }
.pg-switch-track {
position: absolute;
inset: 0;
background: #45475a;
border-radius: 11px;
transition: background 0.2s;
}
.pg-switch input:checked + .pg-switch-track { background: #6366f1; }
.pg-switch-thumb {
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
background: #fff;
border-radius: 50%;
transition: transform 0.2s;
pointer-events: none;
}
.pg-switch input:checked ~ .pg-switch-thumb { transform: translateX(18px); }
.pg-gen-btn {
width: 100%;
padding: 12px;
margin-top: 6px;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: #fff;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 14px;
font-weight: 700;
letter-spacing: 1px;
transition: opacity 0.2s;
}
.pg-gen-btn:hover { opacity: 0.9; }
.pg-gen-btn:active { transform: scale(0.98); }
`);
// ========== 构建 DOM ==========
let config = loadConfig();
let currentPassword = '';
let hoverZoneActive = false;
let hoverZoneTimer = null;
// 悬浮按钮
const toggle = document.createElement('button');
toggle.id = 'pwd-gen-toggle';
toggle.textContent = '🔑';
toggle.title = '密码生成器 (Alt+P)';
toggle.style.opacity = '0';
toggle.style.pointerEvents = 'none';
document.body.appendChild(toggle);
// 鼠标靠近右下角时显示按钮
const hoverZone = document.createElement('div');
hoverZone.style.cssText = `
position: fixed;
bottom: 0;
right: 0;
width: 120px;
height: 120px;
z-index: 2147483645;
`;
document.body.appendChild(hoverZone);
hoverZone.addEventListener('mouseenter', () => {
clearTimeout(hoverZoneTimer);
toggle.style.opacity = '1';
toggle.style.pointerEvents = 'auto';
hoverZoneActive = true;
});
// 鼠标离开按钮和角落区域后隐藏
function scheduleHide() {
hoverZoneTimer = setTimeout(() => {
if (!panel.classList.contains('visible')) {
toggle.style.opacity = '0';
toggle.style.pointerEvents = 'none';
}
hoverZoneActive = false;
}, 600);
}
hoverZone.addEventListener('mouseleave', scheduleHide);
toggle.addEventListener('mouseleave', (e) => {
// 如果鼠标移向面板,不隐藏
if (!panel.contains(e.relatedTarget)) {
scheduleHide();
}
});
toggle.addEventListener('mouseenter', () => {
clearTimeout(hoverZoneTimer);
});
// 面板关闭时也隐藏按钮
const origToggleClick = () => {
panel.classList.toggle('visible');
if (panel.classList.contains('visible') && !currentPassword) {
generate();
}
if (!panel.classList.contains('visible') && !hoverZoneActive) {
toggle.style.opacity = '0';
toggle.style.pointerEvents = 'none';
}
};
// ========== 面板(与之前相同)==========
const panel = document.createElement('div');
panel.id = 'pwd-gen-panel';
panel.innerHTML = `
<div class="pg-title">🔐 随机密码生成器</div>
<div class="pg-output-wrap">
<div class="pg-output" id="pg-output"></div>
<button class="pg-copy-btn" id="pg-copy">复制</button>
</div>
<div class="pg-strength-label" id="pg-strength-label">强度:-</div>
<div class="pg-strength-bar"><div class="pg-strength-fill" id="pg-strength-fill"></div></div>
<div class="pg-row">
<span class="pg-label">密码长度</span>
<input type="number" class="pg-length-input" id="pg-length" min="4" max="128" value="">
<input type="range" class="pg-slider" id="pg-slider" min="4" max="128" value="">
</div>
<div class="pg-row">
<span class="pg-label">大写字母 A-Z</span>
<label class="pg-switch">
<input type="checkbox" id="pg-upper" >
<div class="pg-switch-track"></div>
<div class="pg-switch-thumb"></div>
</label>
</div>
<div class="pg-row">
<span class="pg-label">小写字母 a-z</span>
<label class="pg-switch">
<input type="checkbox" id="pg-lower" >
<div class="pg-switch-track"></div>
<div class="pg-switch-thumb"></div>
</label>
</div>
<div class="pg-row">
<span class="pg-label">数字 0-9</span>
<label class="pg-switch">
<input type="checkbox" id="pg-numbers" >
<div class="pg-switch-track"></div>
<div class="pg-switch-thumb"></div>
</label>
</div>
<div class="pg-row">
<span class="pg-label">特殊符号</span>
<label class="pg-switch">
<input type="checkbox" id="pg-symbols" >
<div class="pg-switch-track"></div>
<div class="pg-switch-thumb"></div>
</label>
</div>
<div class="pg-row">
<span class="pg-label">排除易混淆字符</span>
<label class="pg-switch">
<input type="checkbox" id="pg-ambiguous" >
<div class="pg-switch-track"></div>
<div class="pg-switch-thumb"></div>
</label>
</div>
<button class="pg-gen-btn" id="pg-generate">生成密码</button>
`;
document.body.appendChild(panel);
// ========== 元素引用 ==========
const outputEl = document.getElementById('pg-output');
const copyBtn = document.getElementById('pg-copy');
const lengthInput = document.getElementById('pg-length');
const sliderInput = document.getElementById('pg-slider');
const upperCb = document.getElementById('pg-upper');
const lowerCb = document.getElementById('pg-lower');
const numbersCb = document.getElementById('pg-numbers');
const symbolsCb = document.getElementById('pg-symbols');
const ambiguousCb = document.getElementById('pg-ambiguous');
const genBtn = document.getElementById('pg-generate');
const strengthFill = document.getElementById('pg-strength-fill');
const strengthLabel = document.getElementById('pg-strength-label');
// ========== 初始化配置 ==========
function applyConfig(cfg) {
lengthInput.value = cfg.length;
sliderInput.value = cfg.length;
upperCb.checked = cfg.uppercase;
lowerCb.checked = cfg.lowercase;
numbersCb.checked = cfg.numbers;
symbolsCb.checked = cfg.symbols;
ambiguousCb.checked = cfg.excludeAmbiguous;
}
applyConfig(config);
// ========== 读取当前配置 ==========
function readConfig() {
config.length = Math.max(4, Math.min(128, parseInt(lengthInput.value) || 16));
config.uppercase = upperCb.checked;
config.lowercase = lowerCb.checked;
config.numbers = numbersCb.checked;
config.symbols = symbolsCb.checked;
config.excludeAmbiguous = ambiguousCb.checked;
return config;
}
// ========== 更新强度显示 ==========
function updateStrength(password) {
const score = getStrength(password);
const { text, color } = getStrengthLabel(score);
const percent = Math.round((score / 7) * 100);
strengthFill.style.width = percent + '%';
strengthFill.style.background = color;
strengthLabel.textContent = `强度:${text}`;
strengthLabel.style.color = color;
}
// ========== 生成并显示 ==========
function generate() {
const cfg = readConfig();
currentPassword = generatePassword(cfg);
outputEl.textContent = currentPassword;
updateStrength(currentPassword);
saveConfig(cfg);
}
// ========== 事件绑定 ==========
toggle.addEventListener('click', origToggleClick);
// 面板关闭按钮(点击面板外部关闭)
document.addEventListener('click', (e) => {
if (panel.classList.contains('visible') &&
!panel.contains(e.target) &&
!toggle.contains(e.target)) {
panel.classList.remove('visible');
if (!hoverZoneActive) {
toggle.style.opacity = '0';
toggle.style.pointerEvents = 'none';
}
}
});
// 快捷键 Alt+P
document.addEventListener('keydown', (e) => {
if (e.altKey && e.key.toLowerCase() === 'l') {
e.preventDefault();
toggle.style.opacity = '1';
toggle.style.pointerEvents = 'auto';
hoverZoneActive = true;
origToggleClick();
}
});
genBtn.addEventListener('click', generate);
// 长度输入与滑块同步
lengthInput.addEventListener('input', () => {
sliderInput.value = lengthInput.value;
});
sliderInput.addEventListener('input', () => {
lengthInput.value = sliderInput.value;
});
// 复制
copyBtn.addEventListener('click', () => {
if (!currentPassword) return;
GM_setClipboard(currentPassword, 'text');
copyBtn.textContent = '已复制 ✓';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
}, 1500);
});
// 油猴菜单命令
GM_registerMenuCommand('🔐 打开密码生成器', () => {
toggle.style.opacity = '1';
toggle.style.pointerEvents = 'auto';
hoverZoneActive = true;
panel.classList.add('visible');
if (!currentPassword) generate();
});
})();