您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动化批量上架Steam激活码到SteamPY平台,支持精确元素定位和错误恢复
// ==UserScript== // @name SteamPY 批量上架激活码助手 (正式版) // @copyright 2025, Yuxiang ZHANG (https://github.com/jamespaulzhang) // @license MIT // @namespace http://tampermonkey.net/ // @version 1.1 // @description 自动化批量上架Steam激活码到SteamPY平台,支持精确元素定位和错误恢复 // @author Yuxiang ZHANG // @icon https://steampy.com/img/logo.63413a4f.png // @match https://steampy.com/pyUserInfo/sellerCDKey* // @match https://steampy.com/pyUserInfo/sellerCDKey/* // @grant GM_setValue // @grant GM_getValue // @grant GM_setClipboard // @grant GM_notification // @require https://cdn.jsdelivr.net/npm/sweetalert2@11 // ==/UserScript== (function() { 'use strict'; // 配置 const config = { defaultPrice: 10, defaultRegion: 'china', delayBetweenItems: 2000, batchSize: 5, maxWaitTime: 15000, retryTimes: 3 }; // 添加自定义样式 function addCustomStyles() { const style = document.createElement('style'); style.textContent = ` .centered-swal .swal2-popup { width: 300px !important; max-height: 220px !important; padding: 15px !important; font-size: 13px !important; } .centered-swal .swal2-title { font-size: 16px !important; margin: 0 0 8px 0 !important; padding-right: 20px; } .centered-swal .swal2-html-container { margin: 0 0 12px 0 !important; max-height: 100px !important; overflow-y: auto !important; font-size: 13px !important; } .centered-swal .swal2-actions { margin-top: 8px !important; padding: 0 !important; } .centered-swal .swal2-styled { margin: 4px !important; padding: 5px 10px !important; font-size: 13px !important; } .centered-swal .swal2-icon { width: 36px !important; height: 36px !important; margin: 5px auto 8px !important; } .centered-swal .swal2-icon .swal2-icon-content { font-size: 24px !important; line-height: 36px !important; } .centered-swal .swal2-success-circular-line-left, .centered-swal .swal2-success-circular-line-right, .centered-swal .swal2-success-fix { display: none !important; } .centered-swal .swal2-success .swal2-success-ring { width: 36px !important; height: 36px !important; border-width: 2px !important; } .centered-swal .swal2-success [class^="swal2-success-line"] { height: 3px !important; background-color: #a5dc86 !important; } .centered-swal .swal2-success .swal2-success-line-tip { width: 12px !important; left: 6px !important; top: 21px !important; } .centered-swal .swal2-success .swal2-success-line-long { width: 20px !important; right: 6px !important; top: 17px !important; } .centered-swal .swal2-error .swal2-x-mark { position: relative; width: 36px; height: 36px; } .centered-swal .swal2-error [class^="swal2-x-mark-line"] { height: 3px !important; background-color: #f27474 !important; } .centered-swal .swal2-error .swal2-x-mark-line-left { width: 24px !important; top: 17px !important; left: 6px !important; transform: rotate(45deg) !important; } .centered-swal .swal2-error .swal2-x-mark-line-right { width: 24px !important; top: 17px !important; right: 6px !important; transform: rotate(-45deg) !important; } .centered-swal .swal2-warning .swal2-icon-content { font-size: 24px !important; line-height: 36px !important; } .centered-swal .swal2-close { top: 10px !important; right: 10px !important; font-size: 20px !important; width: 24px !important; height: 24px !important; } .compact-notification .swal2-popup { max-height: 280px !important; } .compact-notification .swal2-html-container { max-height: 180px !important; } `; document.head.appendChild(style); } // 主初始化函数 async function init() { try { addCustomStyles(); // 添加自定义样式 await waitForVueApp(); createUI(); } catch (err) { console.error('初始化失败:', err); showError('加载失败', `无法检测到SteamPY页面元素: ${err.message}`); } } // 等待Vue应用加载 function waitForVueApp() { return new Promise((resolve, reject) => { const startTime = Date.now(); const interval = setInterval(() => { const addButton = findElementByText('button.wh150-rem.ht50-rem.button-detail-bg', '添加CDKey'); if (addButton) { clearInterval(interval); resolve(); } else if (Date.now() - startTime > config.maxWaitTime) { clearInterval(interval); reject(new Error('等待Vue应用加载超时')); } }, 300); }); } // 创建UI界面 function createUI() { if (document.getElementById('steampyBatchHelperUI')) return; // 创建主容器 const container = document.createElement('div'); container.id = 'steampyBatchHelperUI'; Object.assign(container.style, { position: 'fixed', top: '20px', right: '20px', zIndex: '9999', backgroundColor: '#2a475e', color: 'white', fontFamily: 'Arial, sans-serif', width: '350px', borderRadius: '5px', boxShadow: '0 0 10px rgba(0,0,0,0.5)', overflow: 'hidden' }); // 创建标题栏 const header = document.createElement('div'); header.id = 'helperHeader'; Object.assign(header.style, { background: '#1a365d', padding: '12px 15px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', cursor: 'move', borderBottom: '1px solid #66c0f4' }); container.appendChild(header); // 创建标题 const title = document.createElement('h3'); title.className = 'ui-title'; title.textContent = '批量上架助手'; Object.assign(title.style, { color: '#66c0f4', fontWeight: 'bold', fontSize: '0.3rem', margin: '0' }); header.appendChild(title); // 创建控制按钮容器 const controls = document.createElement('div'); controls.className = 'ui-controls'; Object.assign(controls.style, { display: 'flex', gap: '10px' }); header.appendChild(controls); // 最小化按钮 const minimizeBtn = document.createElement('div'); minimizeBtn.id = 'helperMinimizeBtn'; minimizeBtn.textContent = '−'; Object.assign(minimizeBtn.style, { width: '24px', height: '24px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontWeight: 'bold', fontSize: '14px', background: '#5cba47', transition: 'all 0.2s ease' }); controls.appendChild(minimizeBtn); // 关闭按钮 const closeBtn = document.createElement('div'); closeBtn.id = 'helperCloseBtn'; closeBtn.textContent = '×'; Object.assign(closeBtn.style, { width: '24px', height: '24px', borderRadius: '50%', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontWeight: 'bold', fontSize: '14px', background: '#ff4d4d', transition: 'all 0.2s ease' }); controls.appendChild(closeBtn); // 创建内容区域 const content = document.createElement('div'); content.id = 'helperContent'; Object.assign(content.style, { padding: '15px', color: '#c7d5e0' }); container.appendChild(content); // 创建文本区域 const textarea = document.createElement('textarea'); textarea.id = 'batchDataInput'; textarea.placeholder = "每行格式: Steam链接|激活码|价格(可选)\n例如:\nhttps://store.steampowered.com/app/730/CSGO/|ABCDE-FGHIJ-KLMNO|50"; Object.assign(textarea.style, { width: '100%', height: '150px', marginBottom: '10px', padding: '8px', background: '#1a2a4c', border: '1px solid #66c0f4', borderRadius: '4px', color: '#e0e0e0', resize: 'vertical' }); content.appendChild(textarea); // 创建按钮组 const btnGroup = document.createElement('div'); Object.assign(btnGroup.style, { display: 'flex', gap: '10px', marginBottom: '10px' }); content.appendChild(btnGroup); // 粘贴按钮 const pasteBtn = document.createElement('button'); pasteBtn.id = 'pasteBtn'; pasteBtn.textContent = '从剪贴板粘贴'; Object.assign(pasteBtn.style, { flex: '1', padding: '8px', border: 'none', borderRadius: '3px', cursor: 'pointer', fontWeight: 'bold', background: '#1a9fff', color: 'white', transition: 'all 0.2s ease' }); btnGroup.appendChild(pasteBtn); // 清空按钮 const clearBtn = document.createElement('button'); clearBtn.id = 'clearBtn'; clearBtn.textContent = '清空'; Object.assign(clearBtn.style, { flex: '1', padding: '8px', border: 'none', borderRadius: '3px', cursor: 'pointer', fontWeight: 'bold', background: '#ff4d4d', color: 'white', transition: 'all 0.2s ease' }); btnGroup.appendChild(clearBtn); // 全球同步复选框 const globalGroup = document.createElement('div'); Object.assign(globalGroup.style, { display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '15px' }); content.appendChild(globalGroup); const globalCheckbox = document.createElement('input'); globalCheckbox.type = 'checkbox'; globalCheckbox.id = 'globalRegionCheckbox'; globalGroup.appendChild(globalCheckbox); const globalLabel = document.createElement('label'); globalLabel.htmlFor = 'globalRegionCheckbox'; globalLabel.textContent = '在全球区同步进行上架'; globalGroup.appendChild(globalLabel); // 默认价格 const defaultPriceLabel = document.createElement('label'); defaultPriceLabel.textContent = '默认价格(如果数据中未指定):'; Object.assign(defaultPriceLabel.style, { display: 'block', marginBottom: '5px' }); content.appendChild(defaultPriceLabel); const defaultPriceInput = document.createElement('input'); defaultPriceInput.type = 'number'; defaultPriceInput.id = 'defaultPrice'; defaultPriceInput.value = config.defaultPrice; Object.assign(defaultPriceInput.style, { width: '100%', padding: '8px', marginBottom: '15px', background: '#1a2a4c', border: '1px solid #66c0f4', borderRadius: '4px', color: '#e0e0e0' }); content.appendChild(defaultPriceInput); // 操作延迟 const delayLabel = document.createElement('label'); delayLabel.textContent = '操作延迟(毫秒):'; Object.assign(delayLabel.style, { display: 'block', marginBottom: '5px' }); content.appendChild(delayLabel); const delayInput = document.createElement('input'); delayInput.type = 'number'; delayInput.id = 'delayBetweenItems'; delayInput.value = config.delayBetweenItems; Object.assign(delayInput.style, { width: '100%', padding: '8px', marginBottom: '15px', background: '#1a2a4c', border: '1px solid #66c0f4', borderRadius: '4px', color: '#e0e0e0' }); content.appendChild(delayInput); // 批量大小 const batchLabel = document.createElement('label'); batchLabel.textContent = '每批处理数量:'; Object.assign(batchLabel.style, { display: 'block', marginBottom: '5px' }); content.appendChild(batchLabel); const batchInput = document.createElement('input'); batchInput.type = 'number'; batchInput.id = 'batchSize'; batchInput.value = config.batchSize; Object.assign(batchInput.style, { width: '100%', padding: '8px', marginBottom: '20px', background: '#1a2a4c', border: '1px solid #66c0f4', borderRadius: '4px', color: '#e0e0e0' }); content.appendChild(batchInput); // 开始按钮 const startBtn = document.createElement('button'); startBtn.id = 'startBtn'; startBtn.textContent = '开始批量上架'; Object.assign(startBtn.style, { display: 'block', width: '100%', background: '#5cba47', color: 'white', border: 'none', padding: '10px', borderRadius: '3px', cursor: 'pointer', fontWeight: 'bold', fontSize: '16px', transition: 'all 0.3s ease', boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)' }); content.appendChild(startBtn); // 状态区域 const statusBox = document.createElement('div'); statusBox.id = 'batchStatus'; Object.assign(statusBox.style, { marginTop: '15px', fontSize: '14px', padding: '10px', background: 'rgba(0, 0, 0, 0.2)', borderRadius: '3px', minHeight: '60px' }); content.appendChild(statusBox); // 创建最小化UI const minimizedUI = document.createElement('div'); minimizedUI.id = 'minimizedHelperUI'; Object.assign(minimizedUI.style, { position: 'fixed', top: '20px', right: '20px', width: '50px', height: '50px', background: '#5cba47', borderRadius: '50%', display: 'none', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)', zIndex: '9999', transition: 'all 0.3s ease' }); const minimizedText = document.createElement('span'); minimizedText.textContent = 'S'; Object.assign(minimizedText.style, { color: 'white', fontWeight: 'bold', fontSize: '18px' }); minimizedUI.appendChild(minimizedText); document.body.appendChild(container); document.body.appendChild(minimizedUI); // 添加拖动功能 addDragFunctionality(container, header); // 添加最小化/展开功能 setupMinimizeFunctionality(container, minimizedUI); // 事件绑定 pasteBtn.addEventListener('click', pasteFromClipboard); clearBtn.addEventListener('click', () => { textarea.value = ''; }); startBtn.addEventListener('click', startBatchProcess); closeBtn.addEventListener('click', () => { container.style.display = 'none'; minimizedUI.style.display = 'none'; }); } // 添加拖动功能 function addDragFunctionality(element, handle) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; handle.onmousedown = dragMouseDown; function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // 获取鼠标初始位置 pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // 计算鼠标移动的距离 pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 设置元素的新位置 element.style.top = (element.offsetTop - pos2) + "px"; element.style.left = (element.offsetLeft - pos1) + "px"; } function closeDragElement() { // 停止移动 document.onmouseup = null; document.onmousemove = null; } } // 添加最小化/展开功能 function setupMinimizeFunctionality(mainUI, minimizedUI) { const minimizeBtn = document.getElementById('helperMinimizeBtn'); minimizeBtn.addEventListener('click', () => { // 保存位置 const top = mainUI.style.top; const left = mainUI.style.left; // 隐藏主UI mainUI.style.display = 'none'; // 显示最小化UI minimizedUI.style.display = 'flex'; minimizedUI.style.top = top; minimizedUI.style.left = left; }); minimizedUI.addEventListener('click', () => { // 隐藏最小化UI minimizedUI.style.display = 'none'; // 显示主UI mainUI.style.display = 'block'; mainUI.style.top = minimizedUI.style.top; mainUI.style.left = minimizedUI.style.left; }); // 添加拖动功能到最小化UI addDragFunctionality(minimizedUI, minimizedUI); } // 核心处理函数 async function processItem(item, isGlobal, attempt = 1) { try { console.group(`处理物品 [尝试 ${attempt}]:`, item); // 1. 点击添加按钮 const addButton = await waitForElement('button.wh150-rem.ht50-rem.button-detail-bg', '添加CDKey'); await safeClick(addButton); console.log('已点击添加按钮'); // 2. 等待模态窗口完全加载 const visibleModal = await waitForModalReady(); console.log('可见模态窗口已定位'); // 3. 在可见模态窗口中输入链接 const input = await waitForElementWithin(visibleModal, 'input.addCdkIpt'); await safeType(input, item.url); console.log('已输入游戏链接'); // 4. 在可见模态窗口中点击搜索 const searchBtn = await waitForElementWithin(visibleModal, 'button.addCDKBtn:not([disabled])'); await safeClick(searchBtn); console.log('已点击搜索按钮'); // 5. 处理搜索结果 await processSearchResults(visibleModal, item); // 6. 等待表单加载 console.log("等待1秒,确保表单加载..."); await new Promise(resolve => setTimeout(resolve, 1000)); // 7. 填写表单(不再需要区域参数) await fillForm(visibleModal, item, isGlobal); // 8. 提交 await submitForm(visibleModal); console.groupEnd(); return { success: true }; } catch (err) { console.groupEnd(); console.error(`处理失败: ${err.message}`); if (attempt >= config.retryTimes) { throw new Error(`处理失败: ${err.message}`); } console.warn(`将在 ${config.delayBetweenItems}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, config.delayBetweenItems)); return processItem(item, isGlobal, attempt + 1); } } // 处理搜索结果 async function processSearchResults(visibleModal, item) { const gameCardSelector = 'div.flex-row.mt-5.c-point'; // 1. 在可见模态框中等待游戏卡片加载 const gameCard = await waitForElementWithin(visibleModal, gameCardSelector, null, 20000); if (!gameCard) { throw new Error('无法找到游戏卡片'); } // 2. 添加视觉标记以便调试 gameCard.style.outline = '3px solid blue'; gameCard.style.boxShadow = '0 0 10px rgba(0,0,255,0.5)'; console.log('已标记游戏卡片'); // 3. 点击游戏卡片(确保在可见模态框中) await simulateClick(gameCard); console.log('已选择游戏'); // 4. 等待表单加载(检测提交按钮) await waitForElement('button.ivu-btn-error.ivu-btn-long', null, 15000); console.log('表单已加载'); } // 填写表单 - 修复版本 async function fillForm(visibleModal, item, isGlobal) { try { console.log("开始填写表单..."); // 1. 输入CDKey const keyTextarea = await waitForElementWithin(visibleModal, 'textarea.ivu-input'); keyTextarea.value = item.key; keyTextarea.dispatchEvent(new Event('input', { bubbles: true })); keyTextarea.dispatchEvent(new Event('change', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 100)); console.log('已输入CDKey'); // 2. 输入价格 const priceInput = await waitForElementWithin(visibleModal, 'input.ivu-input[type="number"]'); priceInput.value = item.price.toString(); priceInput.dispatchEvent(new Event('input', { bubbles: true })); priceInput.dispatchEvent(new Event('change', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 100)); console.log('已输入价格'); // 3. 修复全球同步复选框定位 - 新的定位方法 if (isGlobal) { try { // 等待全球同步区域加载 await new Promise(resolve => setTimeout(resolve, 800)); // 新的定位策略:直接通过文本定位 const globalTextElement = await waitForElementWithin( visibleModal, 'span', '在全球区同步进行上架', 5000 ); if (globalTextElement) { const globalSection = globalTextElement.closest('div.mt-15.f15'); if (globalSection) { const checkbox = globalSection.querySelector('input[type="checkbox"]'); if (checkbox) { // 添加视觉反馈 globalSection.style.outline = '3px solid #00ff00'; globalSection.style.backgroundColor = 'rgba(0,255,0,0.1)'; if (!checkbox.checked) { // 使用更可靠的点击方法 await safeClick(checkbox); console.log('已勾选全球同步'); // 等待状态更新 await new Promise(resolve => setTimeout(resolve, 500)); } } else { console.warn('在全球同步区域内未找到复选框'); } } else { console.warn('找不到全球同步区域'); } } else { console.warn('找不到包含"在全球区同步进行上架"文本的元素'); } } catch (err) { console.warn('设置全球同步选项失败,尝试备用方法:', err); // 备用方法:直接通过文本定位 const textElement = findElementByText('span', '在全球区同步进行上架', visibleModal); if (textElement) { const parentDiv = textElement.closest('div.mt-15.f15'); if (parentDiv) { const checkbox = parentDiv.querySelector('input[type="checkbox"]'); if (checkbox && !checkbox.checked) { await safeClick(checkbox); console.log('(备用方法) 已勾选全球同步'); } } } } } console.log('表单填写完成'); } catch (err) { console.error('填写表单失败:', err); throw new Error(`填写表单失败: ${err.message}`); } } // 新增辅助函数:在指定元素内查找包含文本的元素 function findElementByText(selector, text, parent = document) { const elements = parent.querySelectorAll(selector); for (const element of elements) { if (element.textContent.includes(text)) { return element; } } return null; } // 提交表单 - 修复版本:处理多模态窗口问题 async function submitForm(visibleModal) { try { // 等待提交按钮可点击 const submitBtn = await waitForElementWithin(visibleModal, 'button.ivu-btn-error.ivu-btn-long'); // 添加视觉标记 submitBtn.style.outline = '3px solid yellow'; submitBtn.style.boxShadow = '0 0 10px rgba(255,255,0,0.5)'; // 记录当前模态窗口数量 const currentModalCount = document.querySelectorAll('div.ivu-modal-wrap:not(.ivu-modal-hidden)').length; // 点击提交按钮 await safeClick(submitBtn); console.log('已点击提交按钮'); // 等待新模态窗口出现(确认出售弹窗) console.log('等待确认出售弹窗...'); let confirmModal; try { // 等待新模态窗口出现(数量增加) confirmModal = await waitForNewModal(currentModalCount, 10000); console.log('确认出售弹窗已显示'); } catch (err) { console.warn('等待新模态窗口失败,尝试直接查找确认出售弹窗...', err); // 备用:直接等待一个包含"注意!!"文本的模态框 confirmModal = await waitForVisibleElementWithText('div.ivu-modal-wrap:not(.ivu-modal-hidden)', '注意!!', 5000); } // 添加视觉标记 confirmModal.style.outline = '3px solid orange'; confirmModal.style.boxShadow = '0 0 10px rgba(255,165,0,0.5)'; // 在确认弹窗中找到确认出售按钮 - 使用更精确的选择器 const confirmButton = await waitForElementWithin( confirmModal, 'button.ivu-btn-info.ivu-btn-long.ivu-btn-large', '确认出售' ); if (!confirmButton) { throw new Error('无法找到确认出售按钮'); } // 添加视觉标记 confirmButton.style.outline = '3px solid red'; confirmButton.style.boxShadow = '0 0 10px rgba(255,0,0,0.5)'; // 点击确认出售按钮 await safeClick(confirmButton); console.log('已点击确认出售按钮'); // 等待确认弹窗关闭 await waitForElementDisappear('div.ivu-modal-wrap:not(.ivu-modal-hidden)', 10000); console.log('确认出售弹窗已关闭'); } catch (err) { console.error('提交失败:', err); throw new Error(`提交失败: ${err.message}`); } } // 新增:等待包含特定文本的可见元素 function waitForVisibleElementWithText(selector, text, timeout = 10000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = () => { try { const elements = document.querySelectorAll(selector); for (const el of elements) { const style = window.getComputedStyle(el); const isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && el.offsetWidth > 0 && el.offsetHeight > 0 && !el.classList.contains('ivu-modal-hidden'); // 检查元素是否包含目标文本 if (isVisible && el.textContent.includes(text)) { return resolve(el); } } if (Date.now() - startTime >= timeout) { reject(new Error(`等待包含文本的可见元素超时: ${text}`)); } else { setTimeout(check, 100); } } catch (err) { if (Date.now() - startTime >= timeout) { reject(err); } else { setTimeout(check, 100); } } }; check(); }); } // 新增:等待新模态窗口出现 function waitForNewModal(baseCount, timeout = 10000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = () => { try { const modals = document.querySelectorAll('div.ivu-modal-wrap:not(.ivu-modal-hidden)'); if (modals.length > baseCount) { // 返回最后一个模态窗口(最上层的) resolve(modals[modals.length - 1]); } else if (Date.now() - startTime >= timeout) { reject(new Error(`等待新模态窗口超时,当前数量: ${modals.length}, 期望大于: ${baseCount}`)); } else { setTimeout(check, 100); } } catch (err) { if (Date.now() - startTime >= timeout) { reject(err); } else { setTimeout(check, 100); } } }; check(); }); } // 辅助函数:模拟点击 async function simulateClick(selectorOrElement) { const element = typeof selectorOrElement === 'string' ? await waitForElement(selectorOrElement) : selectorOrElement; element.scrollIntoViewIfNeeded(); await new Promise(resolve => setTimeout(resolve, 300)); const rect = element.getBoundingClientRect(); const mouseEvent = new MouseEvent('click', { bubbles: true, cancelable: true, clientX: rect.left + rect.width/2, clientY: rect.top + rect.height/2 }); element.dispatchEvent(mouseEvent); // 手动触发可能的自定义事件 const customEvent = new CustomEvent('customClick', { bubbles: true }); element.dispatchEvent(customEvent); } // 改进的输入函数 async function safeType(inputElement, value) { // 确保input是有效的输入元素 if (!(inputElement instanceof HTMLInputElement) && !(inputElement instanceof HTMLTextAreaElement)) { throw new Error('safeType: 无效的输入元素'); } // 清空并输入值 inputElement.value = ""; inputElement.dispatchEvent(new Event('input', { bubbles: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 300)); // 模拟真实输入 for (const char of value) { inputElement.value += char; inputElement.dispatchEvent(new Event('input', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 10)); } // 触发额外事件确保Vue检测 inputElement.dispatchEvent(new Event('blur', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 500)); } // 在父元素内查找子元素 - 修复版本 function waitForElementWithin(parent, selector, text = null, timeout = 10000) { return new Promise((resolve, reject) => { // 确保parent是DOM元素 if (!(parent instanceof Element)) { return reject(new Error('parent必须是DOM元素')); } // 确保selector是字符串 if (typeof selector !== 'string') { return reject(new Error('selector必须是字符串')); } const startTime = Date.now(); const check = () => { try { const elements = parent.querySelectorAll(selector); let found = null; for (const el of elements) { const style = window.getComputedStyle(el); const isVisible = style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0 && el.offsetHeight > 0; if (isVisible && (!text || el.textContent.includes(text))) { found = el; break; } } if (found) { resolve(found); } else if (Date.now() - startTime >= timeout) { reject(new Error(`在父元素内查找元素超时: ${selector}`)); } else { setTimeout(check, 100); } } catch (err) { if (Date.now() - startTime >= timeout) { reject(err); } else { setTimeout(check, 100); } } }; check(); }); } // 改进的点击函数 async function safeClick(element) { element.scrollIntoViewIfNeeded(); await new Promise(resolve => setTimeout(resolve, 500)); // 触发完整点击事件 element.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 50)); element.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); await new Promise(resolve => setTimeout(resolve, 50)); element.dispatchEvent(new MouseEvent('click', { bubbles: true })); // 等待操作执行 await new Promise(resolve => setTimeout(resolve, 1000)); } // 等待模态窗口完全就绪 async function waitForModalReady() { console.log("开始等待可见模态窗口..."); // 使用更可靠的方法检测模态窗口 const modal = await waitForVisibleElement('div.ivu-modal-wrap:not(.ivu-modal-hidden)', 10000); // 添加视觉标记以便调试 modal.style.outline = '3px solid green'; modal.style.boxShadow = '0 0 10px rgba(0,255,0,0.5)'; return modal; } // 等待可见元素(非隐藏状态) function waitForVisibleElement(selector, timeout = 20000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = () => { try { const elements = document.querySelectorAll(selector); for (const el of elements) { // 增强可见性检查 const style = window.getComputedStyle(el); const isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && el.offsetWidth > 0 && el.offsetHeight > 0 && !el.classList.contains('ivu-modal-hidden'); if (isVisible) { console.log("找到可见元素!"); return resolve(el); } } if (Date.now() - startTime >= timeout) { console.error(`等待可见元素超时: ${selector}`); reject(new Error(`等待可见元素超时: ${selector}`)); } else { setTimeout(check, 100); } } catch (err) { console.error('可见性检查错误:', err); if (Date.now() - startTime >= timeout) { reject(err); } else { setTimeout(check, 100); } } }; check(); }); } // 等待元素出现 function waitForElement(selector, text = null, timeout = 30000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = () => { try { let element; if (text) { element = findElementByText(selector, text); } else { element = document.querySelector(selector); } if (element) { resolve(element); } else if (Date.now() - startTime >= timeout) { reject(new Error(`等待元素超时: ${selector}`)); } else { setTimeout(check, 100); } } catch (err) { console.error('元素查找错误:', err); if (Date.now() - startTime >= timeout) { reject(err); } else { setTimeout(check, 100); } } }; check(); }); } // 辅助函数:等待元素消失 function waitForElementDisappear(selector, timeout = 10000) { return new Promise((resolve, reject) => { const startTime = Date.now(); const check = () => { const element = document.querySelector(selector); if (!element || window.getComputedStyle(element).display === 'none') { resolve(); } else if (Date.now() - startTime >= timeout) { reject(new Error(`等待元素消失超时: ${selector}`)); } else { setTimeout(check, 100); } }; check(); }); } // 从剪贴板粘贴 async function pasteFromClipboard() { try { const text = await navigator.clipboard.readText(); document.getElementById('batchDataInput').value = text; showSuccess('已从剪贴板粘贴数据'); } catch (err) { console.error('剪贴板错误:', err); showError('无法访问剪贴板', '请手动粘贴数据'); } } // 开始批量处理 async function startBatchProcess() { const ui = { input: document.getElementById('batchDataInput'), defaultPrice: document.getElementById('defaultPrice'), delay: document.getElementById('delayBetweenItems'), batchSize: document.getElementById('batchSize'), globalCheck: document.getElementById('globalRegionCheckbox'), status: document.getElementById('batchStatus') }; // 验证输入 const items = parseInputData(ui.input.value.trim(), parseFloat(ui.defaultPrice.value)); if (!items.length) { return showError('输入错误', '没有有效的上架数据'); } // 确认对话框 const confirmation = await Swal.fire({ title: '确认批量上架?', html: `<div style="font-size: 14px;"> 将上架 <b>${items.length}</b> 个激活码<br> 默认价格: <b>${ui.defaultPrice.value}</b> 元<br> 每批处理: <b>${ui.batchSize.value}</b> 个 </div>`, icon: 'question', showCancelButton: true, customClass: { popup: 'centered-swal' } }); if (!confirmation.isConfirmed) return; // 开始处理 const settings = { isGlobal: ui.globalCheck.checked, delay: parseInt(ui.delay.value) || config.delayBetweenItems, batchSize: parseInt(ui.batchSize.value) || config.batchSize }; await runBatchProcessing(items, settings, ui.status); } // 解析输入数据 function parseInputData(data, defaultPrice) { return data.split('\n') .map(line => line.trim()) .filter(line => line) .map(line => { const [url, key, price, region] = line.split('|').map(part => part.trim()); return { url: url, key: key || '', price: price ? parseFloat(price) : defaultPrice, region: region || config.defaultRegion }; }) .filter(item => item.url && item.key); } // 执行批量处理 async function runBatchProcessing(items, settings, statusElement) { let processed = 0, successful = 0, failed = 0; const failedItems = []; for (let i = 0; i < items.length; i += settings.batchSize) { const batch = items.slice(i, i + settings.batchSize); for (const item of batch) { statusElement.innerHTML = `处理中: ${++processed}/${items.length}<br> 成功: ${successful}, 失败: ${failed}`; try { const result = await processItem(item, settings.isGlobal); if (result.success) successful++; } catch (err) { failed++; failedItems.push(item); console.error('处理失败:', err); statusElement.innerHTML += `<br><span style="color:red">失败: ${item.url}</span>`; } if (processed < items.length) { await new Promise(resolve => setTimeout(resolve, settings.delay)); } } // 批次间隔 if (i + settings.batchSize < items.length) { statusElement.innerHTML += `<br>已完成一批 ${settings.batchSize} 个, 暂停 5 秒...`; await new Promise(resolve => setTimeout(resolve, 5000)); } } // 完成处理 statusElement.innerHTML += `<br><b>处理完成! 成功: ${successful}, 失败: ${failed}</b>`; showCompletion(successful, failed, failedItems); } // 显示完成通知 function showCompletion(success, failed, failedItems) { let html = `<div style="font-size: 14px;">批量上架完成!<br>成功: <b>${success}</b>, 失败: <b>${failed}</b></div>`; if (failed > 0) { html += `<div style="margin-top: 10px; max-height: 120px; overflow-y: auto;">`; html += `<b>失败项:</b><br>`; html += failedItems.slice(0, 5).map(item => `• ${item.url.substring(0, 40)}...`).join('<br>'); if (failedItems.length > 5) html += `<br>...及其他 ${failedItems.length - 5} 项`; html += `</div>`; } Swal.fire({ title: '完成', html: html, icon: failed ? 'warning' : 'success', customClass: { popup: 'centered-swal compact-notification' } }); } // 工具函数:显示通知 function showSuccess(text) { Swal.fire({ text, icon: 'success', timer: 1500, customClass: { popup: 'centered-swal' } }); } function showError(title, text) { Swal.fire({ title, text, icon: 'error', customClass: { popup: 'centered-swal' } }); } // 启动脚本 if (document.readyState === 'complete') { init(); } else { window.addEventListener('load', init); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址