// ==UserScript== // @name 青龙面板数据采集器 - Cookie/LocalStorage管理器 // @namespace https://www.52pojie.cn/home.php?mod=space&uid=1034393 // @version 3.5 // @description 支持最新版青龙面板Client ID/Secret认证(Header方式),修复所有语法错误,支持HTTPS页面请求HTTP // @author auralife // @match https://developer.aliyun.com/* // @icon https://ts1.tc.mm.bing.net/th/id/ODF.b3kRz8VbzcklQgIluTHuYA?w=32&h=32&qlt=90&pcl=fffffa&o=6&pid=1.2 // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect * // @license MIT // ==/UserScript== (function() { 'use strict'; // ========== 用户配置区 ========== const CONFIG = { // 青龙面板配置(新版支持多种认证方式) QINGLONG: { URL: 'http://127.0.0.1:5700', // 你的青龙面板地址 // 方式1:使用Client ID + Client Secret(新版推荐,通过Header传递) CLIENT_ID: '', // 你的Client ID CLIENT_SECRET: '', // 你的Client Secret // // 方式2:使用Token(旧版) // TOKEN: '', // 例如:'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // // 方式3:使用用户名密码(自动登录获取token) USERNAME: '', // 例如:'admin' PASSWORD: '' // 例如:'admin123' }, // 要保存的环境变量KEY ENV_KEY: 'COOKIE_ALIYUN', // 例如:'COOKIE_ALIYUN' // 插件显示位置 POSITION: 'top-right', // 认证方式优先级:client > token > password AUTH_PRIORITY: ['client', 'token', 'password'], // 调试模式:true会在控制台输出详细信息 DEBUG: false }; // =============================== // 状态管理 const state = { selectedCookies: new Set(), selectedLocal: new Set(), selectedSession: new Set(), editedValue: '', originalValue: '', currentData: { cookies: [], localStorage: [], sessionStorage: [] } }; // Token管理 const tokenManager = { token: null, expiry: null, // 调试日志 log: function(...args) { if (CONFIG.DEBUG) { console.log('[青龙编辑器]', ...args); } }, // 从Client ID/Secret获取token (正确的OpenAPI方式) // 从Client ID/Secret获取token (根据你的API文档修正) getTokenFromClient: async function() { if (!CONFIG.QINGLONG.CLIENT_ID || !CONFIG.QINGLONG.CLIENT_SECRET) { return null; } try { const baseUrl = CONFIG.QINGLONG.URL.replace(/\/$/, ''); const url = `${baseUrl}/open/auth/token?client_id=${encodeURIComponent(CONFIG.QINGLONG.CLIENT_ID)}&client_secret=${encodeURIComponent(CONFIG.QINGLONG.CLIENT_SECRET)}`; this.log('尝试Client认证 URL:', url); const response = await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('请求超时(10秒)')); }, 10000); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'Accept': 'application/json' }, onload: (res) => { clearTimeout(timeout); this.log('认证响应状态:', res.status); if (res.status >= 200 && res.status < 300) { try { const data = JSON.parse(res.responseText); this.log('解析后的数据:', data); let token = null; if (data.data && data.data.token) { token = data.data.token; } else if (data.token) { token = data.token; } if (token) { this.token = token; // 如果有过期时间字段,按实际情况处理 if (data.data && data.data.expiration) { this.expiry = data.data.expiration * 1000; } else { this.expiry = Date.now() + 7200 * 1000; // 默认2小时 } this.log('✅ Client认证成功,token:', token.substring(0, 20) + '...'); resolve(token); // ✅ 正确决议 } else { this.log('❌ 响应中没有找到token'); resolve(null); // ✅ 返回 null,让上层尝试其他方式 } } catch (e) { this.log('❌ 解析JSON失败:', e.message); resolve(null); // ✅ 解析失败也返回 null } } else { this.log('❌ HTTP错误:', res.status); resolve(null); // ✅ HTTP错误也返回 null } }, onerror: (err) => { clearTimeout(timeout); this.log('❌ 网络错误:', err); reject(new Error('网络请求失败')); // 网络错误可以 reject }, ontimeout: () => { clearTimeout(timeout); reject(new Error('请求超时')); // 超时 reject } }); }); return response; // 这里 response 就是 resolve 传出的 token 或 null } catch (e) { this.log('Client认证异常:', e.message); return null; // 发生异常(如超时、网络错误)时返回 null } }, // 从用户名密码登录获取token getTokenFromPassword: async function() { if (!CONFIG.QINGLONG.USERNAME || !CONFIG.QINGLONG.PASSWORD) { this.log('未配置用户名密码'); return null; } try { const baseUrl = CONFIG.QINGLONG.URL.replace(/\/$/, ''); const url = `${baseUrl}/api/login`; this.log('尝试密码登录:', url); const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ username: CONFIG.QINGLONG.USERNAME, password: CONFIG.QINGLONG.PASSWORD }), onload: function(res) { resolve({ ok: res.status >= 200 && res.status < 300, status: res.status, responseText: res.responseText, json: function() { try { return Promise.resolve(JSON.parse(res.responseText)); } catch (e) { return Promise.reject(e); } } }); }, onerror: function(err) { reject(err); } }); }); if (!response.ok) { this.log('密码登录HTTP错误:', response.status); return null; } const data = await response.json(); this.log('密码登录响应:', data); let token = null; if (data.data && data.data.token) { token = data.data.token; } else if (data.token) { token = data.token; } if (token) { this.token = token; this.log('✅ 密码登录成功'); return token; } return null; } catch (e) { this.log('密码登录异常:', e); return null; } }, // 从页面获取已有token getTokenFromPage: function() { // 从localStorage获取 let token = localStorage.getItem('token'); if (token) { if (token.startsWith('"') && token.endsWith('"')) { try { token = JSON.parse(token); } catch (e) { // 忽略解析错误 } } this.log('从localStorage获取token'); return token; } // 从cookie获取 const cookies = document.cookie.split(';'); for (let cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'token') { this.log('从cookie获取token'); return decodeURIComponent(value); } } // 从配置中获取 if (CONFIG.QINGLONG.TOKEN) { this.log('从配置获取token'); return CONFIG.QINGLONG.TOKEN; } this.log('未找到token'); return null; }, // 获取有效token getValidToken: async function() { // 如果已有token且未过期,直接返回 if (this.token && this.expiry && this.expiry > Date.now()) { this.log('使用缓存的token,剩余时间:', Math.round((this.expiry - Date.now())/1000) + '秒'); return this.token; } this.log('获取新token,优先级:', CONFIG.AUTH_PRIORITY); // 按优先级尝试不同认证方式 for (const method of CONFIG.AUTH_PRIORITY) { this.log(`尝试认证方式: ${method}`); let token = null; try { switch (method) { case 'client': token = await this.getTokenFromClient(); break; case 'token': token = this.getTokenFromPage(); break; case 'password': token = await this.getTokenFromPassword(); break; } } catch (e) { this.log(`认证方式 ${method} 失败:`, e.message); continue; } if (token) { this.token = token; this.log(`✅ 使用 ${method} 方式认证成功`); return token; } } throw new Error('无法获取有效token,请检查认证配置'); }, // 清理token clearToken: function() { this.token = null; this.expiry = null; this.log('token已清理'); } }; // 带认证的请求函数 (使用GM_xmlhttpRequest) async function qinglongRequest(endpoint, options = {}) { options.retryCount = options.retryCount || 0; try { const token = await tokenManager.getValidToken(); if (!token) throw new Error('无法获取有效token'); tokenManager.log('使用token:', token.substring(0, 20) + '...'); const baseUrl = CONFIG.QINGLONG.URL.replace(/\/$/, ''); const url = endpoint.startsWith('http') ? endpoint : `${baseUrl}/open${endpoint}`; tokenManager.log('请求URL:', url); tokenManager.log('请求方法:', options.method || 'GET'); return await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method || 'GET', url: url, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'Accept': 'application/json', ...options.headers }, data: options.body, onload: function(res) { tokenManager.log('响应状态码:', res.status); tokenManager.log('响应体预览:', res.responseText.substring(0, 200)); if (res.status === 401) { tokenManager.log('token已过期,尝试刷新'); tokenManager.clearToken(); if (options.retryCount < 1) { // 只重试一次 options.retryCount++; qinglongRequest(endpoint, options) .then(resolve) .catch(reject); } else { reject(new Error('多次401,认证失败,请检查token权限')); } return; } resolve({ ok: res.status >= 200 && res.status < 300, status: res.status, statusText: res.statusText, responseText: res.responseText, json: function() { try { return Promise.resolve(JSON.parse(res.responseText)); } catch (e) { return Promise.reject(e); } } }); }, onerror: function(err) { tokenManager.log('请求错误:', err); reject(new Error('网络请求失败')); } }); }); } catch (error) { tokenManager.log('请求失败:', error); throw error; } } // 添加样式 GM_addStyle(` .data-editor-panel { position: fixed; ${CONFIG.POSITION.includes('top') ? 'top: 20px;' : 'bottom: 20px;'} ${CONFIG.POSITION.includes('right') ? 'right: 20px;' : 'left: 20px;'} width: 700px; max-width: 90vw; background: white; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.2); z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; } .data-editor-header { padding: 12px 16px; background: #f8f9fa; border-bottom: 1px solid #eee; border-radius: 8px 8px 0 0; font-weight: 600; display: flex; justify-content: space-between; align-items: center; cursor: move; user-select: none; } .data-editor-tabs { display: flex; border-bottom: 1px solid #dee2e6; background: #f8f9fa; padding: 0 16px; } .data-editor-tab { padding: 10px 16px; cursor: pointer; border: 1px solid transparent; border-bottom: none; margin-bottom: -1px; color: #495057; } .data-editor-tab.active { color: #007bff; background: white; border-color: #dee2e6 #dee2e6 white; border-radius: 4px 4px 0 0; font-weight: 500; } .data-editor-content { padding: 16px; max-height: 500px; overflow-y: auto; } .data-item { margin-bottom: 8px; padding: 8px; background: #f8f9fa; border-radius: 4px; border: 1px solid #e9ecef; transition: all 0.2s; } .data-item:hover { background: #e9ecef; } .data-item.selected { background: #e3f2fd; border-color: #2196f3; } .data-item-header { display: flex; align-items: center; gap: 8px; cursor: pointer; } .data-item-checkbox { margin: 0; width: 16px; height: 16px; cursor: pointer; } .data-item-key { font-weight: 600; color: #2196f3; flex: 1; } .data-item-value { color: #28a745; font-family: monospace; font-size: 12px; max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .search-box { margin-bottom: 12px; padding: 8px; border: 1px solid #ced4da; border-radius: 4px; width: 100%; box-sizing: border-box; } .select-all-bar { padding: 8px 12px; background: #e9ecef; border-radius: 4px; margin-bottom: 12px; display: flex; align-items: center; gap: 12px; } .editor-section { margin-top: 16px; border: 1px solid #dee2e6; border-radius: 4px; } .editor-section-title { padding: 8px 12px; background: #f8f9fa; border-bottom: 1px solid #dee2e6; font-weight: 500; display: flex; justify-content: space-between; align-items: center; } .editor-textarea { width: 100%; min-height: 200px; padding: 12px; border: none; border-bottom: 1px solid #dee2e6; font-family: monospace; font-size: 13px; resize: vertical; box-sizing: border-box; } .editor-textarea:focus { outline: none; background: #f8f9fa; } .button-group { padding: 12px; display: flex; gap: 8px; flex-wrap: wrap; background: #f8f9fa; border-top: 1px solid #dee2e6; } .btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: all 0.2s; white-space: nowrap; } .btn-primary { background: #007bff; color: white; } .btn-primary:hover { background: #0056b3; } .btn-success { background: #28a745; color: white; } .btn-success:hover { background: #218838; } .btn-info { background: #17a2b8; color: white; } .btn-info:hover { background: #138496; } .btn-warning { background: #ffc107; color: #212529; } .btn-warning:hover { background: #e0a800; } .btn-danger { background: #dc3545; color: white; } .btn-danger:hover { background: #c82333; } .btn-secondary { background: #6c757d; color: white; } .btn-secondary:hover { background: #5a6268; } .btn:disabled { opacity: 0.5; cursor: not-allowed; } .status-message { margin: 8px 16px 16px; padding: 8px 12px; border-radius: 4px; font-size: 13px; } .status-success { background: #d4edda; border: 1px solid #c3e6cb; color: #155724; } .status-error { background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; } .status-info { background: #d1ecf1; border: 1px solid #bee5eb; color: #0c5460; } .badge { display: inline-block; padding: 3px 6px; border-radius: 3px; font-size: 11px; font-weight: 600; } .badge-info { background: #17a2b8; color: white; } .float-btn { position: fixed; ${CONFIG.POSITION.includes('top') ? 'top: 80px;' : 'bottom: 80px;'} ${CONFIG.POSITION.includes('right') ? 'right: 20px;' : 'left: 20px;'} width: 48px; height: 48px; background: #007bff; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 99998; font-size: 20px; transition: all 0.3s; } .float-btn:hover { transform: scale(1.1); background: #0056b3; } .auth-guide { padding: 16px; background: #e3f2fd; border-radius: 4px; margin-bottom: 16px; border-left: 4px solid #2196f3; } .auth-info { padding: 8px 12px; background: #f8f9fa; border-radius: 4px; margin: 8px 16px; font-size: 12px; border-left: 3px solid #28a745; } `); // 获取所有cookie function getAllCookies() { return document.cookie.split(';') .filter(c => c.trim()) .map(cookie => { const [key, ...valueParts] = cookie.trim().split('='); return { key: key.trim(), value: valueParts.join('='), type: 'cookie' }; }); } // 获取localStorage所有数据 function getAllLocalStorage() { const items = []; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); try { const value = localStorage.getItem(key); items.push({ key, value, type: 'local' }); } catch (e) { console.warn('读取localStorage失败', key, e); } } return items; } // 获取sessionStorage所有数据 function getAllSessionStorage() { const items = []; for (let i = 0; i < sessionStorage.length; i++) { const key = sessionStorage.key(i); try { const value = sessionStorage.getItem(key); items.push({ key, value, type: 'session' }); } catch (e) { console.warn('读取sessionStorage失败', key, e); } } return items; } // 刷新数据 function refreshData() { state.currentData = { cookies: getAllCookies(), localStorage: getAllLocalStorage(), sessionStorage: getAllSessionStorage() }; } // 根据勾选生成数据 function generateDataFromSelection() { const selectedData = { cookies: state.currentData.cookies .filter(item => state.selectedCookies.has(item.key)) .map(({ key, value }) => ({ key, value })), localStorage: state.currentData.localStorage .filter(item => state.selectedLocal.has(item.key)) .map(({ key, value }) => ({ key, value })), sessionStorage: state.currentData.sessionStorage .filter(item => state.selectedSession.has(item.key)) .map(({ key, value }) => ({ key, value })), url: window.location.href, title: document.title, timestamp: new Date().toISOString() }; return JSON.stringify(selectedData, null, 2); } // 解析青龙返回的数据到勾选状态 function parseDataToSelection(data) { try { const parsed = typeof data === 'string' ? JSON.parse(data) : data; // 清空现有勾选 state.selectedCookies.clear(); state.selectedLocal.clear(); state.selectedSession.clear(); // 勾选cookies if (parsed.cookies && Array.isArray(parsed.cookies)) { parsed.cookies.forEach(cookie => { if (cookie.key) state.selectedCookies.add(cookie.key); }); } // 勾选localStorage if (parsed.localStorage && Array.isArray(parsed.localStorage)) { parsed.localStorage.forEach(item => { if (item.key) state.selectedLocal.add(item.key); }); } // 勾选sessionStorage if (parsed.sessionStorage && Array.isArray(parsed.sessionStorage)) { parsed.sessionStorage.forEach(item => { if (item.key) state.selectedSession.add(item.key); }); } return true; } catch (e) { console.error('解析数据失败', e); return false; } } // 从青龙获取环境变量 async function fetchFromQinglong() { try { const res = await qinglongRequest('/envs'); if (!res.ok) throw new Error(`请求失败: ${res.status}`); const data = await res.json(); tokenManager.log('获取环境变量响应:', data); // OpenAPI可能返回的格式:{code:200, data: [...]} // 或者:{code:200, data: {data: [...]}} let envList = []; if (data.code === 200) { if (Array.isArray(data.data)) { envList = data.data; } else if (data.data && Array.isArray(data.data.data)) { envList = data.data.data; } } tokenManager.log('解析后的环境变量列表:', envList); const targetEnv = envList.find(env => env.name === CONFIG.ENV_KEY); if (!targetEnv) throw new Error(`未找到环境变量 ${CONFIG.ENV_KEY}`); state.originalValue = targetEnv.value; parseDataToSelection(state.originalValue); return state.originalValue; } catch (error) { tokenManager.log('获取环境变量失败:', error); throw error; } } // 保存到青龙 // 替换整个 saveToQinglong 函数 async function saveToQinglong(value) { try { // 先获取所有环境变量 const listRes = await qinglongRequest('/envs'); if (!listRes.ok) throw new Error(`获取环境变量失败: ${listRes.status}`); const listData = await listRes.json(); tokenManager.log('获取环境变量列表响应:', listData); // 解析环境变量列表 let envList = []; if (listData.code === 200) { if (Array.isArray(listData.data)) { envList = listData.data; } else if (listData.data && Array.isArray(listData.data.data)) { envList = listData.data.data; } } const targetEnv = envList.find(env => env.name === CONFIG.ENV_KEY); if (targetEnv) { // 更新环境变量 const updateRes = await qinglongRequest('/envs', { method: 'PUT', body: JSON.stringify({ id: targetEnv.id, name: targetEnv.name, value: value, remarks: targetEnv.remarks || `更新于 ${new Date().toLocaleString()}` }) }); if (!updateRes.ok) throw new Error(`更新失败: ${updateRes.status}`); const updateData = await updateRes.json(); if (updateData.code !== 200) throw new Error(updateData.message); return { action: 'update', value }; } else { // 创建环境变量 const createRes = await qinglongRequest('/envs', { method: 'POST', body: JSON.stringify({ name: CONFIG.ENV_KEY, value: value, remarks: `创建于 ${new Date().toLocaleString()}` }) }); if (!createRes.ok) throw new Error(`创建失败: ${createRes.status}`); const createData = await createRes.json(); if (createData.code !== 200) throw new Error(createData.message); return { action: 'create', value }; } } catch (error) { tokenManager.log('保存到青龙失败:', error); throw error; } } // 渲染数据项列表 function renderDataItems(container, items, type) { if (!container) return; const selectedSet = type === 'cookie' ? state.selectedCookies : type === 'local' ? state.selectedLocal : state.selectedSession; let html = `
已选: ${selectedSet.size}
`; container.innerHTML = html; // 搜索功能 const searchInput = document.getElementById(`search-${type}`); const itemsContainer = document.getElementById(`items-${type}`); function filterItems(searchText) { const filtered = items.filter(item => item.key.toLowerCase().includes(searchText.toLowerCase()) || String(item.value).toLowerCase().includes(searchText.toLowerCase()) ); itemsContainer.innerHTML = filtered.map(item => `
${escapeHtml(item.key)} ${escapeHtml(String(item.value).substring(0, 50))}${String(item.value).length > 50 ? '...' : ''}
`).join(''); // 绑定事件 itemsContainer.querySelectorAll('.data-item-header').forEach(header => { header.addEventListener('click', (e) => { if (e.target.type === 'checkbox') return; const checkbox = header.querySelector('.data-item-checkbox'); checkbox.checked = !checkbox.checked; checkbox.dispatchEvent(new Event('change', { bubbles: true })); }); }); itemsContainer.querySelectorAll('.data-item-checkbox').forEach(cb => { cb.addEventListener('change', (e) => { e.stopPropagation(); const key = e.target.dataset.key; const type = e.target.dataset.type; if (e.target.checked) { selectedSet.add(key); } else { selectedSet.delete(key); } // 更新样式 const item = e.target.closest('.data-item'); if (item) { item.classList.toggle('selected', e.target.checked); } // 更新全选状态和计数 updateSelectAllStatus(type); }); }); } searchInput.addEventListener('input', (e) => { filterItems(e.target.value); }); filterItems(''); // 全选功能 document.getElementById(`select-all-${type}`).addEventListener('change', (e) => { const checked = e.target.checked; const currentItems = items.filter(item => { const searchText = searchInput.value.toLowerCase(); return item.key.toLowerCase().includes(searchText) || String(item.value).toLowerCase().includes(searchText); }); currentItems.forEach(item => { if (checked) { selectedSet.add(item.key); } else { selectedSet.delete(item.key); } }); // 重新渲染当前视图 filterItems(searchInput.value); }); } function updateSelectAllStatus(type) { const selectAll = document.getElementById(`select-all-${type}`); if (!selectAll) return; const items = type === 'cookie' ? state.currentData.cookies : type === 'local' ? state.currentData.localStorage : state.currentData.sessionStorage; const selectedSet = type === 'cookie' ? state.selectedCookies : type === 'local' ? state.selectedLocal : state.selectedSession; selectAll.checked = items.length > 0 && items.every(item => selectedSet.has(item.key)); // 更新计数 const badge = selectAll.closest('.select-all-bar').querySelector('.badge'); if (badge) { badge.textContent = `已选: ${selectedSet.size}`; } } function renderTab(tabName) { const content = document.getElementById('panel-content'); if (!content) return; if (tabName === 'cookies') { content.innerHTML = '
'; renderDataItems(document.getElementById('cookies-container'), state.currentData.cookies, 'cookie'); } else if (tabName === 'local') { content.innerHTML = '
'; renderDataItems(document.getElementById('local-container'), state.currentData.localStorage, 'local'); } else if (tabName === 'session') { content.innerHTML = '
'; renderDataItems(document.getElementById('session-container'), state.currentData.sessionStorage, 'session'); } else if (tabName === 'editor') { content.innerHTML = `
📝 编辑数据 (可直接修改) 环境变量: ${CONFIG.ENV_KEY}
`; const textarea = document.getElementById('editor-textarea'); textarea.addEventListener('input', (e) => { state.editedValue = e.target.value; }); document.getElementById('format-json').addEventListener('click', () => { try { const parsed = JSON.parse(textarea.value); textarea.value = JSON.stringify(parsed, null, 2); state.editedValue = textarea.value; showStatus('JSON格式化成功', 'success'); } catch (e) { showStatus('不是有效的JSON', 'error'); } }); document.getElementById('validate-json').addEventListener('click', () => { try { JSON.parse(textarea.value); showStatus('JSON格式有效', 'success'); } catch (e) { showStatus('JSON格式无效: ' + e.message, 'error'); } }); document.getElementById('copy-json').addEventListener('click', () => { navigator.clipboard.writeText(textarea.value).then(() => { showStatus('已复制到剪贴板', 'success'); }).catch(() => { showStatus('复制失败', 'error'); }); }); } } function showStatus(message, type = 'info') { const statusDiv = document.getElementById('panel-status'); if (statusDiv) { statusDiv.className = `status-message status-${type}`; statusDiv.textContent = message; } } // 显示认证指引 function showAuthGuide() { return `

🔑 青龙面板认证配置指引

方式1:使用Client ID/Secret(推荐)

1. 登录青龙面板后台

2. 进入"系统设置" → "应用设置"

3. 点击"新建应用",填写名称

4. 获取 Client ID 和 Client Secret

5. 配置到脚本的 CONFIG.QINGLONG 中


方式2:使用Token

从浏览器 localStorage 中获取 token 直接配置


方式3:使用用户名密码

直接配置登录账号密码,脚本自动登录

`; } // 创建主面板 function createMainPanel() { if (document.getElementById('data-editor-panel')) return; refreshData(); const panel = document.createElement('div'); panel.id = 'data-editor-panel'; panel.className = 'data-editor-panel'; // 头部 const header = document.createElement('div'); header.className = 'data-editor-header'; header.innerHTML = ` 📝 数据采集编辑器 - ${CONFIG.ENV_KEY} × `; // 检查认证配置 const hasAuth = CONFIG.QINGLONG.CLIENT_ID || CONFIG.QINGLONG.TOKEN || CONFIG.QINGLONG.USERNAME; // 标签页 const tabs = document.createElement('div'); tabs.className = 'data-editor-tabs'; tabs.innerHTML = `
🍪 Cookies
💾 LocalStorage
⏳ SessionStorage
✏️ 编辑器
`; // 内容区域 const content = document.createElement('div'); content.className = 'data-editor-content'; content.id = 'panel-content'; if (!hasAuth) { content.innerHTML = showAuthGuide(); } // 按钮组 const buttonGroup = document.createElement('div'); buttonGroup.className = 'button-group'; buttonGroup.innerHTML = ` `; // 状态显示 const statusDiv = document.createElement('div'); statusDiv.id = 'panel-status'; statusDiv.className = 'status-message'; panel.appendChild(header); panel.appendChild(tabs); panel.appendChild(content); panel.appendChild(buttonGroup); panel.appendChild(statusDiv); document.body.appendChild(panel); // 如果有认证配置,显示认证方式 if (hasAuth) { const authInfo = document.createElement('div'); authInfo.className = 'auth-info'; let authMethod = ''; if (CONFIG.QINGLONG.CLIENT_ID) authMethod = 'Client模式'; else if (CONFIG.QINGLONG.TOKEN) authMethod = 'Token模式'; else if (CONFIG.QINGLONG.USERNAME) authMethod = '密码模式'; authInfo.innerHTML = ` 认证方式: ${authMethod} ${CONFIG.QINGLONG.CLIENT_ID ? `
Client ID: ${CONFIG.QINGLONG.CLIENT_ID.substring(0, 8)}...` : ''} `; panel.insertBefore(authInfo, statusDiv); } // 如果没有认证配置,隐藏标签页 if (!hasAuth) { tabs.style.display = 'none'; } else { renderTab('cookies'); } // 关闭按钮 document.getElementById('close-panel').addEventListener('click', () => panel.remove()); // 标签切换 document.querySelectorAll('.data-editor-tab').forEach(tab => { tab.addEventListener('click', () => { document.querySelectorAll('.data-editor-tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); renderTab(tab.dataset.tab); }); }); // 从青龙获取 document.getElementById('fetch-from-qinglong').addEventListener('click', async () => { showStatus('⏳ 从青龙获取中...', 'info'); try { const value = await fetchFromQinglong(); state.editedValue = value; // 切换到编辑器标签页 document.querySelectorAll('.data-editor-tab').forEach(t => t.classList.remove('active')); document.querySelector('[data-tab="editor"]').classList.add('active'); renderTab('editor'); showStatus('✅ 获取成功!已自动勾选对应的数据项', 'success'); } catch (error) { showStatus(`❌ 获取失败: ${error.message}`, 'error'); } }); // 生成 Cookie 字符串 document.getElementById('generate-cookie-string').addEventListener('click', () => { const str = generateCookieStringFromSelection(); if (str) { state.editedValue = str; // 切换到编辑器标签页 document.querySelectorAll('.data-editor-tab').forEach(t => t.classList.remove('active')); document.querySelector('[data-tab="editor"]').classList.add('active'); renderTab('editor'); showStatus('✅ 已生成Cookie字符串', 'success'); } else { showStatus('请至少勾选一个Cookie项', 'error'); } }); // 生成 LocalStorage 字符串 document.getElementById('generate-local-string').addEventListener('click', () => { const str = generateLocalStringFromSelection(); if (str) { state.editedValue = str; document.querySelectorAll('.data-editor-tab').forEach(t => t.classList.remove('active')); document.querySelector('[data-tab="editor"]').classList.add('active'); renderTab('editor'); showStatus('✅ 已生成LocalStorage字符串', 'success'); } else { showStatus('请至少勾选一个LocalStorage项', 'error'); } }); // 生成 SessionStorage 字符串 document.getElementById('generate-session-string').addEventListener('click', () => { const str = generateSessionStringFromSelection(); if (str) { state.editedValue = str; document.querySelectorAll('.data-editor-tab').forEach(t => t.classList.remove('active')); document.querySelector('[data-tab="editor"]').classList.add('active'); renderTab('editor'); showStatus('✅ 已生成SessionStorage字符串', 'success'); } else { showStatus('请至少勾选一个SessionStorage项', 'error'); } }); // 从勾选生成 document.getElementById('generate-from-selection').addEventListener('click', () => { const generated = generateDataFromSelection(); state.editedValue = generated; // 切换到编辑器标签页 document.querySelectorAll('.data-editor-tab').forEach(t => t.classList.remove('active')); document.querySelector('[data-tab="editor"]').classList.add('active'); renderTab('editor'); showStatus('✅ 已从勾选生成数据', 'success'); }); // 生成 Cookie 字符串 (key=value;key=value;) function generateCookieStringFromSelection() { const selectedItems = state.currentData.cookies.filter(item => state.selectedCookies.has(item.key)); if (selectedItems.length === 0) return ''; return selectedItems.map(item => `${item.key}=${item.value}`).join(';') + ';'; } // 生成 LocalStorage 字符串 function generateLocalStringFromSelection() { const selectedItems = state.currentData.localStorage.filter(item => state.selectedLocal.has(item.key)); if (selectedItems.length === 0) return ''; return selectedItems.map(item => `${item.key}=${item.value}`).join(';') + ';'; } // 生成 SessionStorage 字符串 function generateSessionStringFromSelection() { const selectedItems = state.currentData.sessionStorage.filter(item => state.selectedSession.has(item.key)); if (selectedItems.length === 0) return ''; return selectedItems.map(item => `${item.key}=${item.value}`).join(';') + ';'; } // 保存到青龙 document.getElementById('save-to-qinglong').addEventListener('click', async () => { if (!state.editedValue) { alert('请先生成或编辑要保存的数据'); return; } showStatus('⏳ 保存到青龙中...', 'info'); try { const result = await saveToQinglong(state.editedValue); showStatus(`✅ 保存成功!已${result.action === 'update' ? '更新' : '创建'}环境变量`, 'success'); } catch (error) { showStatus(`❌ 保存失败: ${error.message}`, 'error'); } }); // 刷新数据 document.getElementById('refresh-data').addEventListener('click', () => { refreshData(); const activeTab = document.querySelector('.data-editor-tab.active').dataset.tab; if (activeTab !== 'editor') { renderTab(activeTab); } showStatus('✅ 数据已刷新', 'success'); }); // 测试认证按钮 document.getElementById('test-auth').addEventListener('click', async () => { showStatus('⏳ 测试认证中...', 'info'); try { const token = await tokenManager.getValidToken(); if (token) { showStatus(`✅ 认证成功! token: ${token.substring(0, 20)}...`, 'success'); } else { showStatus('❌ 认证失败', 'error'); } } catch (error) { showStatus(`❌ 认证失败: ${error.message}`, 'error'); } }); // 拖拽功能 let isDragging = false; let currentX, currentY, initialX, initialY, xOffset = 0, yOffset = 0; header.addEventListener('mousedown', (e) => { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; isDragging = true; }); document.addEventListener('mousemove', (e) => { if (isDragging) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; panel.style.transform = `translate(${currentX}px, ${currentY}px)`; } }); document.addEventListener('mouseup', () => { isDragging = false; }); } function escapeHtml(unsafe) { if (!unsafe) return ''; return String(unsafe) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // 初始化浮动按钮 function init() { const floatBtn = document.createElement('div'); floatBtn.className = 'float-btn'; floatBtn.innerHTML = '📦'; floatBtn.title = '打开数据采集编辑器'; floatBtn.addEventListener('click', createMainPanel); document.body.appendChild(floatBtn); tokenManager.log('脚本初始化完成'); } // 启动脚本 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();