Greasy Fork

Greasy Fork is available in English.

获取竞价数据(shop.48.cn)

自动提取 Cookie 并请求竞价数据接口

// ==UserScript==
// @name         获取竞价数据(shop.48.cn)
// @namespace    http://tampermonkey.net/
// @version      1.8.1
// @description  自动提取 Cookie 并请求竞价数据接口
// @author       GPT
// @match        https://shop.48.cn/pai/item/*
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';
    
    // 延迟函数
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    // 获取起竞价
    function getStartingBid() {
        const container = document.querySelector("#TabTab03Con1");
        if (!container) return null;
    
        const html = container.innerHTML;
        const match = html.match(/起竞价:<span>(\d+)<\/span>元/);
    
        if (match && match[1]) {
            return parseInt(match[1], 10);
        }
    
        return null;
    }

    // ✅ 获取 URL 中的商品 ID
    function getItemIdFromURL() {
        const match = window.location.href.match(/item\/(\d+)/);
        return match ? match[1] : null;
    }

    // ✅ 构建 POST 参数
    function buildParams(itemId) {
        return new URLSearchParams({
            id: itemId,
            numPerPage: 100,
            pageNum: 0,
            r: Math.random().toString()
        });
    }

    // ✅ 获取指定 Cookie 值
    function getCookie(name) {
        const cookies = document.cookie.split("; ");
        for (const cookie of cookies) {
            const [key, value] = cookie.split("=");
            if (key === name) return value;
        }
        return null;
    }

    // ✅ 构建 Cookie 请求头
    function buildCookieHeader() {
        const cookieNames = [
            "Hm_lvt_f32737cfa62ed971bb3185792d3204eb",
            "route",
            ".AspNet.ApplicationCookie",
            "HMACCOUNT",
            "__RequestVerificationToken",
            "Hm_lpvt_f32737cfa62ed971bb3185792d3204eb"
        ];

        const cookieMap = {};
        cookieNames.forEach(name => {
            const value = getCookie(name);
            if (value) {
                cookieMap[name] = value;
            } else {
                console.warn(`未找到 cookie: ${name}`);
            }
        });

        if (Object.keys(cookieMap).length === 0) return null;

        return Object.entries(cookieMap)
            .map(([k, v]) => `${k}=${v}`)
            .join("; ");
    }

    // ✅ 发起 POST 请求
    async function fetchBidAmountAtIndex(targetIndex, itemId, cookieHeader, initialLimit = 100, maxLimit = 1000, pageSize = 100) {
        let validList = [];
    
        // 第一阶段:逐步放大 numPerPage
        // for (let numPerPage = initialLimit; numPerPage <= maxLimit; numPerPage += 100) {
        //     const params = new URLSearchParams({
        //         id: itemId,
        //         numPerPage: numPerPage.toString(),
        //         pageNum: "0",
        //         r: Math.random().toString()
        //     });
    
        //     const response = await fetch("https://shop.48.cn/pai/GetShowBids", {
        //         method: "POST",
        //         headers: {
        //             "Content-Type": "application/x-www-form-urlencoded",
        //             "Cookie": cookieHeader
        //         },
        //         body: params
        //     });
            
    
        //     if (!response.ok) {
        //         throw new Error(`请求失败,状态码 ${response.status}`);
        //     }
    
        //     const data = await response.json();
        //     console.error(data);
        //     validList = data.list.filter(entry => entry.auction_status === 1);
    
        //     if (validList.length >= targetIndex) {
        //         console.error(validList);
        //         return validList[targetIndex - 1].bid_amt;
        //     }
    
        //     // 如果这一页返回的数据已经小于请求条数,说明数据到底了,提前退出
        //     if (data.list.length < numPerPage) {
        //         return 0;
        //     }
        // }
    
        // 第二阶段:开始翻页 pageNum 从 1 往后翻
        let pageNum = 1;
        initialLimit = 20
    
        while (true) {
            const params = new URLSearchParams({
                id: itemId,
                numPerPage: initialLimit.toString(),
                pageNum: pageNum.toString(),
                r: Math.random().toString()
            });
    
            const response = await fetch("https://shop.48.cn/pai/GetShowBids", {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded",
                    "Cookie": cookieHeader
                },
                body: params
            });
    
            if (!response.ok) {
                throw new Error(`请求失败,状态码 ${response.status}`);
            }
    
            const data = await response.json();
            const dataList = data.list.slice(0, 20);
            const pageValid = dataList.filter(entry => entry.auction_status === 1);
            validList = validList.concat(pageValid);
    
            if (validList.length >= targetIndex) {
                return validList[targetIndex - 1].bid_amt;
            }
    
            if (data.PageCount < pageNum) {
                // 已到达最后一页
                break;
            }
            if (pageNum % 3 === 0){
                await sleep(900)
            }
            pageNum++;
        }
    
        return 0;
    }

    // 出价接口调用
    async function placeBid(uid, amt, num = 1, code = "") {
        const formData = new URLSearchParams({
            id: uid,
            amt: Math.ceil(amt).toString(),  // 金额向上取整
            num: num.toString(),
            code: code,
            r: Math.random().toString()
        });
    
        try {
            const response = await fetch("/pai/ToBids", {
                method: "POST",
                headers: {
                    "Content-Type": "application/x-www-form-urlencoded"
                },
                body: formData
            });
    
            const data = await response.json();
    
            // 页面提示和信息更新
            if (!data.HasError) {
                layer?.msg?.(data.Message || "出价成功!");
                const el1 = document.querySelector("#sp_auction_count_1");
                if (el1) el1.innerText = data.ErrorCode;
                
                const el0 = document.querySelector("#sp_auction_count_0");
                if (el0) el0.innerText = data.ErrorCode;
    
                // 可选函数(你可以取消注释)
                // SetInfoB(uid, 20, 0);
                // getInfoByTime();
    
                return true;
            } else {
                layer?.msg?.(data.Message || "出价失败!");
                const el1 = document.querySelector("#sp_auction_count_1");
                if (el1) el1.innerText = data.ErrorCode;
                
                const el0 = document.querySelector("#sp_auction_count_0");
                if (el0) el0.innerText = data.ErrorCode;
                
                await sleep(1000);
                return false;
            }
        } catch (error) {
            console.error("出价异常:", error);
            layer?.msg?.("出价接口调用失败");
            return false;
        }
    }

    

    // 创建按钮
    function createFetchButton() {
        // 1. 创建容器
        const container = document.createElement("div");
        container.id = "bid-fetch-container";
        Object.assign(container.style, {
            position: "fixed",
            top: "80px",
            right: "20px",
            zIndex: "9999",
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            gap: "8px",
            backgroundColor: "white",
            padding: "12px",
            borderRadius: "10px",
            boxShadow: "0 0 10px rgba(0,0,0,0.2)",
            cursor: "move", // 设置鼠标样式
            userSelect: "none"
        });
    
        // 2. 拖动逻辑
        let isDragging = false;
        let offsetX, offsetY;
    
        container.addEventListener("mousedown", function (e) {
            isDragging = true;
            offsetX = e.clientX - container.getBoundingClientRect().left;
            offsetY = e.clientY - container.getBoundingClientRect().top;
            document.body.style.userSelect = "none"; // 禁止文本选中
        });
    
        document.addEventListener("mousemove", function (e) {
            if (isDragging) {
                const left = e.clientX - offsetX;
                const top = e.clientY - offsetY;
                container.style.left = `${left}px`;
                container.style.top = `${top}px`;
                container.style.right = "auto"; // 防止初始 right 冲突
            }
        });
    
        document.addEventListener("mouseup", function () {
            isDragging = false;
            document.body.style.userSelect = ""; // 恢复文本选中
        });
    
        // 3. 输入框
        // 目标名次输入框 + 标签
        const rankLabel = document.createElement("label");
        rankLabel.innerText = "目标名次:";
        rankLabel.style.fontSize = "14px";
        rankLabel.style.marginRight = "5px";

        const rankInput = document.createElement("input");
        rankInput.type = "number";
        rankInput.id = "positionInput";
        rankInput.placeholder = "输入第几位";
        rankInput.min = "1";
        Object.assign(rankInput.style, {
            padding: "6px",
            width: "140px",
            fontSize: "14px",
            borderRadius: "6px",
            border: "1px solid #ccc"
        });
        
        // 预算金额输入框 + 标签
        const budgetLabel = document.createElement("label");
        budgetLabel.innerText = "预算金额(不填默认无限):";
        budgetLabel.style.fontSize = "14px";
        budgetLabel.style.marginRight = "5px";
        
        const budgetInput = document.createElement("input");
        budgetInput.type = "number";
        budgetInput.placeholder = "¥";
        budgetInput.style.width = "80px";
        budgetInput.style.marginRight = "20px";
    
        // 4. 按钮
        const btn = document.createElement("button");
        btn.innerText = "📥 获取竞价数据";
        btn.id = "fetchBidsBtn";
        Object.assign(btn.style, {
            padding: "10px 16px",
            fontSize: "16px",
            backgroundColor: "#4CAF50",
            color: "white",
            border: "none",
            borderRadius: "8px",
            cursor: "pointer"
        });
    
        // 5. 显示框
        const output = document.createElement("div");
        output.id = "bidAmtOutput";
        output.innerText = "等待结果...";
        Object.assign(output.style, {
            marginTop: "4px",
            fontSize: "14px",
            color: "#333"
        });
    
        // 6. 按钮点击逻辑(保持原样,不变)
        btn.addEventListener("click", async () => {
            btn.innerText = "⏳ 正在获取...";
            btn.disabled = true;
    
            try {
                const index = parseInt(rankInput.value);
                const budget = parseFloat(budgetInput.value);
            
                if (isNaN(index) || index <= 0) {
                    layer?.msg?.("请输入有效的目标名次!");
                    return;
                }
            
                if (budget <= 0) {
                    layer?.msg?.("请输入有效的预算金额!");
                    return;
                }
    
                const itemId = getItemIdFromURL();
                const params = buildParams(itemId);
                const cookieHeader = buildCookieHeader();
    
                if (!cookieHeader) {
                    output.innerText = "未获取到 cookie";
                    return;
                }
    
                // 获取出价金额
                let bidAmt = await fetchBidAmountAtIndex(index, itemId, cookieHeader);
        
                // 如果获取失败,使用起竞价
                if (bidAmt === 0) {
                    const startingBid = getStartingBid();
                    if (startingBid !== null) {
                        bidAmt = startingBid;
                        output.innerText = `第 ${index} 位无人出价,使用起竞价 ¥${startingBid}`;
                    } else {
                        output.innerText = `第 ${index} 位无人出价,且未能获取起竞价`;
                        return;
                    }
                } else {
                    output.innerText = `第 ${index} 位出价金额:¥${bidAmt}`;
                }
                
                //自动出价
                if (cbInput.checked) {
                    const finalBid = Math.ceil(bidAmt) + 1;
            
                    if (!isNaN(budget) && finalBid > budget) {
                        layer?.msg?.(`⚠️ 出价 ¥${finalBid} 超出预算 ¥${budget},不执行出价`);
                        return;
                    }
            
                    const success = await placeBid(itemId, finalBid);
                    if (success) {
                        console.log("自动出价成功");
                    } else {
                        layer?.msg?.("❌ 自动出价失败!");
                    }
                }
                
            } catch (err) {
                console.error(err);
                output.innerText = "请求出错,请查看控制台";
            } finally {
                btn.innerText = "📥 获取竞价数据";
                btn.disabled = false;
            }
        });
        
        // 7. 添加复选框
        const checkbox = document.createElement("label");
        checkbox.style.fontSize = "14px";
        checkbox.style.cursor = "pointer";
        
        const cbInput = document.createElement("input");
        cbInput.type = "checkbox";
        cbInput.id = "autoBidCheckbox";
        cbInput.style.marginRight = "4px";
        
        checkbox.appendChild(cbInput);
        checkbox.appendChild(document.createTextNode("自动 +1 元出价"));
        
        const warning = document.createElement("div");
        warning.innerText = "⚠️ 自动出价在测试阶段 慎用 勾选了则自动出价 未勾选仅做查询";
        warning.style.color = "red";
        warning.style.fontWeight = "bold";
        warning.style.margin = "8px 0";
    
        // 8. 加入页面
        container.appendChild(rankLabel);
        container.appendChild(rankInput);
        container.appendChild(budgetLabel);
        container.appendChild(budgetInput);
        container.appendChild(warning);  // 警告文本
        container.appendChild(checkbox); // 自动+1元
        container.appendChild(btn);      // 获取按钮
        container.appendChild(output);   // 输出结果
        document.body.appendChild(container);
    }



    // ✅ 主执行函数
    async function run() {
        const itemId = getItemIdFromURL();
        if (!itemId) {
            console.warn("未找到商品 ID");
            return;
        }

        const params = buildParams(itemId);
        const cookieHeader = buildCookieHeader();

        if (!cookieHeader) {
            console.error("未能获取必要的 cookie,可能是未登录");
            return;
        }

        try {
            const data = await fetchBids(params, cookieHeader);
            console.log("竞价数据:", data);
        } catch (error) {
            console.error("竞价请求失败:", error);
        }
    }
    
    // 主入口
    function init() {
        createFetchButton(); // 添加按钮
    }
    
    // ✅ 启动脚本
    init();

})();