Greasy Fork is available in English.

随机密码生成器

在页面中添加一个随机密码生成器,可以生成密码并记录历史记录,提供显示/隐藏功能和拖动按钮功能。

// ==UserScript==
// @name         随机密码生成器
// @namespace    http://tampermonkey.net/
// @version      1.5.1
// @description  在页面中添加一个随机密码生成器,可以生成密码并记录历史记录,提供显示/隐藏功能和拖动按钮功能。
// @author       wll
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @license      AGPL-3.0-or-later
// ==/UserScript==

/*
### 脚本特点及好处:
- **在页面中添加一个随机密码生成器**:生成器可以生成复杂的随机密码。
- **记录历史记录**:生成的密码会记录在历史记录中,并可以删除特定记录。
- **提供显示/隐藏功能**:通过油猴菜单命令控制生成器打开按钮的显示或隐藏状态。
- **拖动按钮功能**:生成器打开按钮可以拖动,并记录其拖动后的位置信息,防止拖动出页面之外。
*/

(function() {
    'use strict';

    const settingsKey = 'passwordGeneratorSettings';
    const historyKey = 'passwordGeneratorHistory';
    const positionKey = 'buttonPosition';
    const buttonVisibleKey = 'buttonVisible';

    // 读取设置
    function loadSettings() {
        const settings = GM_getValue(settingsKey, null);
        return settings ? JSON.parse(settings) : {
            upper: false,
            lower: false,
            numbers: false,
            special: false,
            minLength: 8,
            maxLength: 12,
            quantity: 1
        };
    }

    // 保存设置
    function saveSettings(settings) {
        GM_setValue(settingsKey, JSON.stringify(settings));
    }

    // 读取历史记录
    function loadHistory() {
        const history = GM_getValue(historyKey, null);
        return history ? JSON.parse(history) : [];
    }

    // 保存历史记录
    function saveHistory(history) {
        GM_setValue(historyKey, JSON.stringify(history));
    }

    // 读取按钮位置
    function loadPosition() {
        return GM_getValue(positionKey, { top: '10px', right: '10px' });
    }

    // 保存按钮位置
    function savePosition(position) {
        GM_setValue(positionKey, position);
    }

    // 读取按钮可见状态
    function loadButtonVisible() {
        return GM_getValue(buttonVisibleKey, true);
    }

    // 保存按钮可见状态
    function saveButtonVisible(visible) {
        GM_setValue(buttonVisibleKey, visible);
    }

    const settings = loadSettings();
    const history = loadHistory();
    let buttonVisible = loadButtonVisible();

    // 创建按钮打开密码生成器
    const button = document.createElement('button');
    button.innerText = '打开密码生成器';
    button.style.position = 'fixed';
    button.style.zIndex = '1000';
    button.style.padding = '10px 20px';
    button.style.backgroundColor = '#4CAF50';
    button.style.color = 'white';
    button.style.border = 'none';
    button.style.borderRadius = '5px';
    button.style.cursor = 'pointer';
    const savedPosition = loadPosition();
    button.style.top = savedPosition.top;
    button.style.right = savedPosition.right;

    if (buttonVisible) {
        document.body.appendChild(button);
    }

    // 注册油猴菜单命令
    GM_registerMenuCommand('显示/隐藏菜单按钮', () => {
        buttonVisible = !buttonVisible;
        if (buttonVisible) {
            document.body.appendChild(button);
        } else {
            if (button.parentNode) {
                button.parentNode.removeChild(button);
            }
        }
        saveButtonVisible(buttonVisible);
    });

    // 创建生成器界面
    const generatorDiv = document.createElement('div');
    generatorDiv.style.display = 'none';
    generatorDiv.style.position = 'fixed';
    generatorDiv.style.top = '50%';
    generatorDiv.style.left = '50%';
    generatorDiv.style.transform = 'translate(-50%, -50%)';
    generatorDiv.style.padding = '20px';
    generatorDiv.style.backgroundColor = 'white';
    generatorDiv.style.border = '1px solid #ccc';
    generatorDiv.style.zIndex = '1000';
    generatorDiv.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.1)';
    generatorDiv.style.textAlign = 'center';
    generatorDiv.style.width = '300px';
    generatorDiv.style.maxHeight = '400px';
    generatorDiv.style.overflow = 'auto';
    document.body.appendChild(generatorDiv);

    // 添加选项
    generatorDiv.innerHTML = `
        <div style="position: relative;">
            <h2>随机密码生成器</h2>
            <button id="close" style="position: absolute; top: 10px; right: 10px; background: none; border: none; font-size: 16px; cursor: pointer;">✖</button>
        </div>
        <div style="text-align: left; border-bottom: 1px solid #ccc; padding-bottom: 10px;">
            <h3>选项</h3>
            <label><input type="checkbox" id="upper"> 字母大写</label><br>
            <label><input type="checkbox" id="lower"> 字母小写</label><br>
            <label><input type="checkbox" id="numbers"> 数字</label><br>
            <label><input type="checkbox" id="special"> 特殊字符</label><br>
            <label>生成长度:<input type="number" id="minLength" value="${settings.minLength}" min="1" style="width: 50px;"> 到 <input type="number" id="maxLength" value="${settings.maxLength}" min="1" style="width: 50px;"></label><br>
            <label>生成数量:<input type="number" id="quantity" value="${settings.quantity}" min="1" style="width: 50px;"></label><br>
            <button id="generate" style="margin-top: 10px; padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer;">生成密码</button><br>
        </div>
        <div style="text-align: left; border-bottom: 1px solid #ccc; padding-bottom: 10px; margin-top: 10px;">
            <h3>生成的密码</h3>
            <div id="passwordOutput" style="width: 100%; margin-top: 10px;"></div>
        </div>
        <div style="text-align: left; margin-top: 10px;">
            <h3>历史记录</h3>
            <div id="historyOutput" style="width: 100%; margin-top: 10px; max-height: 100px; overflow-y: auto;"></div>
        </div>
    `;

    // 提示框
    const toast = document.createElement('div');
    toast.style.position = 'fixed';
    toast.style.bottom = '20px';
    toast.style.right = '20px';
    toast.style.backgroundColor = 'rgba(0,0,0,0.7)';
    toast.style.color = 'white';
    toast.style.padding = '10px';
    toast.style.borderRadius = '5px';
    toast.style.zIndex = '1001';
    toast.style.display = 'none';
    document.body.appendChild(toast);

    // 显示提示
    function showToast(message) {
        toast.innerText = message;
        toast.style.display = 'block';
        setTimeout(() => {
            toast.style.display = 'none';
        }, 3000);
    }

    // 生成密码
    function generatePassword() {
        const upper = document.getElementById('upper').checked;
        const lower = document.getElementById('lower').checked;
        const numbers = document.getElementById('numbers').checked;
        const special = document.getElementById('special').checked;
        const minLength = parseInt(document.getElementById('minLength').value);
        const maxLength = parseInt(document.getElementById('maxLength').value);
        const quantity = parseInt(document.getElementById('quantity').value);

        const upperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        const lowerChars = 'abcdefghijklmnopqrstuvwxyz';
        const numberChars = '0123456789';
        const specialChars = '!@#$%^&*()_+[]{}|;:,.<>?';

        let allChars = '';
        if (upper) allChars += upperChars;
        if (lower) allChars += lowerChars;
        if (numbers) allChars += numberChars;
        if (special) allChars += specialChars;

        if (allChars === '') {
            showToast('请至少选择一种复杂度类型!');
            return [];
        }

        const passwords = [];
        for (let j = 0; j < quantity; j++) {
            const passwordLength = Math.floor(Math.random() * (maxLength - minLength + 1)) + minLength;
            let password = '';
            for (let i = 0; i < passwordLength; i++) {
                password += allChars.charAt(Math.floor(Math.random() * allChars.length));
            }
            passwords.push(password);
        }
        return passwords;
    }

    // 复制到剪贴板
    function copyToClipboard(text) {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
        showToast('密码已复制到剪贴板!');
    }

    // 更新UI
    function updateOutput(passwords) {
        const output = document.getElementById('passwordOutput');
        output.innerHTML = passwords.map((pwd, index) => `
            <div style="display: flex; justify-content: space-between; align-items: center;">
                <span>${pwd}</span>
                <button class="copy" data-password="${pwd}" style="background: none; border: none; color: blue; cursor: pointer;">复制</button>
            </div>
        `).join('');
    }

    function updateHistoryOutput() {
        const output = document.getElementById('historyOutput');
        output.innerHTML = history.map((pwd, index) => `
            <div style="display: flex; justify-content: space-between; align-items: center;">
                <span>${pwd}</span>
                <button class="delete" data-index="${index}" style="background: none; border: none; color: red; cursor: pointer;">删除</button>
            </div>
        `).join('');
    }

    // 点击事件
    button.addEventListener('click', () => {
        generatorDiv.style.display = generatorDiv.style.display === 'none' ? 'block' : 'none';
    });

    document.getElementById('close').addEventListener('click', () => {
        generatorDiv.style.display = 'none';
    });

    document.getElementById('generate').addEventListener('click', () => {
        const passwords = generatePassword();
        updateOutput(passwords);

        // 保存设置
        const settings = {
            upper: document.getElementById('upper').checked,
            lower: document.getElementById('lower').checked,
            numbers: document.getElementById('numbers').checked,
            special: document.getElementById('special').checked,
            minLength: parseInt(document.getElementById('minLength').value),
            maxLength: parseInt(document.getElementById('maxLength').value),
            quantity: parseInt(document.getElementById('quantity').value)
        };
        saveSettings(settings);
    });

    document.getElementById('passwordOutput').addEventListener('click', (e) => {
        if (e.target.classList.contains('copy')) {
            const password = e.target.getAttribute('data-password');
            copyToClipboard(password);
            history.push(password);
            saveHistory(history);
            updateHistoryOutput();
        }
    });

    document.getElementById('historyOutput').addEventListener('click', (e) => {
        if (e.target.classList.contains('delete')) {
            const index = parseInt(e.target.getAttribute('data-index'));
            history.splice(index, 1);
            saveHistory(history);
            updateHistoryOutput();
        }
    });

    // 使按钮可拖动
    button.addEventListener('mousedown', (e) => {
        e.preventDefault();
        let offsetX = e.clientX - button.getBoundingClientRect().left;
        let offsetY = e.clientY - button.getBoundingClientRect().top;

        function mouseMoveHandler(e) {
            let newX = e.clientX - offsetX;
            let newY = e.clientY - offsetY;
            let maxX = window.innerWidth - button.offsetWidth;
            let maxY = window.innerHeight - button.offsetHeight;

            newX = Math.max(0, Math.min(newX, maxX));
            newY = Math.max(0, Math.min(newY, maxY));

            button.style.left = `${newX}px`;
            button.style.top = `${newY}px`;
            button.style.right = 'auto';
        }

        function mouseUpHandler() {
            savePosition({ top: button.style.top, left: button.style.left });
            document.removeEventListener('mousemove', mouseMoveHandler);
            document.removeEventListener('mouseup', mouseUpHandler);
        }

        document.addEventListener('mousemove', mouseMoveHandler);
        document.addEventListener('mouseup', mouseUpHandler);
    });

    // 初始化
    document.getElementById('upper').checked = settings.upper;
    document.getElementById('lower').checked = settings.lower;
    document.getElementById('numbers').checked = settings.numbers;
    document.getElementById('special').checked = settings.special;
    document.getElementById('minLength').value = settings.minLength;
    document.getElementById('maxLength').value = settings.maxLength;
    document.getElementById('quantity').value = settings.quantity;
    updateHistoryOutput();

})();