// ==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();
}
})();