新球体育网欧盘分析

新球体育网(球探)手机端网页,比赛的分析页面右上角添加了一个按钮,点击后生成关键时间点的欧盘变化。

当前为 2025-10-02 提交的版本,查看 最新版本

// ==UserScript==
// @name         新球体育网欧盘分析
// @namespace    http://dol.freevar.com/
// @version      0.5
// @description  新球体育网(球探)手机端网页,比赛的分析页面右上角添加了一个按钮,点击后生成关键时间点的欧盘变化。
// @author       Dolphin
// @match        https://m.titan007.com/analy/Analysis/*
// @match        https://m.titan007.com/Analy/Analysis/*
// @run-at       document-idle
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加欧盘按钮
    const btnsDiv = document.querySelector('div.btns');
    const oddsBtn = document.createElement('div');
    oddsBtn.className = 'btn';
    oddsBtn.textContent = '欧盘';
    oddsBtn.style.backgroundColor = '#4c8';
    btnsDiv.appendChild(oddsBtn);

    // 按钮点击事件处理
    oddsBtn.addEventListener('click', function() {
        // 清除已存在的表格
        const existingTable = document.querySelector('#oddsTable');
        if (existingTable) {
            existingTable.remove();
        }

        // 请求赔率数据
        GM_xmlhttpRequest({
            method: 'GET',
            url: `https://txt.titan007.com/1x2/${scheduleId}.js`,
            onload: function(response) {
                // 解析赔率数据
                const data = parseOddsData(response.responseText);
                if (!data) return;

                // 获取需要分析的时间点
                const timePoints = getTimePoints(data);

                // 计算每个时间点的平均赔率
                const oddsData = calculateAverageOdds(data, timePoints);

                // 创建并插入表格
                const table = createOddsTable(oddsData);
                const contentDiv = document.querySelector('div#content');
                contentDiv.insertBefore(table, contentDiv.firstChild);
            }
        });
    });

    // 解析赔率数据
    function parseOddsData(responseText) {
        try {
            // 创建临时变量存储解析结果
            const data = {};

            // 提取公司信息
            const gameMatch = responseText.match(/var game=Array\((.*?)\);/);
            if (gameMatch && gameMatch[1]) {
                data.companies = {};
                const companyEntries = gameMatch[1].split('","');
                companyEntries.forEach(entry => {
                    const parts = entry.replace(/^"|"$/g, '').split('|');
                    if (parts.length >= 3) {
                        data.companies[parts[1]] = {
                            id: parts[1],
                            name: parts[2]
                        };
                    }
                });
            }

            // 提取赔率详情
            const gameDetailMatch = responseText.match(/var gameDetail=Array\((.*?)\);/);
            if (gameDetailMatch && gameDetailMatch[1]) {
                data.details = {};
                const detailEntries = gameDetailMatch[1].split('","');
                detailEntries.forEach(entry => {
                    const cleanEntry = entry.replace(/^"|"$/g, '');
                    const [companyId, oddsStr] = cleanEntry.split('^');
                    if (companyId && oddsStr) {
                        data.details[companyId] = [];
                        const oddsEntries = oddsStr.split(';');
                        oddsEntries.forEach(oddEntry => {
                            if (oddEntry.trim() === '') return;
                            const parts = oddEntry.split('|');
                            if (parts.length >= 4) {
                                // 解析时间
                                const dateParts = parts[3].split(' ');
                                const timeParts = dateParts[1].split(':');
                                const monthDay = dateParts[0].split('-');
                                const year = parts[7] || new Date().getFullYear();

                                const date = new Date(
                                    parseInt(year),
                                    parseInt(monthDay[0]) - 1,
                                    parseInt(monthDay[1]),
                                    parseInt(timeParts[0]),
                                    parseInt(timeParts[1])
                                );

                                data.details[companyId].push({
                                    win: parseFloat(parts[0]),
                                    draw: parseFloat(parts[1]),
                                    lose: parseFloat(parts[2]),
                                    timestamp: date.getTime()
                                });
                            }
                        });
                        // 按时间排序
                        data.details[companyId].sort((a, b) => a.timestamp - b.timestamp);
                    }
                });
            }

            return data;
        } catch (e) {
            console.error('解析赔率数据出错:', e);
            return null;
        }
    }

    // 获取需要分析的时间点
    function getTimePoints(data) {
        // 收集网页自带的4个时间戳
        const baseTimes = [
            parseInt(jsonData.nearMatches.homeMatches.matches[0].matchTime) * 1000,
            parseInt(jsonData.nearMatches.homeMatches.matches[1].matchTime) * 1000,
            parseInt(jsonData.nearMatches.awayMatches.matches[0].matchTime) * 1000,
            parseInt(jsonData.nearMatches.awayMatches.matches[1].matchTime) * 1000
        ];

        // 添加这4个时间点的3小时后
        const threeHoursLater = baseTimes.map(time => time + 3 * 60 * 60 * 1000);

        // 找到所有公司最早有赔率数据的时间点
        let earliestOddsTime = Infinity;
        Object.values(data.details).forEach(companyOdds => {
            if (companyOdds.length > 0 && companyOdds[0].timestamp < earliestOddsTime) {
                earliestOddsTime = companyOdds[0].timestamp;
            }
        });

        // 添加最早时间点和24小时后
        const earliestTimes = [
            earliestOddsTime + 3600000,
            earliestOddsTime + 24 * 60 * 60 * 1000
        ];

        // 合并所有时间点并去重,然后按降序排列
        const allTimes = [...baseTimes, ...threeHoursLater, ...earliestTimes];
        const uniqueTimes = [...new Set(allTimes)];
        uniqueTimes.sort((a, b) => b - a);

        return uniqueTimes;
    }

    // 计算每个时间点的平均赔率
    function calculateAverageOdds(data, timePoints) {
        const result = [];

        timePoints.forEach(timestamp => {
            let totalWin = 0;
            let totalDraw = 0;
            let totalLose = 0;
            let companyCount = 0;

            // 计算每个公司在该时间点前的最新赔率
            Object.values(data.details).forEach(companyOdds => {
                // 找到时间点前的最后一个赔率记录
                for (let i = companyOdds.length - 1; i >= 0; i--) {
                    if (companyOdds[i].timestamp <= timestamp) {
                        totalWin += companyOdds[i].win;
                        totalDraw += companyOdds[i].draw;
                        totalLose += companyOdds[i].lose;
                        companyCount++;
                        break;
                    }
                }
            });

            // 计算平均值
            result.push({
                timestamp: timestamp,
                companyCount: companyCount,
                avgWin: companyCount > 0 ? (totalWin / companyCount).toFixed(4) : '-',
                avgDraw: companyCount > 0 ? (totalDraw / companyCount).toFixed(4) : '-',
                avgLose: companyCount > 0 ? (totalLose / companyCount).toFixed(4) : '-'
            });
        });

        return result;
    }

    // 创建赔率表格
    function createOddsTable(oddsData) {
        const table = document.createElement('table');
        table.id = 'oddsTable';
        table.style.borderCollapse = 'collapse';
        table.style.margin = '0 auto';
        table.style.textAlign = 'center';
        table.style.fontSize = '16px';

        // 创建表头
        const thead = document.createElement('thead');
        const headerRow = document.createElement('tr');
        headerRow.style.backgroundColor = '#ddd';

        const headers = ['公司数量', '平均胜赔', '平均平赔', '平均负赔', '时间'];
        headers.forEach(text => {
            const th = document.createElement('th');
            th.textContent = text;
            th.style.border = '1px solid #888';
            headerRow.appendChild(th);
        });

        thead.appendChild(headerRow);
        table.appendChild(thead);

        // 创建表体
        const tbody = document.createElement('tbody');

        oddsData.forEach((data, index) => {
            const row = document.createElement('tr');
            row.style.backgroundColor = index % 2 === 0 ? '#fff' : '#eee';

            // 公司数量
            const countCell = document.createElement('td');
            countCell.textContent = data.companyCount;
            countCell.style.border = '1px solid #888';
            row.appendChild(countCell);

            // 平均胜赔(带颜色变化)
            const winCell = document.createElement('td');
            winCell.textContent = data.avgWin;
            winCell.style.border = '1px solid #888';
            if (index < oddsData.length - 1 && data.avgWin !== '-' && oddsData[index + 1].avgWin !== '-') {
                const current = data.avgWin;
                const next = oddsData[index + 1].avgWin;
                if (current > next) {
                    winCell.style.backgroundColor = '#fbb'; // 上涨(较下一行更高)
                } else if (current < next) {
                    winCell.style.backgroundColor = '#bfb'; // 下跌(较下一行更低)
                }
            }
            row.appendChild(winCell);

            // 平均平赔(带颜色变化)
            const drawCell = document.createElement('td');
            drawCell.textContent = data.avgDraw;
            drawCell.style.border = '1px solid #888';
            if (index < oddsData.length - 1 && data.avgDraw !== '-' && oddsData[index + 1].avgDraw !== '-') {
                const current = data.avgDraw;
                const next = oddsData[index + 1].avgDraw;
                if (current > next) {
                    drawCell.style.backgroundColor = '#fbb'; // 上涨
                } else if (current < next) {
                    drawCell.style.backgroundColor = '#bfb'; // 下跌
                }
            }
            row.appendChild(drawCell);

            // 平均负赔(带颜色变化)
            const loseCell = document.createElement('td');
            loseCell.textContent = data.avgLose;
            loseCell.style.border = '1px solid #888';
            if (index < oddsData.length - 1 && data.avgLose !== '-' && oddsData[index + 1].avgLose !== '-') {
                const current = data.avgLose;
                const next = oddsData[index + 1].avgLose;
                if (current > next) {
                    loseCell.style.backgroundColor = '#fbb'; // 上涨
                } else if (current < next) {
                    loseCell.style.backgroundColor = '#bfb'; // 下跌
                }
            }
            row.appendChild(loseCell);

            // 时间
            const timeCell = document.createElement('td');
            const date = new Date(data.timestamp);
            timeCell.textContent = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
            timeCell.style.border = '1px solid #888';
            row.appendChild(timeCell);

            tbody.appendChild(row);
        });

        table.appendChild(tbody);
        return table;
    }
})();