// ==UserScript==
// @name 新球体育网欧盘分析
// @namespace http://dol.freevar.com/
// @version 0.6
// @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';
// 全局变量存储解析后的赔率数据
let oddsData = {}; // 存储格式: {公司编号: {时间戳: {win, draw, lose}, ...}, ...}
let allTimestamps = []; // 所有可用的时间戳
let tableData = []; // 表格数据
// 初始化函数
function init() {
// 使用页面自带的scheduleId变量请求数据
const url = `https://txt.titan007.com/1x2/${scheduleId}.js`;
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function(response) {
if (response.status === 200) {
parseOddsData(response.responseText);
initializeTableData();
createUI();
} else {
console.error("获取赔率数据失败:", response.statusText);
}
},
onerror: function(error) {
console.error("请求赔率数据时发生错误:", error);
}
});
}
// 解析赔率数据
function parseOddsData(data) {
// 提取gameDetail数组
const gameDetailMatch = data.match(/var gameDetail=Array\((.*?)\);/);
if (!gameDetailMatch) {
console.error("无法解析gameDetail数据");
return;
}
const gameDetailStr = gameDetailMatch[1];
// 分割各个公司的数据
const companyEntries = gameDetailStr.split('","').map(item => {
// 去除首尾引号
return item.replace(/^"/, '').replace(/"$/, '');
});
// 解析每个公司的数据
companyEntries.forEach(entry => {
const [companyId, oddsInfo] = entry.split('^');
if (!oddsInfo) return;
// 分割不同时间点的赔率
const oddsEntries = oddsInfo.split(';').filter(item => item.trim() !== '');
oddsData[companyId] = {};
oddsEntries.forEach(oddsEntry => {
// 解析赔率条目
const parts = oddsEntry.split('|');
if (parts.length < 4) return;
const [winOdds, drawOdds, loseOdds, timeStr] = parts;
// 解析时间格式:月-日 时:分
const [datePart, timePart] = timeStr.split(' ');
const [month, day] = datePart.split('-');
const [hour, minute] = timePart.split(':');
// 从数据中提取年份
const year = parts[7] || new Date().getFullYear();
// 创建时间戳
const timestamp = new Date(`${year}-${month}-${day}T${hour}:${minute}:00`).getTime();
// 存储赔率信息
oddsData[companyId][timestamp] = {
win: parseFloat(winOdds),
draw: parseFloat(drawOdds),
lose: parseFloat(loseOdds)
};
// 收集所有时间戳
if (!allTimestamps.includes(timestamp)) {
allTimestamps.push(timestamp);
}
});
});
// 按时间戳排序
allTimestamps.sort((a, b) => a - b);
}
// 初始化表格数据 - 添加最早和最晚的时间点
function initializeTableData() {
if (allTimestamps.length > 0) {
// 添加最晚的时间点
addTimePointToTable(allTimestamps[allTimestamps.length - 1]);
// 添加主客最近一场比赛开始和结束的时间点
addTimePointToTable(parseInt(jsonData.nearMatches.homeMatches.matches[0].matchTime) * 1000);
addTimePointToTable(parseInt(jsonData.nearMatches.homeMatches.matches[0].matchTime) * 1000 + 3 * 3600000);
addTimePointToTable(parseInt(jsonData.nearMatches.awayMatches.matches[0].matchTime) * 1000);
addTimePointToTable(parseInt(jsonData.nearMatches.awayMatches.matches[0].matchTime) * 1000 + 3 * 3600000);
// 添加最早的时间点
addTimePointToTable(allTimestamps[0]);
}
}
// 添加时间点到表格数据
function addTimePointToTable(timestamp) {
let totalWin = 0;
let totalDraw = 0;
let totalLose = 0;
let companyCount = 0;
// 计算该时间点前的赔率平均值
Object.values(oddsData).forEach(companyOdds => {
// 找到该时间点或之前的最新赔率
const companyTimestamps = Object.keys(companyOdds).map(Number).sort((a, b) => b - a);
const validTimestamp = companyTimestamps.find(ts => ts <= timestamp);
if (validTimestamp) {
const odds = companyOdds[validTimestamp];
totalWin += odds.win;
totalDraw += odds.draw;
totalLose += odds.lose;
companyCount++;
}
});
if (companyCount > 0) {
const avgWin = (totalWin / companyCount).toFixed(4);
const avgDraw = (totalDraw / companyCount).toFixed(4);
const avgLose = (totalLose / companyCount).toFixed(4);
// 格式化时间显示
const date = new Date(timestamp);
const formattedTime = `${(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')}`;
// 检查该时间点是否已存在
const existingIndex = tableData.findIndex(item => item.timestamp === timestamp);
if (existingIndex === -1) {
// 添加到表格数据
tableData.push({
timestamp,
formattedTime,
companyCount,
avgWin,
avgDraw,
avgLose
});
// 按时间戳倒序排序(最新的在上面)
tableData.sort((a, b) => b.timestamp - a.timestamp);
// 更新表格显示
updateTable();
}
}
}
// 添加用户自定义时间点
function addCustomTimePoint(timeStr) {
// 解析用户输入的时间格式:YYYYMMDDHHmm
if (timeStr.length !== 12) {
alert("请输入正确的时间格式:YYYYMMDDHHmm");
return;
}
const year = timeStr.substr(0, 4);
const month = timeStr.substr(4, 2);
const day = timeStr.substr(6, 2);
const hour = timeStr.substr(8, 2);
const minute = timeStr.substr(10, 2);
try {
const timestamp = new Date(`${year}-${month}-${day}T${hour}:${minute}:00`).getTime();
if (isNaN(timestamp)) {
alert("无效的时间格式");
return;
}
addTimePointToTable(timestamp);
} catch (e) {
alert("无效的时间格式");
console.error("时间解析错误:", e);
}
}
// 创建用户界面
function createUI() {
// 创建容器
const container = document.createElement('div');
container.style.border = '1px solid #888';
container.style.borderRadius = '5px';
container.style.textAlign = 'center';
container.style.fontSize = '16px';
// 创建输入框和按钮
const timeInput = document.createElement('input');
timeInput.type = 'number';
timeInput.style.fontSize = '16px';
timeInput.placeholder = '输入时间(YYYYMMDDHHmm)';
timeInput.style.padding = '4px';
timeInput.style.margin = '8px';
timeInput.style.border = '1px solid #888';
timeInput.style.borderRadius = '4px';
const addButton = document.createElement('button');
addButton.style.fontSize = '16px';
addButton.textContent = '添加时间';
addButton.style.padding = '6px 12px';
addButton.style.backgroundColor = '#48c';
addButton.style.color = 'white';
addButton.style.border = 'none';
addButton.style.borderRadius = '4px';
addButton.style.cursor = 'pointer';
addButton.addEventListener('click', () => {
addCustomTimePoint(timeInput.value);
timeInput.value = '';
});
container.appendChild(timeInput);
container.appendChild(addButton);
// 创建标题
const title = document.createElement('div');
title.style.background = '#eee';
title.textContent = '热门比赛欧亚转换(非精确):0.5球1.9⚽0.75球1.7⚽1球1.5⚽1.25球1.4⚽1.5球1.28⚽1.75球1.22⚽2球1.16';
container.appendChild(title);
// 创建表格
const table = document.createElement('table');
table.id = 'oddsTable';
table.style.borderCollapse = 'collapse';
table.style.margin = '8px auto';
// 创建表头
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
headerRow.style.backgroundColor = '#48c';
headerRow.style.color = 'white';
const headers = ['公司数', '平均胜赔', '平均平赔', '平均负赔', '时间'];
headers.forEach(headerText => {
const th = document.createElement('th');
th.textContent = headerText;
th.style.border = '1px solid #888';
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// 创建表体
const tbody = document.createElement('tbody');
tbody.id = 'oddsTableBody';
table.appendChild(tbody);
container.appendChild(table);
// 插入到页面中
const contentDiv = document.querySelector('div#content');
contentDiv.insertBefore(container, contentDiv.firstChild);
// 初始更新表格内容
updateTable();
}
// 更新表格内容
function updateTable() {
const tbody = document.getElementById('oddsTableBody');
if (!tbody) return;
// 清空表格
tbody.innerHTML = '';
// 添加数据行
tableData.forEach((rowData) => {
const row = document.createElement('tr');
// 公司数量
const countCell = document.createElement('td');
countCell.textContent = rowData.companyCount;
countCell.style.border = '1px solid #888';
row.appendChild(countCell);
// 平均胜赔
const winCell = document.createElement('td');
winCell.textContent = rowData.avgWin;
winCell.style.border = '1px solid #888';
row.appendChild(winCell);
// 平均平赔
const drawCell = document.createElement('td');
drawCell.textContent = rowData.avgDraw;
drawCell.style.border = '1px solid #888';
row.appendChild(drawCell);
// 平均负赔
const loseCell = document.createElement('td');
loseCell.textContent = rowData.avgLose;
loseCell.style.border = '1px solid #888';
row.appendChild(loseCell);
// 时间
const timeCell = document.createElement('td');
timeCell.textContent = rowData.formattedTime;
timeCell.style.border = '1px solid #888';
row.appendChild(timeCell);
tbody.appendChild(row);
});
// 设置颜色标注
setOddsColorCoding();
}
// 设置赔率颜色标注 - 比较上下行赔率
function setOddsColorCoding() {
const rows = document.querySelectorAll('#oddsTableBody tr');
if (rows.length < 2) return;
// 从第二行开始,与上一行比较
for (let i = 1; i < rows.length; i++) {
const currentRow = rows[i];
const prevRow = rows[i - 1];
// 获取当前行和上一行的赔率单元格
const currentWin = parseFloat(currentRow.cells[1].textContent);
const currentDraw = parseFloat(currentRow.cells[2].textContent);
const currentLose = parseFloat(currentRow.cells[3].textContent);
const prevWin = parseFloat(prevRow.cells[1].textContent);
const prevDraw = parseFloat(prevRow.cells[2].textContent);
const prevLose = parseFloat(prevRow.cells[3].textContent);
// 比较并设置颜色
// 上一行相比当前行(上一行是更新的时间点)
if (prevWin > currentWin) {
prevRow.cells[1].style.backgroundColor = '#fbb'; // 红色背景表示上涨
} else if (prevWin < currentWin) {
prevRow.cells[1].style.backgroundColor = '#bfb'; // 绿色背景表示下跌
} else {
prevRow.cells[1].style.backgroundColor = '';
}
if (prevDraw > currentDraw) {
prevRow.cells[2].style.backgroundColor = '#fbb';
} else if (prevDraw < currentDraw) {
prevRow.cells[2].style.backgroundColor = '#bfb';
} else {
prevRow.cells[2].style.backgroundColor = '';
}
if (prevLose > currentLose) {
prevRow.cells[3].style.backgroundColor = '#fbb';
} else if (prevLose < currentLose) {
prevRow.cells[3].style.backgroundColor = '#bfb';
} else {
prevRow.cells[3].style.backgroundColor = '';
}
}
}
// 启动脚本
init();
})();