AIRole.net 图片发送器

在图片上右键发送到AIRole.net进行角色生成

当前为 2025-06-18 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AIRole.net 图片发送器
// @name:en      AIRole.net Image Sender
// @namespace    https://airole.net/
// @version      1.0.0
// @description  在图片上右键发送到AIRole.net进行角色生成
// @description:en Right-click on images to send to AIRole.net for character generation
// @author       AIRole.net
// @match        http://*/*
// @match        https://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_openInTab
// @grant        GM_addStyle
// @icon         https://airole.net/logo.128.png
// @homepage     https://airole.net
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 默认配置
    const DEFAULT_CONFIG = {
        websiteUrl: 'https://airole.net',
        language: 'auto', // auto, zh, en
        enabled: true
    };

    // 多语言文本
    const i18n = {
        zh: {
            contextMenuTitle: '发送到 AIRole.net',
            settingsTitle: 'AIRole.net 图片发送器设置',
            websiteUrlLabel: '目标网站地址:',
            saveButton: '保存设置',
            resetButton: '重置为默认',
            enabledLabel: '启用插件',
            languageLabel: '语言:',
            languageAuto: '自动',
            languageChinese: '中文',
            languageEnglish: 'English',
            settingsSaved: '设置已保存!',
            invalidUrl: '请输入有效的网站地址',
            confirmReset: '确定要重置为默认设置吗?',
            resetSuccess: '已重置为默认设置',
            instructions: '在任意图片上右键,选择"发送到 AIRole.net"即可使用'
        },
        en: {
            contextMenuTitle: 'Send to AIRole.net',
            settingsTitle: 'AIRole.net Image Sender Settings',
            websiteUrlLabel: 'Target Website URL:',
            saveButton: 'Save Settings',
            resetButton: 'Reset to Default',
            enabledLabel: 'Enable Plugin',
            languageLabel: 'Language:',
            languageAuto: 'Auto',
            languageChinese: '中文',
            languageEnglish: 'English',
            settingsSaved: 'Settings saved!',
            invalidUrl: 'Please enter a valid website URL',
            confirmReset: 'Are you sure you want to reset to default settings?',
            resetSuccess: 'Reset to default settings',
            instructions: 'Right-click on any image and select "Send to AIRole.net" to use'
        }
    };

    // 获取当前语言
    function getCurrentLanguage() {
        const config = getConfig();
        if (config.language === 'auto') {
            return navigator.language.startsWith('zh') ? 'zh' : 'en';
        }
        return config.language;
    }

    // 获取国际化文本
    function getText(key) {
        const lang = getCurrentLanguage();
        return i18n[lang][key] || i18n.en[key] || key;
    }

    // 获取配置
    function getConfig() {
        const saved = GM_getValue('config', '{}');
        try {
            const config = JSON.parse(saved);
            return { ...DEFAULT_CONFIG, ...config };
        } catch (e) {
            return DEFAULT_CONFIG;
        }
    }

    // 保存配置
    function saveConfig(config) {
        GM_setValue('config', JSON.stringify(config));
    }

    // 验证URL
    function isValidUrl(string) {
        try {
            const url = new URL(string);
            return url.protocol === 'http:' || url.protocol === 'https:';
        } catch (_) {
            return false;
        }
    }

    // 添加样式
    GM_addStyle(`
        .airole-context-menu {
            position: fixed;
            background: white;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-shadow: 2px 2px 10px rgba(0,0,0,0.1);
            padding: 8px 0;
            z-index: 10000;
            font-family: Arial, sans-serif;
            font-size: 14px;
            min-width: 180px;
        }
        
        .airole-context-menu-item {
            padding: 8px 16px;
            cursor: pointer;
            user-select: none;
            color: #333;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .airole-context-menu-item:hover {
            background-color: #f0f0f0;
        }
        
        .airole-context-menu-item::before {
            content: "🖼️";
            width: 16px;
            height: 16px;
            display: inline-block;
        }
        
        .airole-settings-dialog {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 1px solid #ccc;
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
            padding: 24px;
            z-index: 10001;
            font-family: Arial, sans-serif;
            min-width: 400px;
            max-width: 90vw;
        }
        
        .airole-settings-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.5);
            z-index: 10000;
        }
        
        .airole-settings-title {
            font-size: 18px;
            font-weight: bold;
            margin-bottom: 16px;
            color: #333;
        }
        
        .airole-settings-field {
            margin-bottom: 16px;
        }
        
        .airole-settings-label {
            display: block;
            margin-bottom: 4px;
            font-weight: bold;
            color: #555;
        }
        
        .airole-settings-input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
            box-sizing: border-box;
        }
        
        .airole-settings-select {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
            box-sizing: border-box;
        }
        
        .airole-settings-checkbox {
            margin-right: 8px;
        }
        
        .airole-settings-buttons {
            display: flex;
            gap: 8px;
            justify-content: flex-end;
            margin-top: 24px;
        }
        
        .airole-settings-button {
            padding: 8px 16px;
            border: 1px solid #ccc;
            border-radius: 4px;
            background: white;
            cursor: pointer;
            font-size: 14px;
        }
        
        .airole-settings-button.primary {
            background: #007cba;
            color: white;
            border-color: #007cba;
        }
        
        .airole-settings-button:hover {
            opacity: 0.8;
        }
        
        .airole-settings-instructions {
            background: #f8f9fa;
            border: 1px solid #e9ecef;
            border-radius: 4px;
            padding: 12px;
            margin-top: 16px;
            font-size: 13px;
            color: #666;
        }
    `);

    // 创建设置对话框
    function createSettingsDialog() {
        const config = getConfig();
        
        // 创建遮罩层
        const overlay = document.createElement('div');
        overlay.className = 'airole-settings-overlay';
        
        // 创建对话框
        const dialog = document.createElement('div');
        dialog.className = 'airole-settings-dialog';
        
        dialog.innerHTML = `
            <div class="airole-settings-title">${getText('settingsTitle')}</div>
            
            <div class="airole-settings-field">
                <label class="airole-settings-label">
                    <input type="checkbox" class="airole-settings-checkbox" id="enabledCheckbox" ${config.enabled ? 'checked' : ''}>
                    ${getText('enabledLabel')}
                </label>
            </div>
            
            <div class="airole-settings-field">
                <label class="airole-settings-label" for="websiteUrl">${getText('websiteUrlLabel')}</label>
                <input type="text" id="websiteUrl" class="airole-settings-input" value="${config.websiteUrl}" placeholder="https://airole.net">
            </div>
            
            <div class="airole-settings-field">
                <label class="airole-settings-label" for="language">${getText('languageLabel')}</label>
                <select id="language" class="airole-settings-select">
                    <option value="auto" ${config.language === 'auto' ? 'selected' : ''}>${getText('languageAuto')}</option>
                    <option value="zh" ${config.language === 'zh' ? 'selected' : ''}>${getText('languageChinese')}</option>
                    <option value="en" ${config.language === 'en' ? 'selected' : ''}>${getText('languageEnglish')}</option>
                </select>
            </div>
            
            <div class="airole-settings-instructions">
                ${getText('instructions')}
            </div>
            
            <div class="airole-settings-buttons">
                <button class="airole-settings-button" id="resetButton">${getText('resetButton')}</button>
                <button class="airole-settings-button" id="cancelButton">取消</button>
                <button class="airole-settings-button primary" id="saveButton">${getText('saveButton')}</button>
            </div>
        `;
        
        // 绑定事件
        const saveButton = dialog.querySelector('#saveButton');
        const cancelButton = dialog.querySelector('#cancelButton');
        const resetButton = dialog.querySelector('#resetButton');
        const websiteUrlInput = dialog.querySelector('#websiteUrl');
        const languageSelect = dialog.querySelector('#language');
        const enabledCheckbox = dialog.querySelector('#enabledCheckbox');
        
        function closeDialog() {
            document.body.removeChild(overlay);
            document.body.removeChild(dialog);
        }
        
        saveButton.addEventListener('click', () => {
            const websiteUrl = websiteUrlInput.value.trim();
            
            if (!isValidUrl(websiteUrl)) {
                alert(getText('invalidUrl'));
                return;
            }
            
            const newConfig = {
                websiteUrl: websiteUrl,
                language: languageSelect.value,
                enabled: enabledCheckbox.checked
            };
            
            saveConfig(newConfig);
            alert(getText('settingsSaved'));
            closeDialog();
        });
        
        cancelButton.addEventListener('click', closeDialog);
        
        resetButton.addEventListener('click', () => {
            if (confirm(getText('confirmReset'))) {
                saveConfig(DEFAULT_CONFIG);
                alert(getText('resetSuccess'));
                closeDialog();
            }
        });
        
        overlay.addEventListener('click', closeDialog);
        
        // 添加到页面
        document.body.appendChild(overlay);
        document.body.appendChild(dialog);
        
        // 聚焦到网站URL输入框
        websiteUrlInput.focus();
        websiteUrlInput.select();
    }

    // 发送图片到 AIRole.net
    function sendImageToAIRole(imageUrl) {
        const config = getConfig();
        if (!config.enabled) {
            return;
        }
        
        const targetUrl = `${config.websiteUrl}?img=${encodeURIComponent(imageUrl)}`;
        GM_openInTab(targetUrl, { active: true });
    }

    // 创建右键菜单
    function createContextMenu(event, imageUrl) {
        const config = getConfig();
        if (!config.enabled) {
            return;
        }
        
        // 移除已存在的菜单
        const existingMenu = document.querySelector('.airole-context-menu');
        if (existingMenu) {
            existingMenu.remove();
        }
        
        // 创建菜单
        const menu = document.createElement('div');
        menu.className = 'airole-context-menu';
        menu.style.left = event.pageX + 'px';
        menu.style.top = event.pageY + 'px';
        
        const menuItem = document.createElement('div');
        menuItem.className = 'airole-context-menu-item';
        menuItem.textContent = getText('contextMenuTitle');
        
        menuItem.addEventListener('click', () => {
            sendImageToAIRole(imageUrl);
            menu.remove();
        });
        
        menu.appendChild(menuItem);
        document.body.appendChild(menu);
        
        // 调整位置以确保菜单不会超出窗口
        const menuRect = menu.getBoundingClientRect();
        if (menuRect.right > window.innerWidth) {
            menu.style.left = (event.pageX - menuRect.width) + 'px';
        }
        if (menuRect.bottom > window.innerHeight) {
            menu.style.top = (event.pageY - menuRect.height) + 'px';
        }
        
        // 点击其他地方时隐藏菜单
        setTimeout(() => {
            const hideMenu = (e) => {
                if (!menu.contains(e.target)) {
                    menu.remove();
                    document.removeEventListener('click', hideMenu);
                }
            };
            document.addEventListener('click', hideMenu);
        }, 0);
    }

    // 初始化
    function init() {
        // 注册设置菜单命令
        GM_registerMenuCommand(getText('settingsTitle'), createSettingsDialog);
        
        // 监听图片右键事件
        document.addEventListener('contextmenu', (event) => {
            const target = event.target;
            
            // 检查是否是图片
            if (target.tagName === 'IMG' && target.src) {
                event.preventDefault();
                createContextMenu(event, target.src);
            }
        });
        
        // 阻止默认右键菜单(仅对图片)
        document.addEventListener('contextmenu', (event) => {
            const existingMenu = document.querySelector('.airole-context-menu');
            if (existingMenu && event.target.tagName === 'IMG') {
                event.preventDefault();
            }
        });
    }

    // 页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();