心理价位自动出价增强版

京东拍拍自动根据心理价位出价,自动最高价出价+美化界面+一键启停

// ==UserScript==
// @name         心理价位自动出价增强版
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  京东拍拍自动根据心理价位出价,自动最高价出价+美化界面+一键启停
// @author       你的名字
// @match        https://paipai.jd.com/auction-detail/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    /*** 全局变量区 ***/
    let isRunning = true; // 脚本开关
    let autoMaxBid = true; // 是否自动最高价
    let quickAddOne = true; // 是否自动加1
    let preSeconds = 1; // 提前几秒出价
    let refreshTimer = null;

    /*** UI区 ***/
    const panel = createElement('div', {
        position: 'fixed',
        top: '10px',
        right: '10px',
        background: '#fff',
        padding: '15px',
        borderRadius: '10px',
        boxShadow: '0 2px 12px rgba(0,0,0,0.2)',
        fontSize: '14px',
        zIndex: 9999,
        width: '260px',
        color: '#333',
        fontFamily: 'Arial, sans-serif'
    });

    panel.innerHTML = `
        <div style="margin-bottom:10px;">心理价位:<input id="maxPriceInput" type="number" style="width:80px;" /> <button id="setMaxPrice">设置</button></div>
        <div style="margin-bottom:10px;">刷新间隔(秒):<input id="refreshInput" type="number" style="width:50px;" /> <button id="setRefresh">设置</button> <button id="cancelRefresh">取消</button></div>
        <div style="margin-bottom:10px;">
            <label><input id="quickAdd" type="checkbox" checked /> 快速加价</label><br/>
            <label><input id="maxBid" type="checkbox" checked /> 自动最高出价</label>
        </div>
        <div style="margin-bottom:10px;">提前<input id="preSeconds" type="number" style="width:40px;" value="1"/>秒出价</div>
        <div style="margin-bottom:10px;">状态:<span id="statusText" style="color:green;">运行中</span></div>
        <button id="toggleRun" style="width:100%;background:#007BFF;color:#fff;border:none;padding:5px;border-radius:5px;">暂停脚本</button>
    `;

    document.body.appendChild(panel);

    /*** 初始化已保存的数据 ***/
    const maxPriceInput = panel.querySelector('#maxPriceInput');
    const refreshInput = panel.querySelector('#refreshInput');
    const preSecondsInput = panel.querySelector('#preSeconds');

    maxPriceInput.value = localStorage.getItem('maxPrice') || '';
    refreshInput.value = (parseInt(localStorage.getItem('refreshInterval')) || 60 * 1000) / 1000;
    preSecondsInput.value = localStorage.getItem('preSeconds') || 1;

    /*** 绑定UI按钮事件 ***/
    panel.querySelector('#setMaxPrice').onclick = () => {
        localStorage.setItem('maxPrice', parseFloat(maxPriceInput.value));
        showMessage('心理价位设置成功');
    };

    panel.querySelector('#setRefresh').onclick = () => {
        const interval = parseInt(refreshInput.value) * 1000;
        if (refreshTimer) clearInterval(refreshTimer);
        refreshTimer = setInterval(() => location.reload(), interval);
        localStorage.setItem('refreshInterval', interval);
        showMessage(`刷新时间设置为${refreshInput.value}秒`);
    };

    panel.querySelector('#cancelRefresh').onclick = () => {
        if (refreshTimer) clearInterval(refreshTimer);
        localStorage.removeItem('refreshInterval');
        showMessage('刷新取消');
    };

    panel.querySelector('#quickAdd').onchange = (e) => {
        quickAddOne = e.target.checked;
    };

    panel.querySelector('#maxBid').onchange = (e) => {
        autoMaxBid = e.target.checked;
    };

    panel.querySelector('#toggleRun').onclick = () => {
        isRunning = !isRunning;
        panel.querySelector('#toggleRun').innerText = isRunning ? '暂停脚本' : '启动脚本';
        panel.querySelector('#statusText').innerText = isRunning ? '运行中' : '已暂停';
        panel.querySelector('#statusText').style.color = isRunning ? 'green' : 'red';
    };

    preSecondsInput.onchange = (e) => {
        localStorage.setItem('preSeconds', e.target.value);
        preSeconds = parseInt(e.target.value);
    };

    /*** 出价逻辑区 ***/
    observeElement('#J-count-down', (countdownEl) => {
        const observer = new MutationObserver(() => {
            const countdown = getCountdownTime();
            if (!countdown) return;
            if (countdown <= preSeconds && isRunning) {
                tryAutoBid();
            }
        });
        observer.observe(countdownEl, { childList: true, subtree: true, characterData: true });
    });

    /*** 价格监控区 ***/
    checkPriceLoop();
    function checkPriceLoop() {
        setInterval(() => {
            if (!isRunning) return;
            const price = getCurrentPrice();
            const maxPrice = parseFloat(localStorage.getItem('maxPrice'));
            if (price && maxPrice && price > maxPrice) {
                showMessage(`当前价${price}超出心理价${maxPrice}`, true);
            }
        }, 3000);
    }

    /*** 核心出价动作 ***/
    function tryAutoBid() {
        const bidInput = document.querySelector('.el-input .el-input__inner');
        const bidButton = document.querySelector('.choose-btns.clearfix .btn-special6.btn-lg');
        if (!bidInput || !bidButton) return;

        const currentPrice = getCurrentPrice();
        const maxPrice = parseFloat(localStorage.getItem('maxPrice'));

        if (isNaN(currentPrice) || isNaN(maxPrice)) return;

        if (currentPrice >= maxPrice) {
            showMessage('当前价格已超心理价', true);
            return;
        }

        let newBid = currentPrice + 1;
        if (autoMaxBid) {
            newBid = maxPrice;
        }

        bidInput.value = newBid.toFixed(2);
        bidButton.click();
        showMessage(`自动出价:¥${newBid}`);
    }

    /*** 辅助函数区 ***/
    function createElement(tag, styleObj = {}) {
        const el = document.createElement(tag);
        Object.assign(el.style, styleObj);
        return el;
    }

    function showMessage(text, warning = false) {
        const msg = createElement('div', {
            position: 'fixed',
            top: '70px',
            right: '10px',
            background: warning ? '#ff4d4f' : '#4CAF50',
            color: '#fff',
            padding: '8px 12px',
            borderRadius: '5px',
            fontSize: '14px',
            zIndex: 10000,
            boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
            transition: 'all 0.5s'
        });
        msg.textContent = text;
        document.body.appendChild(msg);
        setTimeout(() => { msg.remove(); }, 3000);
    }

    function getCountdownTime() {
        const iTags = document.querySelectorAll('#J-count-down i');
        if (!iTags.length) return null;
        const timeStr = [...iTags].map(i => i.textContent.trim()).join(':');
        const [h, m, s] = timeStr.split(':').map(Number);
        return h * 3600 + m * 60 + s;
    }

    function getCurrentPrice() {
        const priceEl = document.querySelector('.p-price .price');
        if (!priceEl) return NaN;
        const price = parseFloat(priceEl.textContent.replace(/[^\d.]/g, ''));
        return price;
    }

    function observeElement(selector, callback) {
        const observer = new MutationObserver(() => {
            const el = document.querySelector(selector);
            if (el) {
                callback(el);
                observer.disconnect();
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
    }

    /*** 初始化刷新定时器 ***/
    const intervalFromStorage = localStorage.getItem('refreshInterval');
    if (intervalFromStorage) {
        refreshTimer = setInterval(() => location.reload(), parseInt(intervalFromStorage));
    }
})();