// ==UserScript==
// @name 新球体育网让球指数扩展
// @namespace http://dol.freevar.com/
// @version 0.3
// @description 新球体育网(球探)手机端网页,在让球指数页面里增加一个公司选择菜单,按开盘时间列出各家公司。
// @author Dolphin
// @run-at document-idle
// @match https://m.titan007.com/asian/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect txt.titan007.com
// @license MIT
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
#statsTab {border-collapse: collapse; margin:auto;}
#statsTab tr:nth-child(odd) {background-color: #eee;}
#statsTab td, #statsTab th {border: 1px solid #888; font-size: 16px; text-align: center;padding:0 5px;}
`);
// 公司数据
const companies = [
{companyId:1, name:"澳门"}, {companyId:3, name:"Crown"}, {companyId:8, name:"Bet365"},
{companyId:12, name:"易胜博"}, {companyId:14, name:"伟德"}, {companyId:17, name:"明陞"},
{companyId:23, name:"金宝博"}, {companyId:24, name:"12Bet"}, {companyId:31, name:"利记"},
{companyId:35, name:"盈禾"}, {companyId:42, name:"18Bet"}, {companyId:47, name:"平博"},
{companyId:48, name:"香港"}, {companyId:9, name:"威廉"}, {companyId:19, name:"Interw"},
{companyId:"open", name:"开盘"}
];
// 创建公司选择器
function createSelector() {
const select = document.createElement('select');
select.className = "btn";
companies.forEach(company => {
const option = document.createElement('option');
option.value = company.companyId;
option.textContent = company.name;
select.appendChild(option);
});
return select;
}
// 解析game数组
function parseGameArray(responseText) {
const regex = /var game=Array\((.*?)\);/s;
const match = responseText.match(regex);
if (!match) return [];
const content = match[1];
const elements = content.match(/"((?:\\"|[^"])*?)"/g) || [];
return elements.map(s => s.slice(1, -1).replace(/\\"/g, '"'));
}
// 解析gameDetail数组
function parseGameDetailArray(responseText) {
const regex = /var gameDetail=Array\((.*?)\);/s;
const match = responseText.match(regex);
if (!match) return [];
const content = match[1];
const elements = content.match(/"((?:\\"|[^"])*?)"/g) || [];
return elements.map(s => s.slice(1, -1).replace(/\\"/g, '"'));
}
// 创建公司映射
function createCompanyMap(games) {
const map = new Map();
for (const gameStr of games) {
const parts = gameStr.split('|');
if (parts.length < 3) continue;
const companyId = parts[1];
const companyName = parts[2];
map.set(companyId, companyName);
}
return map;
}
// 处理详细数据获取开盘时间
function processGameDetails(gameDetails, companyMap) {
const openingTimes = {};
for (const detailStr of gameDetails) {
const [companyIdPart, ...rest] = detailStr.split('^');
if (!companyIdPart || rest.length === 0) continue;
const companyId = companyIdPart;
const companyName = companyMap.get(companyId);
if (!companyName) continue;
const blocks = rest.join('^').split(';').filter(b => b.trim() !== '');
let earliestTime = null;
for (const block of blocks) {
const fields = block.split('|');
if (fields.length < 8) continue;
const timeStr = fields[3];
const year = fields[7];
const [monthDay, timePart] = timeStr.split(' ');
const [month, day] = monthDay.split('-');
const [hours, minutes] = timePart.split(':');
const date = new Date(year, month - 1, day, hours, minutes);
if (isNaN(date.getTime())) continue;
if (!earliestTime || date < earliestTime) earliestTime = date;
}
if (earliestTime) openingTimes[companyName] = earliestTime;
}
return openingTimes;
}
// 创建开盘时间表格
function createOpeningTable(openingTimes) {
const entries = Object.entries(openingTimes)
.map(([company, date]) => ({ company, date }))
.sort((a, b) => a.date - b.date);
const table = document.createElement('table');
table.id = 'statsTab';
// 表头
const header = document.createElement('tr');
['公司', '开盘时间'].forEach(text => {
const th = document.createElement('th');
th.textContent = text;
header.appendChild(th);
});
table.appendChild(header);
// 表格内容
entries.forEach(entry => {
const row = document.createElement('tr');
// 公司名称
const companyTd = document.createElement('td');
companyTd.textContent = entry.company;
row.appendChild(companyTd);
// 开盘时间
const timeTd = document.createElement('td');
const month = String(entry.date.getMonth() + 1).padStart(2, '0');
const day = String(entry.date.getDate()).padStart(2, '0');
const hours = String(entry.date.getHours()).padStart(2, '0');
const minutes = String(entry.date.getMinutes()).padStart(2, '0');
timeTd.textContent = `${month}-${day} ${hours}:${minutes}`;
row.appendChild(timeTd);
table.appendChild(row);
});
document.querySelector('div#content').prepend(table);
}
// 转换时间格式
function parseTime(timeStr) {
const year = timeStr.substr(0,4);
const month = timeStr.substr(4,2)-1;
const day = timeStr.substr(6,2);
const hour = timeStr.substr(8,2);
const minute = timeStr.substr(10,2);
const second = timeStr.substr(12,2);
return new Date(year, month, day, hour, minute, second);
}
// 时间格式化函数
function formatTime(timeStr) {
const date = parseTime(timeStr);
return `${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')} ` +
`${String(date.getHours()).padStart(2,'0')}:${String(date.getMinutes()).padStart(2,'0')}:${String(date.getSeconds()).padStart(2,'0')}`;
}
// 计算持续时间
function calculateDuration(data) {
const trends = [];
let currentTrend = null;
let startIndex = 0;
for (let i = 0; i < data.length; i++) {
const home = parseFloat(data[i].HomeOdds);
const away = parseFloat(data[i].AwayOdds);
const trend = home > away ? 'home' : home < away ? 'away' : 'equal';
if (trend !== currentTrend && currentTrend !== null) {
trends.push({
start: startIndex,
end: i-1,
trend: currentTrend
});
startIndex = i;
}
currentTrend = trend;
}
trends.push({start: startIndex, end: data.length-1, trend: currentTrend});
const durationMap = new Map();
for (const trend of trends.filter(t => t.trend !== 'equal')) {
const startTime = parseTime(data[trend.end].ModifyTime);
const endTime = parseTime(data[Math.max(trend.start - 1, 0)].ModifyTime);
const duration = Math.round((endTime - startTime)/1000);
if (duration > 0) {
// 修改为HH:mm:ss格式
const hours = Math.floor(duration/3600);
const minutes = Math.floor((duration%3600)/60);
const seconds = duration%60;
durationMap.set(trend.end,
`${String(hours)}:` +
`${String(minutes).padStart(2,'0')}:` +
`${String(seconds).padStart(2,'0')}`
);
}
}
return durationMap;
}
// 创建表格
function createTable(data) {
const durationMap = calculateDuration(data);
const table = document.createElement('table');
table.id = 'statsTab';
// 添加表头
const header = document.createElement('tr');
['主水', '让球', '客水', '更新时间', '持续时间'].forEach(text => {
const th = document.createElement('th');
th.textContent = text;
header.appendChild(th);
});
table.appendChild(header);
data.forEach((item, index) => {
const row = document.createElement('tr');
const home = parseFloat(item.HomeOdds);
const away = parseFloat(item.AwayOdds);
// 创建单元格
const createCell = (value, compare) => {
const td = document.createElement('td');
td.textContent = value;
if (compare === 'home') td.style.backgroundColor = home > away ? '#fcc' : home < away ? '#cfc' : '';
if (compare === 'away') td.style.backgroundColor = away > home ? '#fcc' : away < home ? '#cfc' : '';
return td;
};
row.appendChild(createCell(item.HomeOdds, 'home'));
row.appendChild(createCell(item.PanKou));
row.appendChild(createCell(item.AwayOdds, 'away'));
row.appendChild(createCell(formatTime(item.ModifyTime)));
const durationTd = document.createElement('td');
if (durationMap.has(index)) durationTd.textContent = durationMap.get(index);
row.appendChild(durationTd);
table.appendChild(row);
});
return table;
}
// 主逻辑
const selector = createSelector();
document.querySelector('div.btns').prepend(selector);
selector.addEventListener('change', function () {
// 删除旧表格
const oldTable = document.querySelector('#statsTab');
if (oldTable) oldTable.remove();
if (this.value === 'open') {
const url = `https://txt.titan007.com/1x2/${scheduleId}.js`;
GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: function (response) {
const games = parseGameArray(response.responseText);
const gameDetails = parseGameDetailArray(response.responseText);
const companyMap = createCompanyMap(games);
const openingTimes = processGameDetails(gameDetails, companyMap);
createOpeningTable(openingTimes);
}
});
} else {
// 请求数据
const url = `/HandicapDataInterface.ashx?scheid=${scheduleId}&type=3&oddskind=${oddskind}&companyid=${this.value}&isHalf=${isHalf}`;
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function () {
if (xhr.status === 200) {
let data = JSON.parse(xhr.responseText);
// 过滤数据
const firstEmptyIndex = data.findIndex(d => d.HappenTime === "");
const startIndex = Math.max(0, firstEmptyIndex - 2);
data = data.slice(startIndex);
// 插入新表格
const table = createTable(data);
document.querySelector('div#content').prepend(table);
}
};
xhr.send();
}
});
})();