Greasy Fork

Greasy Fork is available in English.

[银河奶牛]库存物品快速交易(支持右一左一快速操作)

一键自动出售物品,当单击选择了物品,也就是展开了前往市场,然后按S键,就会自动卖右一,按A键就会自动挂左一

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            [银河奶牛]库存物品快速交易(支持右一左一快速操作)
// @namespace       https://tampermonkey.net/
// @version         1.1.1
// @description     一键自动出售物品,当单击选择了物品,也就是展开了前往市场,然后按S键,就会自动卖右一,按A键就会自动挂左一
// @author          YuoHira
// @license         MIT
// @icon            https://www.milkywayidle.com/favicon.svg
// @match           https://www.milkywayidle.com/game*
// ==/UserScript==

(function () {
    'use strict';

    // 全局变量,用于取消操作
    let isOperating = false;

    // 模拟真实用户点击
    function simulateRealClick(element) {
        if (!element) return false;
        
        try {
            // 获取元素的位置和大小
            const rect = element.getBoundingClientRect();
            
            // 计算元素中心位置
            const centerX = rect.left + rect.width / 2;
            const centerY = rect.top + rect.height / 2;
            
            // 添加1-5像素的随机偏移
            const offsetX = (Math.random() * 10 - 5); // -5到5之间的随机偏移
            const offsetY = (Math.random() * 10 - 5); // -5到5之间的随机偏移
            
            // 最终点击位置 (确保不会超出元素边界)
            const clickX = Math.min(Math.max(centerX + offsetX, rect.left + 2), rect.right - 2);
            const clickY = Math.min(Math.max(centerY + offsetY, rect.top + 2), rect.bottom - 2);
            
            // 创建鼠标事件,添加位置信息
            const eventOptions = {
                bubbles: true,
                cancelable: true,
                view: window,
                button: 0,
                buttons: 1,
                clientX: clickX,
                clientY: clickY,
                screenX: clickX,
                screenY: clickY
            };
            
            const mouseDownEvent = new MouseEvent('mousedown', eventOptions);
            const mouseUpEvent = new MouseEvent('mouseup', eventOptions);
            const clickEvent = new MouseEvent('click', eventOptions);
            
            // 随机延迟模拟人类行为
            const delay1 = Math.floor(Math.random() * 20) + 10; // 10-30ms
            const delay2 = Math.floor(Math.random() * 30) + 20; // 20-50ms
            
            // 分发事件序列
            element.dispatchEvent(mouseDownEvent);
            
            setTimeout(() => {
                element.dispatchEvent(mouseUpEvent);
                
                setTimeout(() => {
                    element.dispatchEvent(clickEvent);
                }, delay2);
            }, delay1);
            
            return true;
        } catch (e) {
            console.error('模拟点击失败:', e);
            // 如果模拟失败,回退到普通点击
            try {
                element.click();
                return true;
            } catch (clickError) {
                console.error('普通点击也失败:', clickError);
                return false;
            }
        }
    }

    // 显示临时提示信息
    function showTemporaryMessage(message, duration = 3000) {
        // 移除之前可能存在的临时提示
        const existingMessage = document.getElementById('temporary-script-message');
        if (existingMessage) {
            existingMessage.remove();
        }

        // 创建提示元素
        const messageDiv = document.createElement('div');
        messageDiv.id = 'temporary-script-message';
        messageDiv.textContent = message;
        messageDiv.style.cssText = `
            position: fixed;
            top: 20%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 15px 25px;
            border-radius: 8px;
            z-index: 10000;
            font-size: 16px;
            opacity: 1;
            transition: opacity 0.5s ease-in-out;
            pointer-events: none; /* 不影响下方元素的点击 */
        `;

        // 添加到页面
        document.body.appendChild(messageDiv);

        // 设置定时器,duration毫秒后移除
        setTimeout(() => {
            messageDiv.style.opacity = '0'; // 淡出效果
            setTimeout(() => {
                messageDiv.remove();
            }, 500); // 等待淡出动画完成再移除
        }, duration);
    }

    // 按键触发自动交易
    document.addEventListener('keydown', function (event) {
        if (event.key.toLowerCase() === 's') {
            event.preventDefault();
            if (!isOperating) {
                autoSell();
            }
        } else if (event.key.toLowerCase() === 'a') {
            event.preventDefault();
            if (!isOperating) {
                autoSellLeft();
            }
        } else if (isOperating) {
            // 按下任意其他键取消操作
            isOperating = false;
            console.log('用户取消了操作');
            // 将alert替换为showTemporaryMessage
            showTemporaryMessage('已取消操作', 3000);
        }
    });

    // 查找按钮函数 - 根据按钮文本查找
    function findButtonByText(buttonText, container = document) {
        // 将buttonText转换为数组,支持"或"操作
        const textArray = Array.isArray(buttonText) ? buttonText : [buttonText];

        // 查找所有按钮元素
        const buttons = container.querySelectorAll('button');

        // 遍历所有按钮查找匹配文本的按钮
        for (const btn of buttons) {
            for (const text of textArray) {
                if (btn.textContent.includes(text)) {
                    return btn;
                }
            }
        }

        // 查找可能嵌套在其他元素中的按钮
        const elements = container.querySelectorAll('*');
        for (const el of elements) {
            for (const text of textArray) {
                if (el.textContent.trim() === text && el.querySelector('button')) {
                    return el.querySelector('button');
                }
            }
        }

        return null;
    }

    // 查找按钮函数 - 根据精确匹配的文本查找
    function findButtonByExactText(buttonText, container = document) {
        // 查找所有按钮元素
        const buttons = container.querySelectorAll('button');

        // 遍历所有按钮查找完全匹配文本的按钮
        for (const btn of buttons) {
            if (btn.textContent.trim() === buttonText) {
                return btn;
            }
        }

        // 查找可能嵌套在其他元素中的按钮
        const elements = container.querySelectorAll('*');
        for (const el of elements) {
            // 检查元素自身是否完全匹配
            if (el.childNodes.length === 1 && el.textContent.trim() === buttonText) {
                const nearestButton = el.closest('button');
                if (nearestButton) {
                    return nearestButton;
                }
            }

            // 检查元素内是否有完全匹配的文本节点
            const textNodes = Array.from(el.childNodes).filter(node => node.nodeType === Node.TEXT_NODE);
            for (const textNode of textNodes) {
                if (textNode.textContent.trim() === buttonText) {
                    const nearestButton = el.closest('button') || el.querySelector('button');
                    if (nearestButton) {
                        return nearestButton;
                    }
                }
            }
        }

        return null;
    }

    // 创建步骤对象 - 封装步骤逻辑
    function createStep(name, containerSelector, buttonTextOrArray, exactMatch = false, successCallback = null) {
        return {
            name: name,
            fn: () => {
                // 使用querySelectorAll查找所有匹配的容器元素
                const containers = document.querySelectorAll(containerSelector);
                if (containers.length === 0) {
                    return false;
                }
                
                // 遍历所有容器
                for (const container of containers) {
                    // 执行成功回调(如果有)
                    if (successCallback) {
                        const callbackResult = successCallback(container);
                        if (callbackResult === true) {
                            return true;
                        }
                    }

                    // 查找并点击按钮
                    const btn = exactMatch
                        ? findButtonByExactText(buttonTextOrArray, container)
                        : findButtonByText(buttonTextOrArray, container);

                    if (btn) {
                        // 使用模拟真实点击替代普通点击
                        return simulateRealClick(btn);
                    }
                }
                
                return false;
            }
        };
    }

    // 持续尝试执行步骤直到成功或超时
    async function tryExecuteStep(stepFn, stepName, timeout = 5000) {
        console.log(`尝试执行: ${stepName}`);
        return new Promise((resolve) => {
            const startTime = Date.now();

            // 使用requestAnimationFrame持续尝试
            function attemptStep() {
                if (!isOperating) {
                    resolve(false); // 用户取消了操作
                    return;
                }

                try {
                    const success = stepFn();
                    if (success) {
                        console.log(`成功: ${stepName}`);
                        resolve(true);
                        return;
                    }
                } catch (err) {
                    console.error(`执行错误 ${stepName}:`, err);
                }

                // 检查是否超时
                if (Date.now() - startTime > timeout) {
                    console.error(`超时: ${stepName}`);
                    resolve(false);
                    return;
                }

                // 继续尝试
                requestAnimationFrame(attemptStep);
            }

            attemptStep();
        });
    }

    // 执行一系列步骤
    async function executeSteps(steps) {
        for (let i = 0; i < steps.length; i++) {
            if (!isOperating) {
                showTemporaryMessage('操作已取消', 3000);
                break;
            }

            const step = steps[i];
            const success = await tryExecuteStep(step.fn, step.name);

            if (!success) {
                console.error(`步骤 ${i + 1} (${step.name}) 失败`);
                isOperating = false;
                showTemporaryMessage(`步骤 ${i + 1} (${step.name}) 失败: 请手动检查`, 3000);
                return false;
            }

            // 成功执行完一个步骤后等待一小段时间让UI更新
            await new Promise(resolve => setTimeout(resolve, 300));
        }

        return true;
    }

    // 自动交易流程 - 直接出售
    async function autoSell() {
        isOperating = true;

        // 检查是否选择了物品
        if (!document.querySelector('[class*="Item_selected__"]')) {
            // 将alert替换为showTemporaryMessage
            showTemporaryMessage('请先选择物品!', 3000);
            isOperating = false;
            return;
        }

        // 定义交易步骤
        const steps = [
            // 步骤1: 前往市场
            createStep('前往市场', '[class*="MuiTooltip-tooltip"]', '前往市场'),

            // 步骤2: 点击出售
            createStep('点击出售', '[class*="MarketplacePanel_orderBook"]', '出售', true),

            // 步骤3: 点击全部或检查已有最多
            createStep('点击全部', '[class*="MarketplacePanel_modalContent"]', ['全部', '最多']),

            // 步骤4: 发布出售订单
            createStep('发布出售订单', '[class*="MarketplacePanel_modalContent__"]', '发布出售')
        ];

        // 执行步骤
        const success = await executeSteps(steps);

        isOperating = false;
        if (success) {
            console.log('交易完成!');
            // 可以选择在这里也加一个完成提示
            // showTemporaryMessage('直接出售完成!', 3000);
        }
    }

    // 自动交易流程 - 挂左一
    async function autoSellLeft() {
        isOperating = true;

        // 检查是否选择了物品
        if (!document.querySelector('[class*="Item_selected__"]')) {
            // 替换alert,使用自定义函数显示3秒后自动消失的提示
            showTemporaryMessage('请先选择物品!', 3000); // 显示提示信息,持续3秒
            isOperating = false;
            return;
        }

        // 定义交易步骤
        const steps = [
            // 步骤1: 前往市场
            createStep('前往市场', '[class*="MuiTooltip-tooltip"]', '前往市场'),

            // 步骤2: 点击新出售挂牌
            createStep('新出售挂牌', '[class*="MarketplacePanel_orderBook"]', '新出售挂牌'),

            // 步骤3: 点击全部或检查已有最多
            createStep('点击全部', '[class*="MarketplacePanel_modalContent"]', ['全部', '最多']),

            // 步骤4: 点击"+"按钮(左一)
            createStep('点击加号', '[class*="MarketplacePanel_modalContent"]', '+'),

            // 步骤5: 发布出售订单
            createStep('发布出售订单', '[class*="MarketplacePanel_modalContent__"]', '发布出售')
        ];

        // 执行步骤
        const success = await executeSteps(steps);

        isOperating = false;
        if (success) {
            console.log('挂左一完成!');
            // 可以选择在这里也加一个完成提示
            // showTemporaryMessage('挂左一完成!', 3000);
        }
    }

    console.log('牛牛快速交易脚本已加载,按S触发直接出售,按A触发挂左一,按任意其他键取消');
})();