// ==UserScript==
// @name 阿里云百炼模型到期时间提取器
// @name:en Bailian Model Expiry Extractor
// @name:zh 阿里云百炼模型到期时间提取器
// @namespace https://greasyforks.org/zh-CN/scripts/543956-%E9%98%BF%E9%87%8C%E4%BA%91%E7%99%BE%E7%82%BC%E6%A8%A1%E5%9E%8B%E5%88%B0%E6%9C%9F%E6%97%B6%E9%97%B4%E6%8F%90%E5%8F%96%E5%99%A8
// @version 1.5.3
// @author will
// @description 精准提取模型名称、Code、免费额度(支持百分比/无额度)、倒计时、到期时间,一键复制 Code。
// @description:en Accurately extract model name, code, quota (%, 0, or N/M), countdown, expiry, and copy code.
// @license MIT
// @homepage https://github.com/jwq2011/TamperMonkey-Scripts
// @supportURL https://github.com/jwq2011/TamperMonkey-Scripts/issues
// @match https://bailian.console.aliyun.com/console*
// @grant GM_setClipboard
// @run-at document-start
// @compatible tampermonkey
// @compatible violentmonkey
// ==/UserScript==
(function () {
'use strict';
const DEBUG = false;
const LOG_PREFIX = '[Bailian Expiry+]';
function log(...args) {
if (DEBUG) console.log(LOG_PREFIX, ...args);
}
let extractedData = [];
// 用户可自定义显示哪些列(默认只显示原始5个)
const userSettings = {
showModelType: false, // 模型类型
showContextLength: false, // 上下文长度
showPrice: false, // 价格
showProtocol: false, // 模型协议
showLimit: false, // 限流
showDescription: false, // 描述
showVendor: false, // 供应商(子页面无)
showUpdateTime: false, // 更新时间(子页面无)
};
(function loadUserSettings() {
try {
const saved = localStorage.getItem('bailian_user_settings');
if (saved) {
Object.assign(userSettings, JSON.parse(saved));
}
} catch (e) {
console.error('[Bailian Settings] 加载用户设置失败:', e);
}
})();
// 等待页面完全加载
function waitForPageReady() {
return new Promise((resolve) => {
// 如果页面已经加载完成,直接返回
if (document.readyState === 'complete') {
resolve();
return;
}
// 等待页面加载完成
const checkInterval = setInterval(() => {
if (document.readyState === 'complete') {
clearInterval(checkInterval);
resolve();
}
}, 50); // 更快的检查频率
// 超时处理
setTimeout(() => {
clearInterval(checkInterval);
resolve();
}, 2000);
});
}
// 等待表格出现 - 优化版本
function waitForTable(maxWaitTime = 3000) {
return new Promise((resolve) => {
const startTime = Date.now();
function check() {
const table = document.querySelector('.efm_ant-table');
if (table) {
log('✅ 表格已加载');
resolve({ success: true, table });
return;
}
if (Date.now() - startTime > maxWaitTime) {
log('⚠️ 等待表格超时');
resolve({ success: false, table: null });
return;
}
// 更小的检查间隔
setTimeout(check, 50);
}
check();
});
}
// 等待特定元素出现 - 优化版本
function waitForElement(selector, maxWaitTime = 2000) {
return new Promise((resolve) => {
const startTime = Date.now();
function check() {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
if (Date.now() - startTime > maxWaitTime) {
resolve(null);
return;
}
setTimeout(check, 50);
}
check();
});
}
// 等待并检测列表视图按钮
async function waitForListViewButton() {
// 等待一段时间让页面完全渲染
await new Promise(resolve => setTimeout(resolve, 1000));
// 查找所有可能的列表视图按钮
const listViewIcons = document.querySelectorAll('.bl-icon-list-line');
log(`找到 ${listViewIcons.length} 个列表视图图标`);
for (let i = 0; i < listViewIcons.length; i++) {
const icon = listViewIcons[i];
const button = icon.closest('button');
// 检查按钮是否可见且可点击
if (button && button.offsetWidth > 0 && button.offsetHeight > 0) {
log(`按钮 ${i+1} 可见,正在检查是否已经是列表视图...`);
// 检查是否有active类
if (button.classList.contains('active__VRFfX')) {
log(`✅ 按钮 ${i+1} 已经是激活状态`);
return { success: true, button: null, alreadyActive: true }; // 已经是列表视图
} else {
log(`✅ 找到可点击的列表视图按钮 ${i+1}`);
return { success: true, button, alreadyActive: false };
}
} else {
log(`按钮 ${i+1} 不可见或无效`);
}
}
log('⚠️ 未找到可点击的列表视图按钮');
return { success: false, button: null, alreadyActive: false };
}
function createFloatingButton() {
const btnId = 'bailian-extractor-btn';
if (document.getElementById(btnId)) return;
const button = document.createElement('button');
button.id = btnId;
Object.assign(button.style, {
position: 'fixed', top: '80px', right: '20px', zIndex: '2147483647',
backgroundColor: '#ff6a00', color: 'white', border: 'none',
padding: '12px 16px', borderRadius: '8px', cursor: 'pointer',
fontSize: '14px', fontWeight: 'bold', boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
opacity: 0.95, fontFamily: 'Arial, sans-serif'
});
button.textContent = '📊 提取模型信息';
// 自动切换到列表视图(精准判断)- 优化版本
async function switchToListView() {
log('🔍 正在尝试切换到列表视图...');
// 快速检查当前视图状态
const currentViewIcon = document.querySelector('.bl-icon-list-line.active__VRFfX');
if (currentViewIcon) {
log('✅ 当前已是列表视图');
return false;
}
// 使用更高效的等待方式
await new Promise(resolve => setTimeout(resolve, 100));
// 查找所有列表视图图标
const listViewIcons = document.querySelectorAll('.bl-icon-list-line');
log(`找到 ${listViewIcons.length} 个列表视图图标`);
if (listViewIcons.length === 0) {
log('⚠️ 未找到列表视图图标');
return false;
}
// 遍历所有图标,找到可见的并尝试点击
for (let i = 0; i < listViewIcons.length; i++) {
const icon = listViewIcons[i];
// 更快的可见性检测
const rect = icon.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
log(`找到可见的列表视图图标 ${i+1}`);
// 检查是否已经是激活状态
if (!icon.classList.contains('active__VRFfX')) {
try {
log('正在点击列表视图图标...');
icon.click();
log('✅ 已点击切换到列表视图');
// 极短等待时间
await new Promise(resolve => setTimeout(resolve, 150));
return true;
} catch (error) {
log(`点击图标 ${i+1} 失败:`, error);
}
} else {
log(`图标 ${i+1} 已经激活`);
return false;
}
} else {
log(`图标 ${i+1} 不可见`);
}
}
log('⚠️ 未找到可点击的列表视图图标');
return false;
}
// 修改按钮点击事件处理函数
button.addEventListener('click', async () => {
button.disabled = true;
button.textContent = '🔍 提取中...';
try {
// 等待页面加载 - 更快的等待
await waitForPageReady();
// 等待表格出现 - 更快的超时
const tableResult = await waitForTable(2000);
// 自动切换视图
let needWait = false;
if (await switchToListView()) {
needWait = true;
}
// 自动展开折叠区域
if (await autoExpandFoldedRows()) {
needWait = true;
}
// 等待 DOM 更新
if (needWait) {
await new Promise(resolve => setTimeout(resolve, 150)); // 极短等待
}
const data = extractAllModels();
if (data.length === 0) {
alert('❌ 未找到任何模型信息,请确认已打开【模型广场】页面并完全加载。');
} else {
extractedData = data;
showResultsModal();
}
} catch (error) {
console.error('执行过程中发生错误:', error);
alert('❌ 执行过程中发生错误,请刷新页面后重试。');
} finally {
button.disabled = false;
button.textContent = '📊 提取模型信息';
}
});
document.body.appendChild(button);
createSettingsPanel(); // 添加设置按钮
log('✅ 按钮已创建');
}
// 自动展开折叠区域 - 优化版本
async function autoExpandFoldedRows() {
log('🔍 正在尝试展开折叠区域...');
// 极短等待时间
await new Promise(resolve => setTimeout(resolve, 100));
let clicked = false;
let expandedCount = 0;
// 方法1: 查找所有展开/收起按钮
const expandButtons = [...document.querySelectorAll('button.efm_ant-table-row-expand-icon')];
log(`找到 ${expandButtons.length} 个展开/收起按钮`);
for (const btn of expandButtons) {
// 更快的可见性检测
const rect = btn.getBoundingClientRect();
if (rect.width > 0 && rect.height > 0) {
// 检查是否为折叠状态
const isCollapsed = btn.classList.contains('efm_ant-table-row-expand-icon-collapsed');
const isExpanded = btn.classList.contains('efm_ant-table-row-expand-icon-expanded');
if (isCollapsed) {
try {
btn.click();
log('✅ 点击展开按钮');
expandedCount++;
clicked = true;
// 极短等待
await new Promise(resolve => setTimeout(resolve, 50));
} catch (error) {
log('点击展开按钮失败:', error);
}
} else if (isExpanded) {
log('✅ 按钮已是展开状态');
}
}
}
if (expandedCount > 0) {
log(`✅ 成功展开 ${expandedCount} 个折叠项`);
} else {
log('⚠️ 未找到可展开的折叠项');
}
return clicked;
}
// 提取模型信息
function extractAllModels() {
log('🔍 开始提取模型数据...');
// 等待表格出现
const maxWaitTime = 5000;
const startTime = Date.now();
while (Date.now() - startTime < maxWaitTime) {
const table = document.querySelector('.efm_ant-table');
if (table) {
log('✅ 表格已加载');
break;
}
// 短暂等待
const dummy = new Promise(resolve => setTimeout(resolve, 100));
dummy.then(() => {});
}
// 查找行元素
const rowSelectors = [
'tr[data-row-key]',
'.ant-table-row',
'tr[role="row"]',
'.efm_ant-table-row'
];
let rows = [];
for (const sel of rowSelectors) {
rows = [...document.querySelectorAll(sel)];
if (rows.length > 0) {
log(`✅ 找到 ${rows.length} 行数据`);
break;
}
}
if (rows.length === 0) {
log('❌ 未找到任何行');
return [];
}
const results = [];
// 判断是否是子页面(详情页)
const isSubPage = /\/model-market\/detail\//.test(location.hash);
for (const row of rows) {
// --- 模型名称 ---
let name = '未知模型';
if (isSubPage) {
const nameEl = row.querySelector('.name__QVnRn') || row.querySelector('td:first-child');
name = (nameEl?.textContent || '未知模型').trim();
} else {
const nameContainer = row.querySelector('.model-name__xEkXf');
const nameEl = nameContainer?.querySelector('span');
name = (nameEl?.textContent || '未知模型').trim();
}
// --- Code 提取 ---
let code = '';
if (isSubPage) {
const spans = row.querySelectorAll('span');
for (const span of spans) {
const text = span.textContent.trim();
if (/^qwen[-\w]*\d/.test(text)) {
code = text.toLowerCase();
break;
}
}
} else {
const codeCell = row.querySelector('td:nth-child(2)');
const codeText = codeCell?.textContent.trim().split(/\s+/)[0] || '';
if (/^qwen[-\w]*\d/.test(codeText)) {
code = codeText.toLowerCase();
}
}
code = code || '—';
// --- 免费额度 + 百分比 ---
let freeQuota = '—';
let quotaText = '0';
let percentText = '0%';
const quotaSpan = row.querySelector('.value__V7Z7e');
if (quotaSpan) {
const text = quotaSpan.textContent.trim();
const match = text.match(/(\d[\d,]*)\s*\/\s*(\d[\d,]+)/);
if (match) {
const used = parseInt(match[1].replace(/,/g, ''));
const total = parseInt(match[2].replace(/,/g, ''));
quotaText = `${used.toLocaleString()}/${total.toLocaleString()}`;
}
}
const percentSpan = row.querySelector('.efm_ant-progress-text');
if (percentSpan) {
const pct = percentSpan.textContent.trim();
if (/^\d+(\.\d+)?%$/.test(pct)) {
percentText = pct;
}
}
if (quotaText !== '0') {
freeQuota = `${quotaText} · ${percentText}`;
} else if (/^\d+(\.\d+)?%$/.test(percentText)) {
freeQuota = percentText;
} else {
freeQuota = /无免费额度/.test(row.textContent) ? '0 · 0%' : '—';
}
// --- 到期时间 ---
const expiryMatch = row.textContent.match(/到期时间.?(\d{4}-\d{2}-\d{2})/);
if (!expiryMatch) continue;
const expiry = expiryMatch[1];
const expiryDate = new Date(expiry);
const today = new Date().setHours(0, 0, 0, 0);
const daysLeft = Math.ceil((expiryDate - today) / 86400000);
if (daysLeft < 0) continue;
// --- 可选字段提取 ---
const modelType = userSettings.showModelType ? (row.querySelector('td:nth-child(3)')?.textContent || '—') : undefined;
const contextLength = userSettings.showContextLength ? (row.querySelector('td:nth-child(4)')?.textContent || '—') : undefined;
const price = userSettings.showPrice ? (row.querySelector('td:nth-child(5)')?.textContent || '—') : undefined;
const protocol = userSettings.showProtocol ? (row.querySelector('td:nth-child(6)')?.textContent || '—') : undefined;
const limit = userSettings.showLimit ? (row.querySelector('td:nth-child(9)')?.textContent || '—') : undefined;
const description = userSettings.showDescription ? (row.querySelector('td:nth-child(10)')?.textContent || '—') : undefined;
const vendor = (userSettings.showVendor && !isSubPage) ? (row.querySelector('td:nth-child(11)')?.textContent || '—') : undefined;
const updateTime = (userSettings.showUpdateTime && !isSubPage) ? (row.querySelector('td:nth-child(12)')?.textContent || '—') : undefined;
results.push({
name, code, freeQuota, daysLeft, expiry,
...(userSettings.showModelType && { modelType }),
...(userSettings.showContextLength && { contextLength }),
...(userSettings.showPrice && { price }),
...(userSettings.showProtocol && { protocol }),
...(userSettings.showLimit && { limit }),
...(userSettings.showDescription && { description }),
...(userSettings.showVendor && { vendor }),
...(userSettings.showUpdateTime && { updateTime })
});
log('✅ 提取:', name, code, freeQuota, `剩余 ${daysLeft} 天`, expiry);
}
return results.sort((a, b) => a.daysLeft - b.daysLeft);
}
// 显示结果弹窗
function showResultsModal() {
const modalId = 'bailian-extractor-modal';
if (document.getElementById(modalId)) document.body.removeChild(document.getElementById(modalId));
const modal = document.createElement('div');
modal.id = modalId;
Object.assign(modal.style, {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
backgroundColor: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center',
justifyContent: 'center', zIndex: '2147483647', fontFamily: 'Arial, sans-serif'
});
const content = document.createElement('div');
Object.assign(content.style, {
backgroundColor: 'white', width: '95%', maxWidth: '1200px', maxHeight: '85vh',
overflow: 'auto', borderRadius: '10px', padding: '20px', position: 'relative'
});
const title = document.createElement('h3');
title.textContent = `✅ 提取结果(${extractedData.length} 个模型)`;
content.appendChild(title);
if (extractedData.length === 0) {
content.appendChild(document.createTextNode('未找到有效模型信息。'));
} else {
const table = document.createElement('table');
table.style.width = '100%';
table.style.borderCollapse = 'collapse';
table.innerHTML = `
<thead>
<tr style="background:#f5f5f5;">
<th style="text-align:left;padding:10px;border:1px solid #ddd;">模型名称</th>
<th style="text-align:left;padding:10px;border:1px solid #ddd;">Code(点击自动复制)</th>
<th style="text-align:left;padding:10px;border:1px solid #ddd;">免费额度</th>
<th style="text-align:left;padding:10px;border:1px solid #ddd;">倒计时显示</th>
<th style="text-align:left;padding:10px;border:1px solid #ddd;">到期时间</th>
${userSettings.showModelType ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">模型类型</th>' : ''}
${userSettings.showContextLength ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">上下文长度</th>' : ''}
${userSettings.showPrice ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">价格</th>' : ''}
${userSettings.showProtocol ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">模型协议</th>' : ''}
${userSettings.showLimit ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">限流</th>' : ''}
${userSettings.showDescription ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">描述</th>' : ''}
${userSettings.showVendor ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">供应商</th>' : ''}
${userSettings.showUpdateTime ? '<th style="text-align:left;padding:10px;border:1px solid #ddd;">更新时间</th>' : ''}
</tr>
</thead>
<tbody></tbody>
`;
const tbody = table.querySelector('tbody');
extractedData.forEach(item => {
const tr = document.createElement('tr');
appendCell(tr, item.name);
appendCodeCell(tr, item.code);
appendCell(tr, item.freeQuota);
appendCountdownCell(tr, item.daysLeft);
appendCell(tr, item.expiry, { color: '#d9534f', fontWeight: 'bold' });
if (userSettings.showModelType) appendCell(tr, item.modelType || '—');
if (userSettings.showContextLength) appendCell(tr, item.contextLength || '—');
if (userSettings.showPrice) appendCell(tr, item.price || '—');
if (userSettings.showProtocol) appendCell(tr, item.protocol || '—');
if (userSettings.showLimit) appendCell(tr, item.limit || '—');
if (userSettings.showDescription) appendCell(tr, item.description || '—');
if (userSettings.showVendor) appendCell(tr, item.vendor || '—');
if (userSettings.showUpdateTime) appendCell(tr, item.updateTime || '—');
tbody.appendChild(tr);
});
content.appendChild(table);
const csvBtn = document.createElement('button');
csvBtn.textContent = '📋 复制为 CSV';
csvBtn.style.marginTop = '15px';
csvBtn.style.padding = '10px';
csvBtn.style.backgroundColor = '#007cba';
csvBtn.style.color = 'white';
csvBtn.style.border = 'none';
csvBtn.style.borderRadius = '4px';
csvBtn.style.cursor = 'pointer';
csvBtn.onclick = () => {
const headers = [
'模型名称', 'Code', '免费额度', '倒计时显示', '到期时间',
...(userSettings.showModelType ? ['模型类型'] : []),
...(userSettings.showContextLength ? ['上下文长度'] : []),
...(userSettings.showPrice ? ['价格'] : []),
...(userSettings.showProtocol ? ['模型协议'] : []),
...(userSettings.showLimit ? ['限流'] : []),
...(userSettings.showDescription ? ['描述'] : []),
...(userSettings.showVendor ? ['供应商'] : []),
...(userSettings.showUpdateTime ? ['更新时间'] : [])
];
const rows = extractedData.map(d => [
d.name,
d.code,
d.freeQuota,
`剩余 ${d.daysLeft} 天`,
d.expiry,
...(userSettings.showModelType ? [d.modelType || '—'] : []),
...(userSettings.showContextLength ? [d.contextLength || '—'] : []),
...(userSettings.showPrice ? [d.price || '—'] : []),
...(userSettings.showProtocol ? [d.protocol || '—'] : []),
...(userSettings.showLimit ? [d.limit || '—'] : []),
...(userSettings.showDescription ? [d.description || '—'] : []),
...(userSettings.showVendor ? [d.vendor || '—'] : []),
...(userSettings.showUpdateTime ? [d.updateTime || '—'] : [])
].map(s => `"${String(s).replace(/"/g, '""')}"`).join(','));
const csv = [headers.join(','), ...rows].join('\n');
navigator.clipboard.writeText(csv).then(() => {
csvBtn.textContent = '✅ 已复制!';
setTimeout(() => csvBtn.textContent = '📋 复制为 CSV', 2000);
});
};
content.appendChild(csvBtn);
}
const close = document.createElement('span');
close.textContent = '×';
close.style.position = 'absolute'; close.style.top = '10px'; close.style.right = '16px';
close.style.fontSize = '24px'; close.style.cursor = 'pointer';
close.onclick = () => document.body.removeChild(modal);
content.appendChild(close);
modal.appendChild(content);
document.body.appendChild(modal);
}
// 工具函数:创建表格单元格
function appendCell(tr, text, style = {}) {
const td = document.createElement('td');
td.style.padding = '10px';
td.style.border = '1px solid #ddd';
Object.assign(td.style, style);
td.textContent = text;
tr.appendChild(td);
}
function appendCodeCell(tr, code) {
const td = document.createElement('td');
td.style.padding = '10px';
td.style.border = '1px solid #ddd';
td.style.cursor = 'pointer';
td.style.color = '#007cba';
td.style.fontWeight = 'bold';
td.title = '点击复制 Code';
td.textContent = code;
td.onclick = () => {
GM_setClipboard(code);
td.textContent = '✅ 已复制!';
setTimeout(() => td.textContent = code, 1500);
};
tr.appendChild(td);
}
function appendCountdownCell(tr, daysLeft) {
const td = document.createElement('td');
td.style.padding = '10px';
td.style.border = '1px solid #ddd';
td.style.fontWeight = 'bold';
td.style.color = daysLeft < 30 ? '#d9534f' :
daysLeft < 90 ? '#f0ad4e' : '#5cb85c';
td.textContent = `剩余 ${daysLeft} 天`;
tr.appendChild(td);
}
// 创建设置按钮和弹窗
function createSettingsPanel() {
const settingsBtnId = 'bailian-settings-btn';
if (document.getElementById(settingsBtnId)) return;
// 设置按钮
const settingsBtn = document.createElement('button');
settingsBtn.id = settingsBtnId;
Object.assign(settingsBtn.style, {
position: 'fixed', top: '140px', right: '20px', zIndex: '2147483646',
backgroundColor: '#4CAF50', color: 'white', border: 'none',
padding: '10px 14px', borderRadius: '8px', cursor: 'pointer',
fontSize: '16px', fontWeight: 'bold', boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
opacity: 0.95, fontFamily: 'Arial, sans-serif'
});
settingsBtn.textContent = '⚙️ 设置';
settingsBtn.title = '点击打开设置面板';
settingsBtn.addEventListener('click', () => {
showSettingsModal();
});
document.body.appendChild(settingsBtn);
log('✅ 设置按钮已创建');
}
// 显示设置弹窗
function showSettingsModal() {
const modalId = 'bailian-settings-modal';
if (document.getElementById(modalId)) document.body.removeChild(document.getElementById(modalId));
const modal = document.createElement('div');
modal.id = modalId;
Object.assign(modal.style, {
position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
backgroundColor: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center',
justifyContent: 'center', zIndex: '2147483647', fontFamily: 'Arial, sans-serif'
});
const content = document.createElement('div');
Object.assign(content.style, {
backgroundColor: 'white', width: '90%', maxWidth: '500px', padding: '20px',
borderRadius: '10px', position: 'relative'
});
const title = document.createElement('h3');
title.textContent = '🔧 设置面板';
content.appendChild(title);
const form = document.createElement('div');
form.style.marginTop = '15px';
const fields = [
{ key: 'showModelType', label: '显示模型类型' },
{ key: 'showContextLength', label: '显示上下文长度' },
{ key: 'showPrice', label: '显示价格' },
{ key: 'showProtocol', label: '显示模型协议' },
{ key: 'showLimit', label: '显示限流' },
{ key: 'showDescription', label: '显示描述' },
{ key: 'showVendor', label: '显示供应商(仅主页面)' },
{ key: 'showUpdateTime', label: '显示更新时间(仅主页面)' }
];
fields.forEach(field => {
const div = document.createElement('div');
div.style.marginBottom = '12px';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `setting-${field.key}`;
checkbox.checked = userSettings[field.key];
checkbox.onchange = () => {
userSettings[field.key] = checkbox.checked;
localStorage.setItem('bailian_user_settings', JSON.stringify(userSettings));
};
const label = document.createElement('label');
label.htmlFor = `setting-${field.key}`;
label.textContent = field.label;
label.style.marginLeft = '8px';
div.appendChild(checkbox);
div.appendChild(label);
form.appendChild(div);
});
content.appendChild(form);
const saveBtn = document.createElement('button');
saveBtn.textContent = '✅ 保存并关闭';
saveBtn.style.marginTop = '20px';
saveBtn.style.padding = '10px 16px';
saveBtn.style.backgroundColor = '#007cba';
saveBtn.style.color = 'white';
saveBtn.style.border = 'none';
saveBtn.style.borderRadius = '4px';
saveBtn.style.cursor = 'pointer';
saveBtn.onclick = () => {
document.body.removeChild(modal);
alert('✅ 设置已保存,下次提取时生效。');
};
content.appendChild(saveBtn);
const close = document.createElement('span');
close.textContent = '×';
close.style.position = 'absolute'; close.style.top = '10px'; close.style.right = '16px';
close.style.fontSize = '24px'; close.style.cursor = 'pointer';
close.onclick = () => document.body.removeChild(modal);
content.appendChild(close);
modal.appendChild(content);
document.body.appendChild(modal);
}
// 初始化函数优化
function init() {
console.log(LOG_PREFIX, '脚本已注入,版本:', GM_info.script.version);
// 等待DOM准备就绪后创建按钮
if (document.readyState === 'loading') {
// 使用更快速的DOM加载检测
const checkInterval = setInterval(() => {
if (document.readyState === 'interactive' || document.readyState === 'complete') {
clearInterval(checkInterval);
setTimeout(createFloatingButton, 50); // 极短等待
}
}, 30);
// 超时处理
setTimeout(() => {
clearInterval(checkInterval);
setTimeout(createFloatingButton, 50);
}, 1000);
} else {
setTimeout(createFloatingButton, 50);
}
}
init();
})();