// ==UserScript== // @name:en [MWI]Inventory Items Quick Sell Assistant // @name [银河奶牛]库存物品一键自动出售 // @namespace https://cnb.cool/shenhuanjie/skyner-cn/tamper-monkey-script/mwi-auto-sell-assistant // @version 1.0.7 // @description:en [Auto Sell Assistant] One-click auto sell items, optimize operation delay, improve game efficiency // @description 一键自动出售库存中指定物品,智能优化操作延迟,提升游戏效率,快来试试吧 // @author shenhuanjie // @license MIT // @match https://www.milkywayidle.com/game* // @icon https://www.milkywayidle.com/favicon.svg // @grant GM_setValue // @grant GM_getValue // @homepage https://greasyfork.org/scripts/535491 // @supportURL https://greasyfork.org/scripts/535491 // @connect greasyfork.org // @require https://cdn.tailwindcss.com // @run-at document-idle // @noframes // @downloadURL https://update.greasyfork.icu/scripts/535491/%5B%E9%93%B6%E6%B2%B3%E5%A5%B6%E7%89%9B%5D%E5%BA%93%E5%AD%98%E7%89%A9%E5%93%81%E4%B8%80%E9%94%AE%E8%87%AA%E5%8A%A8%E5%87%BA%E5%94%AE.user.js // @updateURL https://update.greasyfork.icu/scripts/535491/%5B%E9%93%B6%E6%B2%B3%E5%A5%B6%E7%89%9B%5D%E5%BA%93%E5%AD%98%E7%89%A9%E5%93%81%E4%B8%80%E9%94%AE%E8%87%AA%E5%8A%A8%E5%87%BA%E5%94%AE.meta.js // ==/UserScript== (function() { 'use strict'; // 判断用户语言环境 const isChinese = navigator.language.includes('zh'); // 国际化消息 const messages = { zh: { // 按钮文本 goToMarket: '前往市场', sell: '出售', all: '全部', postSellOrder: '发布出售订单', // 错误消息 selectItemFirst: '请先选择一个物品!', cannotNavigateToMarket: '无法导航到市场页面!', // 通知消息 scriptLoaded: '脚本已加载', executingStep: '执行: {action} ({attempt}/{maxAttempts})', clickedButton: '已点击"{action}"按钮', stepCompleted: '步骤 {current}/{total} ({action}) 耗时 {time}ms', stepFailed: '步骤 {current}/{total} ({action}) 失败: {error} 耗时 {time}ms', executionFailed: '执行失败: {error}', increasingDelay: '增加延迟至 {delay}ms,准备重试', chainCompleted: '动作链执行完毕,总耗时 {time}ms', optimizedDelay: '优化"{action}"的延迟至 {delay}ms', optimizationCompleted: '优化完成,平均节省 {time}ms' }, en: { // 按钮文本 goToMarket: 'Go to Market', sell: 'Sell', all: 'All', postSellOrder: 'Post Sell Order', // 错误消息 selectItemFirst: 'Please select an item first!', cannotNavigateToMarket: 'Cannot navigate to market page!', // 通知消息 scriptLoaded: 'Script loaded', executingStep: 'Executing: {action} ({attempt}/{maxAttempts})', clickedButton: 'Clicked "{action}" button', stepCompleted: 'Step {current}/{total} ({action}) completed in {time}ms', stepFailed: 'Step {current}/{total} ({action}) failed: {error} in {time}ms', executionFailed: 'Execution failed: {error}', increasingDelay: 'Increasing delay to {delay}ms, preparing to retry', chainCompleted: 'Action chain completed, total time: {time}ms', optimizedDelay: 'Optimized "{action}" delay to {delay}ms', optimizationCompleted: 'Optimization completed, average time saved: {time}ms' } }; // 获取消息函数 function getMessage(key, replacements = {}) { const lang = isChinese ? 'zh' : 'en'; let message = messages[lang][key]; if (!message) { console.warn(`Missing translation for key: ${key}`); return key; } for (const [placeholder, value] of Object.entries(replacements)) { message = message.replace(`{${placeholder}}`, value); } return message; } // 全局配置 const CONFIG = { debugMode: true, notificationPosition: 'top-right', notificationDuration: 2000, defaultHighlightColor: 'rgba(255, 0, 0, 0.5)', defaultHighlightDuration: 500, precondition: { selector: '[class*="Item_selected__"]', errorMessage: getMessage('selectItemFirst') }, hotkey: { key: 'k', altKey: false, ctrlKey: false, shiftKey: false }, marketSelectors: [ '[class*="MarketplacePage_container__"]', '[class*="MarketplacePanel_"]', '[data-testid="marketplace"]' ], localStorageKey: 'autoClickOptimalDelays', minDelay: 200, // 最小延迟时间(毫秒) maxDelay: 2000, // 最大延迟时间(毫秒) delayStep: 50, // 延迟调整步长(毫秒) retryAttempts: 3, // 每步最大重试次数 successThreshold: 5, // 计算最优延迟的成功次数基数 optimizationFactor: 1.3 // 安全余量系数 }; // 从本地存储加载最优延迟配置 let optimalDelays = JSON.parse(GM_getValue(CONFIG.localStorageKey, '{}')); // 初始化执行统计 const executionStats = {}; // 节点池(存储所有可选节点) const NODE_POOL = [ { id: 'start', type: 'start' }, { id: 'action1', type: 'action', description: isChinese ? '前往市场' : 'Go to Market', containerSelector: '[class*="MuiTooltip-tooltip"]', buttonSelector: 'button[class*="Button_button__"][class*="Button_fullWidth__"]', text: getMessage('goToMarket'), checkResult: function() { return checkMarketPage(); }, errorMessage: getMessage('cannotNavigateToMarket') }, { id: 'action2', type: 'action', description: isChinese ? '出售' : 'Sell', containerSelector: '[class*="MarketplacePanel_itemContainer__"]', buttonSelector: 'button[class*="Button_sell__"]', text: getMessage('sell') }, { id: 'action3', type: 'action', description: isChinese ? '全部' : 'All', containerSelector: '[class*="MarketplacePanel_quantityInputs__"]', buttonSelector: 'button', text: getMessage('all') }, { id: 'action4', type: 'action', description: isChinese ? '发布出售订单' : 'Post Sell Order', containerSelector: '[class*="MarketplacePanel_modalContent__"]', buttonSelector: '[class*="MarketplacePanel_postButtonContainer__"] > button[class*="Button_success__"]', text: getMessage('postSellOrder') }, { id: 'end', type: 'end' } ]; // 工作流配置(指定当前使用的节点及顺序) const WORKFLOW_CONFIG = [ { nodeId: 'start', onSuccess: 'action1', onFailure: 'end' }, { nodeId: 'action1', onSuccess: 'action2', onFailure: 'end' }, { nodeId: 'action2', onSuccess: 'action3', onFailure: 'end' }, { nodeId: 'action3', onSuccess: 'action4', onFailure: 'end' }, { nodeId: 'action4', onSuccess: 'end', onFailure: 'end' }, { nodeId: 'end' } ]; // 初始化工作流节点延迟配置 NODE_POOL.forEach(node => { if (node.type === 'action') { const key = node.description; node.preDelay = optimalDelays[key] || 800; node.postDelay = optimalDelays[`${key}_post`] || 600; executionStats[key] = { successes: 0, failures: 0, totalTime: 0, attempts: [] }; } }); // 根据工作流配置生成实际执行的节点映射 const WORKFLOW_NODES = WORKFLOW_CONFIG.map(config => { const node = NODE_POOL.find(n => n.id === config.nodeId); return { ...node, onSuccess: config.onSuccess, onFailure: config.onFailure }; }); // 生成工作流字符画函数 function printWorkflowDiagram() { if (!CONFIG.debugMode) return; console.log('===== 工作流配置流程图 ====='); console.log('节点类型:[Start] 开始节点 | [Action] 操作节点 | [End] 结束节点'); console.log('连接符号:→ 成功跳转 | × 失败跳转'); console.log(''); WORKFLOW_CONFIG.forEach(config => { const node = NODE_POOL.find(n => n.id === config.nodeId); if (!node) return; let nodeLabel; switch (node.type) { case 'start': nodeLabel = `[Start] ${node.id}`; break; case 'action': nodeLabel = `[Action] ${node.id} (${node.description})`; break; case 'end': nodeLabel = `[End] ${node.id}`; break; default: nodeLabel = `[Unknown] ${node.id}`; } if (config.onSuccess) { const successNode = NODE_POOL.find(n => n.id === config.onSuccess) || { id: config.onSuccess }; console.log(`${nodeLabel} → ${successNode.id} (成功)`); } if (config.onFailure && config.onFailure !== config.onSuccess) { const failureNode = NODE_POOL.find(n => n.id === config.onFailure) || { id: config.onFailure }; console.log(`${nodeLabel} × ${failureNode.id} (失败)`); } }); console.log('=========================='); } // 调试模式下输出流程图 printWorkflowDiagram(); // 初始化 document.addEventListener('keydown', function(event) { const { key, altKey, ctrlKey, shiftKey } = CONFIG.hotkey; if ( event.key.toLowerCase() === key.toLowerCase() && event.altKey === altKey && event.ctrlKey === ctrlKey && event.shiftKey === shiftKey ) { event.preventDefault(); executeWorkflow(); // 改为触发工作流执行 } }); log(getMessage('scriptLoaded'), 'info'); log(`${isChinese ? '使用最优延迟配置' : 'Using optimal delay configuration'}: ${JSON.stringify(optimalDelays)}`, 'info'); // 市场页面检查函数 function checkMarketPage() { for (const selector of CONFIG.marketSelectors) { if (document.querySelectorAll(selector).length > 0) { return true; } } return false; } // 执行完整动作链 async function executeWorkflow() { // 执行前置条件检查 if (!checkPrecondition()) { showNotification(CONFIG.precondition.errorMessage, 'error'); return; } const startTime = performance.now(); let currentNodeId = 'start'; // 初始节点为start // 计算总步数(不包括start和end节点) const totalSteps = WORKFLOW_NODES.filter(node => node.type === 'action').length; let currentStep = 0; while (true) { const currentNode = WORKFLOW_NODES.find(node => node.id === currentNodeId); if (!currentNode) { showNotification(isChinese ? '工作流节点未找到' : 'Workflow node not found', 'error'); return; } if (currentNode.type === 'end') { // 到达结束节点 const totalDuration = Math.round(performance.now() - startTime); // 检查是否有未处理的错误 if (currentNode.error) { const errorMsg = `${isChinese ? '流程异常结束' : 'Workflow aborted'}: ${currentNode.error.message}`; const notification = showNotification(errorMsg, 'error'); if (notification) { notification.style.top = '20px'; notification.style.right = '20px'; } log(errorMsg, 'error'); } else { showNotification(getMessage('chainCompleted', {time: totalDuration}), 'success'); log(getMessage('chainCompleted', {time: totalDuration}), 'success'); } saveOptimalDelays(); return; } if (currentNode.type === 'action') { currentStep++; // 增加当前步数 let attempt = 0; let success = false; while (attempt < CONFIG.retryAttempts && !success) { attempt++; // 只在日志中记录执行信息,不显示通知 log(getMessage('executingStep', { action: currentNode.description, attempt: attempt, maxAttempts: CONFIG.retryAttempts }), 'info'); const actionStartTime = performance.now(); try { // 不再显示开始执行的通知,只在成功完成时显示 // 查找元素 const element = findElement(currentNode); if (!element) { // 显示错误通知,然后抛出错误 const errorMsg = isChinese ? `未找到"${currentNode.description}"按钮` : `"${currentNode.description}" button not found`; // 确保通知显示在右上角 const notification = showNotification(`${isChinese ? '步骤' : 'Step'} ${currentStep}/${totalSteps}: ${errorMsg}`, 'error'); notification.style.top = '20px'; notification.style.right = '20px'; const error = new Error(errorMsg); error.notificationShown = true; // 标记已显示通知 throw error; } // 高亮并点击元素 highlightElement(element); // 记录点击前的状态 const beforeClickTime = performance.now(); element.click(); log(getMessage('clickedButton', {action: currentNode.description}), 'success'); // 等待后置延迟并检查结果 await wait(currentNode.postDelay); // 检查结果(如果有检查函数) if (typeof currentNode.checkResult === 'function') { const result = currentNode.checkResult(); if (!result) { throw new Error(currentNode.errorMessage || (isChinese ? `执行"${currentNode.description}"后检查失败` : `Check failed after executing "${currentNode.description}"`)); } } // 执行成功 success = true; currentNodeId = currentNode.onSuccess || 'end'; // 默认跳转end // 计算实际执行时间 const actualTime = Math.round(performance.now() - actionStartTime); // 显示步骤完成信息 showNotification(getMessage('stepCompleted', { current: currentStep, total: totalSteps, action: currentNode.description, time: actualTime }), 'success'); // 更新统计信息 updateStats(currentNode.description, true, actualTime); // 等待下一个节点的前置延迟 await wait(currentNode.preDelay); } catch (error) { // 执行失败 const errorTime = Math.round(performance.now() - actionStartTime); log(`${isChinese ? '错误' : 'Error'}: ${error.message}`, 'error'); // 确保显示包含步骤信息的错误消息(如果之前没有显示过) if (!error.notificationShown) { showNotification(`${isChinese ? '步骤' : 'Step'} ${currentStep}/${totalSteps}: ${error.message} (${errorTime}ms)`, 'error'); } updateStats(currentNode.description, false, errorTime); if (attempt < CONFIG.retryAttempts) { currentNode.postDelay = Math.min(currentNode.postDelay + CONFIG.delayStep, CONFIG.maxDelay); log(`增加 "${currentNode.description}" 的延迟至 ${currentNode.postDelay}ms`, 'warning'); showNotification(getMessage('increasingDelay', {delay: currentNode.postDelay}), 'warning'); } else { // 标记为异常结束并传递错误信息 const endNode = WORKFLOW_NODES.find(node => node.id === (currentNode.onFailure || 'end')); if (endNode) { endNode.error = error; } currentNodeId = currentNode.onFailure || 'end'; } } } } else if (currentNode.type === 'start') { // 开始节点直接跳转到onSuccess currentNodeId = currentNode.onSuccess || 'end'; // showNotification(isChinese ? '工作流已启动' : 'Workflow started', 'info'); } } } // 查找元素函数 function findElement(action) { const containers = document.querySelectorAll(action.containerSelector); for (const container of containers) { const candidates = container.querySelectorAll(action.buttonSelector); for (const candidate of candidates) { if (candidate.textContent.trim() === action.text) { return candidate; } } } // 尝试全局查找 const globalCandidates = document.querySelectorAll(action.buttonSelector); for (const candidate of globalCandidates) { if (candidate.textContent.trim() === action.text) { return candidate; } } // 如果没有找到精确匹配,尝试模糊匹配(对于可能的翻译差异) if (!isChinese) { // 在英文环境下,尝试更宽松的匹配 for (const container of containers) { const candidates = container.querySelectorAll(action.buttonSelector); for (const candidate of candidates) { const buttonText = candidate.textContent.trim().toLowerCase(); const actionText = action.text.toLowerCase(); // 检查按钮文本是否包含动作文本,或动作文本是否包含按钮文本 if (buttonText.includes(actionText) || actionText.includes(buttonText)) { return candidate; } } } // 全局模糊匹配 for (const candidate of globalCandidates) { const buttonText = candidate.textContent.trim().toLowerCase(); const actionText = action.text.toLowerCase(); if (buttonText.includes(actionText) || actionText.includes(buttonText)) { return candidate; } } } return null; } // 前置条件检查 function checkPrecondition() { const elements = document.querySelectorAll(CONFIG.precondition.selector); return elements.length > 0; } // 高亮元素 function highlightElement(element, color = CONFIG.defaultHighlightColor, duration = CONFIG.defaultHighlightDuration) { const originalStyle = element.style.cssText; element.style.cssText = ` ${originalStyle} transition: all 0.3s; box-shadow: 0 0 0 3px ${color}; `; setTimeout(() => { element.style.cssText = originalStyle; }, duration); } // 存储活动通知 const activeNotifications = []; // 更新通知位置 function updateNotificationPositions() { let currentTop = 20; activeNotifications.forEach(notif => { notif.style.top = `${currentTop}px`; currentTop += notif.offsetHeight + 10; }); } // 显示通知(支持堆叠效果) function showNotification(message, type = 'info') { // 创建新通知 const notification = document.createElement('div'); notification.className = 'action-chain-notification'; // 通知样式 notification.style.cssText = ` position: fixed; padding: 12px 16px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 9999; transition: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; font-weight: 500; max-width: 300px; transform: translateY(-30px); opacity: 0; right: 20px; `; // 类型样式 const types = { info: { bg: 'rgba(30, 144, 255, 0.95)', text: 'white', icon: 'ℹ️' }, success: { bg: 'rgba(76, 175, 80, 0.95)', text: 'white', icon: '✅' }, error: { bg: 'rgba(244, 67, 54, 0.95)', text: 'white', icon: '❌' }, warning: { bg: 'rgba(255, 193, 7, 0.95)', text: '#333', icon: '⚠️' } }; const style = types[type] || types.info; notification.style.backgroundColor = style.bg; notification.style.color = style.text; // 设置内容(添加图标) notification.innerHTML = `