主要改动: 1. 移除微信公众号相关配置和检测规则 2. 将 @match 改为通配符 *://*/* 支持所有网站 3. 添加注释说明如何添加自定义网站检测规则 4. 保留通义听悟作为示例 5. 强调通用性,用户可自行扩展 脚本现在是真正的通用数据同步工具,不局限于特定网站。
612 lines
22 KiB
JavaScript
612 lines
22 KiB
JavaScript
// ==UserScript==
|
||
// @name 通用数据同步脚本
|
||
// @namespace http://tampermonkey.net/
|
||
// @version 3.0
|
||
// @description 通用数据同步服务客户端,支持自动检测项目和账号,可视化配置管理
|
||
// @author Hermes
|
||
// @match *://*/*
|
||
// @grant GM_xmlhttpRequest
|
||
// @grant GM_setClipboard
|
||
// @grant GM_notification
|
||
// @grant GM_setValue
|
||
// @grant GM_getValue
|
||
// @connect 47.122.126.244
|
||
// @connect *
|
||
// ==/UserScript==
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
// ==================== 配置管理 ====================
|
||
const DEFAULT_CONFIG = {
|
||
serverUrl: 'http://47.122.126.244:5001',
|
||
password: 'admin123123123',
|
||
|
||
// 自动检测配置
|
||
autoDetect: true, // 是否自动检测项目名和账号
|
||
tag: 'auto', // 'auto' 表示自动检测,或手动指定如 'tingwu:ykaayk@qq.com'
|
||
type: 'cookie', // cookie/token/credential/session/custom
|
||
|
||
// 过期配置
|
||
expiresIn: 604800, // 7天(秒)
|
||
|
||
// 其他配置
|
||
autoSync: false, // 是否自动同步(页面加载后自动上传)
|
||
syncInterval: 0, // 定时同步间隔(秒,0 表示不定时同步)
|
||
};
|
||
|
||
// 加载配置
|
||
function loadConfig() {
|
||
const saved = GM_getValue('sync_config', null);
|
||
if (saved) {
|
||
try {
|
||
return {...DEFAULT_CONFIG, ...JSON.parse(saved)};
|
||
} catch (e) {
|
||
console.error('配置解析失败,使用默认配置', e);
|
||
}
|
||
}
|
||
return {...DEFAULT_CONFIG};
|
||
}
|
||
|
||
// 保存配置
|
||
function saveConfig(config) {
|
||
GM_setValue('sync_config', JSON.stringify(config));
|
||
console.log('✅ 配置已保存');
|
||
}
|
||
|
||
// 全局配置对象
|
||
let CONFIG = loadConfig();
|
||
|
||
// ==================== 项目检测规则 ====================
|
||
// 用户可以根据需要添加自己的检测规则
|
||
const PROJECT_RULES = [
|
||
// 示例:通义听悟
|
||
{
|
||
name: 'tingwu',
|
||
match: (url) => url.includes('tingwu.aliyun.com'),
|
||
getAccount: () => {
|
||
// 从页面提取账号信息
|
||
const emailElement = document.querySelector('[data-account]');
|
||
if (emailElement) return emailElement.getAttribute('data-account');
|
||
|
||
// 从 localStorage 提取
|
||
try {
|
||
const userData = JSON.parse(localStorage.getItem('user_data') || '{}');
|
||
return userData.email || userData.username || 'unknown';
|
||
} catch {}
|
||
|
||
return 'unknown';
|
||
},
|
||
loginUrl: 'https://tingwu.aliyun.com/'
|
||
},
|
||
// 可以在这里添加更多网站的检测规则
|
||
// 示例格式:
|
||
// {
|
||
// name: '网站名称',
|
||
// match: (url) => url.includes('example.com'),
|
||
// getAccount: () => {
|
||
// // 返回账号标识
|
||
// return 'user@example.com';
|
||
// },
|
||
// loginUrl: 'https://example.com/login'
|
||
// }
|
||
];
|
||
|
||
// ==================== 核心功能 ====================
|
||
|
||
/**
|
||
* 自动检测项目和账号
|
||
*/
|
||
function autoDetectTag() {
|
||
const url = window.location.href;
|
||
|
||
for (const rule of PROJECT_RULES) {
|
||
if (rule.match(url)) {
|
||
const account = rule.getAccount();
|
||
return {
|
||
tag: `${rule.name}:${account}`,
|
||
loginUrl: rule.loginUrl,
|
||
project: rule.name,
|
||
account: account
|
||
};
|
||
}
|
||
}
|
||
|
||
return {
|
||
tag: 'unknown:unknown',
|
||
loginUrl: window.location.origin,
|
||
project: 'unknown',
|
||
account: 'unknown'
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取当前页面的 Cookie 数据
|
||
*/
|
||
function getCookieData() {
|
||
const cookies = document.cookie.split(';').map(c => {
|
||
const [name, ...valueParts] = c.trim().split('=');
|
||
return {
|
||
name: name,
|
||
value: valueParts.join('='),
|
||
domain: window.location.hostname,
|
||
path: '/'
|
||
};
|
||
}).filter(c => c.name);
|
||
|
||
return {
|
||
cookies: cookies,
|
||
raw_cookie: document.cookie,
|
||
user_agent: navigator.userAgent,
|
||
url: window.location.href
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 上传数据到服务器
|
||
*/
|
||
function uploadData(tag, type, data, metadata) {
|
||
const payload = {
|
||
password: CONFIG.password,
|
||
tag: tag,
|
||
type: type,
|
||
data: data,
|
||
metadata: metadata
|
||
};
|
||
|
||
GM_xmlhttpRequest({
|
||
method: 'POST',
|
||
url: `${CONFIG.serverUrl}/api/data`,
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
data: JSON.stringify(payload),
|
||
onload: function(response) {
|
||
let result;
|
||
try {
|
||
result = JSON.parse(response.responseText);
|
||
} catch (e) {
|
||
console.error('❌ 响应解析失败:', response.responseText);
|
||
GM_notification({
|
||
title: '同步失败',
|
||
text: '服务器响应格式错误',
|
||
timeout: 5000
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 检查是否成功:HTTP 200 且响应体包含 success: true
|
||
if (response.status === 200 && result.success === true) {
|
||
console.log('✅ 数据同步成功:', result);
|
||
GM_notification({
|
||
title: '数据同步成功',
|
||
text: `Tag: ${tag}\n时间: ${result.timestamp || '未知'}`,
|
||
timeout: 3000
|
||
});
|
||
} else {
|
||
// 失败:可能是 HTTP 错误码,或响应包含 error 字段
|
||
const errorMsg = result.error || result.message || response.statusText || '未知错误';
|
||
console.error('❌ 数据同步失败:', errorMsg, response);
|
||
GM_notification({
|
||
title: '数据同步失败',
|
||
text: `状态码: ${response.status}\n错误: ${errorMsg}`,
|
||
timeout: 5000
|
||
});
|
||
}
|
||
},
|
||
onerror: function(error) {
|
||
console.error('❌ 网络错误:', error);
|
||
GM_notification({
|
||
title: '网络错误',
|
||
text: '无法连接到同步服务器',
|
||
timeout: 5000
|
||
});
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 同步当前页面数据
|
||
*/
|
||
function syncData() {
|
||
let tag = CONFIG.tag;
|
||
let loginUrl = window.location.origin;
|
||
|
||
// 自动检测
|
||
if (CONFIG.autoDetect || tag === 'auto') {
|
||
const detected = autoDetectTag();
|
||
tag = detected.tag;
|
||
loginUrl = detected.loginUrl;
|
||
|
||
console.log('🔍 自动检测结果:', detected);
|
||
}
|
||
|
||
// 获取数据
|
||
let data;
|
||
if (CONFIG.type === 'cookie') {
|
||
data = getCookieData();
|
||
} else {
|
||
console.error('❌ 不支持的数据类型:', CONFIG.type);
|
||
return;
|
||
}
|
||
|
||
// 构建元数据
|
||
const metadata = {
|
||
expires_in: CONFIG.expiresIn,
|
||
login_url: loginUrl,
|
||
note: `自动同步 - ${new Date().toLocaleString()}`
|
||
};
|
||
|
||
// 上传
|
||
uploadData(tag, CONFIG.type, data, metadata);
|
||
}
|
||
|
||
/**
|
||
* 复制配置到剪贴板
|
||
*/
|
||
function copyConfig() {
|
||
let tag = CONFIG.tag;
|
||
|
||
if (CONFIG.autoDetect || tag === 'auto') {
|
||
const detected = autoDetectTag();
|
||
tag = detected.tag;
|
||
}
|
||
|
||
const config = {
|
||
serverUrl: CONFIG.serverUrl,
|
||
password: CONFIG.password,
|
||
tag: tag,
|
||
type: CONFIG.type
|
||
};
|
||
|
||
const configText = JSON.stringify(config, null, 2);
|
||
GM_setClipboard(configText);
|
||
|
||
console.log('📋 配置已复制到剪贴板:', config);
|
||
GM_notification({
|
||
title: '配置已复制',
|
||
text: `Tag: ${tag}`,
|
||
timeout: 2000
|
||
});
|
||
}
|
||
|
||
// ==================== UI 界面 ====================
|
||
|
||
/**
|
||
* 创建浮动按钮
|
||
*/
|
||
function createFloatingButton() {
|
||
const button = document.createElement('div');
|
||
button.id = 'data-sync-button';
|
||
button.innerHTML = '🔄';
|
||
button.style.cssText = `
|
||
position: fixed;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
width: 50px;
|
||
height: 50px;
|
||
background: #4CAF50;
|
||
color: white;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 24px;
|
||
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
||
z-index: 10000;
|
||
transition: all 0.3s;
|
||
`;
|
||
|
||
button.addEventListener('mouseenter', () => {
|
||
button.style.transform = 'scale(1.1)';
|
||
});
|
||
|
||
button.addEventListener('mouseleave', () => {
|
||
button.style.transform = 'scale(1)';
|
||
});
|
||
|
||
button.addEventListener('click', () => {
|
||
showSyncPanel();
|
||
});
|
||
|
||
document.body.appendChild(button);
|
||
}
|
||
|
||
/**
|
||
* 显示同步面板
|
||
*/
|
||
function showSyncPanel() {
|
||
const detected = autoDetectTag();
|
||
|
||
const panel = document.createElement('div');
|
||
panel.id = 'data-sync-panel';
|
||
panel.innerHTML = `
|
||
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); max-width: 450px;">
|
||
<h3 style="margin: 0 0 15px 0; color: #333;">数据同步控制面板</h3>
|
||
|
||
<div style="margin-bottom: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
|
||
<div style="margin-bottom: 8px;">
|
||
<strong>项目:</strong> ${detected.project}
|
||
</div>
|
||
<div style="margin-bottom: 8px;">
|
||
<strong>账号:</strong> ${detected.account}
|
||
</div>
|
||
<div style="margin-bottom: 8px;">
|
||
<strong>Tag:</strong> ${detected.tag}
|
||
</div>
|
||
<div>
|
||
<strong>类型:</strong> ${CONFIG.type}
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
|
||
<button id="sync-now-btn" style="flex: 1; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||
🔄 立即同步
|
||
</button>
|
||
<button id="copy-config-btn" style="flex: 1; padding: 10px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||
📋 复制配置
|
||
</button>
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 10px;">
|
||
<button id="settings-btn" style="flex: 1; padding: 10px; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||
⚙️ 配置设置
|
||
</button>
|
||
<button id="close-panel-btn" style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||
关闭
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
panel.style.cssText = `
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 10001;
|
||
`;
|
||
|
||
// 添加背景遮罩
|
||
const overlay = document.createElement('div');
|
||
overlay.id = 'data-sync-overlay';
|
||
overlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 10000;
|
||
`;
|
||
|
||
document.body.appendChild(overlay);
|
||
document.body.appendChild(panel);
|
||
|
||
// 绑定事件
|
||
document.getElementById('sync-now-btn').addEventListener('click', () => {
|
||
syncData();
|
||
closePanel();
|
||
});
|
||
|
||
document.getElementById('copy-config-btn').addEventListener('click', () => {
|
||
copyConfig();
|
||
});
|
||
|
||
document.getElementById('settings-btn').addEventListener('click', () => {
|
||
closePanel();
|
||
showSettingsPanel();
|
||
});
|
||
|
||
document.getElementById('close-panel-btn').addEventListener('click', closePanel);
|
||
overlay.addEventListener('click', closePanel);
|
||
}
|
||
|
||
/**
|
||
* 显示设置面板
|
||
*/
|
||
function showSettingsPanel() {
|
||
const panel = document.createElement('div');
|
||
panel.id = 'data-sync-settings-panel';
|
||
panel.innerHTML = `
|
||
<div style="background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); max-width: 500px; max-height: 80vh; overflow-y: auto;">
|
||
<h3 style="margin: 0 0 15px 0; color: #333;">配置设置</h3>
|
||
|
||
<div style="margin-bottom: 15px;">
|
||
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">服务器地址</label>
|
||
<input type="text" id="setting-serverUrl" value="${CONFIG.serverUrl}"
|
||
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
|
||
</div>
|
||
|
||
<div style="margin-bottom: 15px;">
|
||
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">密码</label>
|
||
<input type="password" id="setting-password" value="${CONFIG.password}"
|
||
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
|
||
</div>
|
||
|
||
<div style="margin-bottom: 15px;">
|
||
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">Tag(留空自动检测)</label>
|
||
<input type="text" id="setting-tag" value="${CONFIG.tag === 'auto' ? '' : CONFIG.tag}" placeholder="auto"
|
||
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
|
||
<small style="color: #888;">格式:项目名:账号,如 tingwu:user@email.com</small>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 15px;">
|
||
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">数据类型</label>
|
||
<select id="setting-type" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
|
||
<option value="cookie" ${CONFIG.type === 'cookie' ? 'selected' : ''}>Cookie</option>
|
||
<option value="token" ${CONFIG.type === 'token' ? 'selected' : ''}>Token</option>
|
||
<option value="credential" ${CONFIG.type === 'credential' ? 'selected' : ''}>Credential</option>
|
||
<option value="session" ${CONFIG.type === 'session' ? 'selected' : ''}>Session</option>
|
||
<option value="custom" ${CONFIG.type === 'custom' ? 'selected' : ''}>Custom</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 15px;">
|
||
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">过期时间(秒)</label>
|
||
<input type="number" id="setting-expiresIn" value="${CONFIG.expiresIn}"
|
||
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
|
||
<small style="color: #888;">默认 604800(7天)</small>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 15px;">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="checkbox" id="setting-autoDetect" ${CONFIG.autoDetect ? 'checked' : ''}
|
||
style="margin-right: 8px;">
|
||
<span style="font-weight: bold; color: #555;">自动检测项目和账号</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 15px;">
|
||
<label style="display: flex; align-items: center; cursor: pointer;">
|
||
<input type="checkbox" id="setting-autoSync" ${CONFIG.autoSync ? 'checked' : ''}
|
||
style="margin-right: 8px;">
|
||
<span style="font-weight: bold; color: #555;">页面加载后自动同步</span>
|
||
</label>
|
||
</div>
|
||
|
||
<div style="margin-bottom: 20px;">
|
||
<label style="display: block; margin-bottom: 5px; font-weight: bold; color: #555;">定时同步间隔(秒,0=禁用)</label>
|
||
<input type="number" id="setting-syncInterval" value="${CONFIG.syncInterval}"
|
||
style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">
|
||
</div>
|
||
|
||
<div style="display: flex; gap: 10px;">
|
||
<button id="save-settings-btn" style="flex: 1; padding: 10px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||
✅ 保存配置
|
||
</button>
|
||
<button id="reset-settings-btn" style="flex: 1; padding: 10px; background: #FF9800; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||
🔄 恢复默认
|
||
</button>
|
||
<button id="close-settings-btn" style="padding: 10px 20px; background: #f44336; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
|
||
关闭
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
panel.style.cssText = `
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 10001;
|
||
`;
|
||
|
||
// 添加背景遮罩
|
||
const overlay = document.createElement('div');
|
||
overlay.id = 'data-sync-overlay';
|
||
overlay.style.cssText = `
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 10000;
|
||
`;
|
||
|
||
document.body.appendChild(overlay);
|
||
document.body.appendChild(panel);
|
||
|
||
// 绑定事件
|
||
document.getElementById('save-settings-btn').addEventListener('click', () => {
|
||
const newConfig = {
|
||
serverUrl: document.getElementById('setting-serverUrl').value.trim(),
|
||
password: document.getElementById('setting-password').value,
|
||
tag: document.getElementById('setting-tag').value.trim() || 'auto',
|
||
type: document.getElementById('setting-type').value,
|
||
expiresIn: parseInt(document.getElementById('setting-expiresIn').value) || 604800,
|
||
autoDetect: document.getElementById('setting-autoDetect').checked,
|
||
autoSync: document.getElementById('setting-autoSync').checked,
|
||
syncInterval: parseInt(document.getElementById('setting-syncInterval').value) || 0,
|
||
};
|
||
|
||
CONFIG = newConfig;
|
||
saveConfig(newConfig);
|
||
|
||
GM_notification({
|
||
title: '配置已保存',
|
||
text: '设置将在下次页面加载时生效',
|
||
timeout: 3000
|
||
});
|
||
|
||
closePanel();
|
||
});
|
||
|
||
document.getElementById('reset-settings-btn').addEventListener('click', () => {
|
||
if (confirm('确定要恢复默认配置吗?')) {
|
||
CONFIG = {...DEFAULT_CONFIG};
|
||
saveConfig(CONFIG);
|
||
GM_notification({
|
||
title: '配置已重置',
|
||
text: '已恢复为默认配置',
|
||
timeout: 3000
|
||
});
|
||
closePanel();
|
||
}
|
||
});
|
||
|
||
document.getElementById('close-settings-btn').addEventListener('click', closePanel);
|
||
overlay.addEventListener('click', closePanel);
|
||
}
|
||
|
||
/**
|
||
* 关闭面板
|
||
*/
|
||
function closePanel() {
|
||
const panel = document.getElementById('data-sync-panel');
|
||
const overlay = document.getElementById('data-sync-overlay');
|
||
if (panel) panel.remove();
|
||
if (overlay) overlay.remove();
|
||
}
|
||
|
||
// ==================== 初始化 ====================
|
||
|
||
function init() {
|
||
console.log('🚀 通用数据同步脚本已加载');
|
||
|
||
// 创建浮动按钮
|
||
createFloatingButton();
|
||
|
||
// 自动同步
|
||
if (CONFIG.autoSync) {
|
||
setTimeout(() => {
|
||
console.log('⏰ 执行自动同步...');
|
||
syncData();
|
||
}, 3000);
|
||
}
|
||
|
||
// 定时同步
|
||
if (CONFIG.syncInterval > 0) {
|
||
setInterval(() => {
|
||
console.log('⏰ 执行定时同步...');
|
||
syncData();
|
||
}, CONFIG.syncInterval * 1000);
|
||
}
|
||
|
||
// 快捷键 Ctrl+Shift+S
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'S') {
|
||
e.preventDefault();
|
||
syncData();
|
||
}
|
||
});
|
||
|
||
// 快捷键 Ctrl+Shift+C
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
|
||
e.preventDefault();
|
||
copyConfig();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 等待 DOM 加载完成
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', init);
|
||
} else {
|
||
init();
|
||
}
|
||
|
||
})();
|