上传文件至「api」

This commit is contained in:
2026-08-13 18:03:58 +02:00
parent 7f3d6d31ce
commit 755d1337aa
4 changed files with 2754 additions and 0 deletions
+387
View File
@@ -0,0 +1,387 @@
import sys
import urllib.parse
import re
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "两个BT影视"
def init(self, extend=""):
self.host = 'https://www.bttwo.life'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
'Referer': self.host
}
# 预加载筛选ID映射
self._filter_ids = None
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return False
def _load_filter_ids(self):
"""加载筛选ID映射"""
if self._filter_ids is not None:
return
print("[两个BT] 加载筛选ID映射...")
# 默认映射(兜底)
self._filter_ids = {
'areas': {
'全部': '', '美国': '5', '日本': '11', '韩国': '12', '中国大陆': '52',
'中国香港': '14', '中国台湾': '21', '法国': '6', '英国': '30', '加拿大': '32',
'德国': '18', '意大利': '19', '西班牙': '24', '俄罗斯': '16', '印度': '34',
'澳大利亚': '22', '泰国': '33', '其他': '78'
},
'types': {
'全部': '', '剧情': '1', '悬疑': '2', '恐怖': '3', '惊悚': '4',
'喜剧': '5', '爱情': '6', '犯罪': '9', '动作': '10', '动画': '11',
'奇幻': '12', '科幻': '14', '历史': '15', '战争': '16', '冒险': '18',
'家庭': '19', '纪录': '20', '传记': '28', '运动': '30', '武侠': '31'
},
'years': {
'全部': '', '2026': '1', '2025': '3', '2024': '4', '2023': '56',
'2022': '13', '2021': '2', '2020': '6', '2019': '8', '2018': '9',
'2010-2014': '11', '2000-2009': '12', '1990-1999': '13', '1980-1989': '14',
'1970-1979': '15', '1960-1969': '16', '1950-1959': '17', '1940-1949': '18',
'1930-1939': '19', '1920-1929': '20'
}
}
# 尝试从网站动态获取(可选)
try:
html = self.fetch(f'{self.host}/filter?classify=3', headers=self.headers).text
# 提取地区ID
area_matches = re.findall(r'href="\?classify=\d+&amp;areas=(\d+)"[^>]*>([^<]+)</a>', html)
for vid, vn in area_matches:
name = vn.strip()
if name.lower() == 'unknown':
name = '其他'
self._filter_ids['areas'][name] = vid
# 提取类型ID
type_matches = re.findall(r'href="\?classify=\d+&amp;(?:areas=\d+&amp;)?types=(\d+)"[^>]*>([^<]+)</a>', html)
for vid, vn in type_matches:
name = vn.strip()
self._filter_ids['types'][name] = vid
# 提取年份ID
year_matches = re.findall(r'href="\?classify=\d+&amp;(?:areas=\d+&amp;)?years=(\d+)"[^>]*>([^<]+)</a>', html)
for vid, vn in year_matches:
name = vn.strip()
self._filter_ids['years'][name] = vid
print(f"[两个BT] 筛选ID加载完成: 地区{len(self._filter_ids['areas'])}, 类型{len(self._filter_ids['types'])}, 年份{len(self._filter_ids['years'])}")
except Exception as e:
print(f"[两个BT] 动态加载筛选ID失败,使用默认映射: {e}")
def homeContent(self, filter):
self._load_filter_ids()
result = {}
result['class'] = [
{'type_id': '1', 'type_name': '电影'},
{'type_id': '2', 'type_name': '电视剧'},
{'type_id': '3', 'type_name': '动漫'}
]
result['filters'] = self._get_filters()
return result
def homeVideoContent(self):
try:
rsp = self.fetch(self.host, headers=self.headers)
doc = self.html(rsp.text)
return {'list': self._get_videos(doc)}
except Exception as e:
print(f'homeVideoContent 错误: {e}')
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
try:
self._load_filter_ids()
# 构建筛选参数(将中文转换为数字ID)
params = []
# classify参数:1=电影, 2=电视剧, 3=动漫
classify_map = {'1': '1', '2': '2', '3': '3'}
classify = classify_map.get(tid, '1')
params.append(f"classify={classify}")
# 地区筛选
if extend.get('area'):
area_name = extend['area']
area_id = self._filter_ids['areas'].get(area_name, '')
if area_id:
params.append(f"areas={area_id}")
# 类型筛选
if extend.get('type'):
type_name = extend['type']
type_id = self._filter_ids['types'].get(type_name, '')
if type_id:
params.append(f"types={type_id}")
# 年份筛选
if extend.get('year'):
year_name = extend['year']
year_id = self._filter_ids['years'].get(year_name, '')
if year_id:
params.append(f"years={year_id}")
# 分页参数
if str(pg) != '1':
params.append(f"page={pg}")
# 构建URL
url = f"{self.host}/filter"
if params:
url += "?" + "&".join(params)
print(f'[两个BT] 分类请求: {url}')
rsp = self.fetch(url, headers=self.headers)
doc = self.html(rsp.text)
return {
'list': self._get_videos(doc),
'page': int(pg),
'pagecount': 9999,
'limit': 20
}
except Exception as e:
print(f'categoryContent 错误: {e}')
return {'list': []}
def detailContent(self, ids):
try:
vid = ids[0]
detail_url = f"{self.host}{vid}" if str(vid).startswith('/') else f"{self.host}/play/{vid}"
rsp = self.fetch(detail_url, headers=self.headers)
doc = self.html(rsp.text)
# 修复标题提取逻辑 - 按优先级尝试不同的选择器
title = "未知"
# 1. 首先尝试h1标题(最常见)
title_nodes = doc.xpath('//h1[contains(@class,"text-lg")]/text()')
if title_nodes and title_nodes[0].strip():
title = title_nodes[0].strip()
else:
# 2. 尝试h2标题
title_nodes = doc.xpath('//h2[contains(@class,"text-xl")]/text()')
if title_nodes and title_nodes[0].strip():
title = title_nodes[0].strip()
else:
# 3. 尝试title标签
title_nodes = doc.xpath('//title/text()')
if title_nodes and title_nodes[0].strip():
title = title_nodes[0].strip()
# 清理title中的网站后缀
title = re.sub(r'\s*-\s*两个BT.*$', '', title)
title = re.sub(r'\s*\|\s*两个BT.*$', '', title)
else:
# 4. 尝试meta标签
meta_nodes = doc.xpath('//meta[@property="og:title"]/@content')
if meta_nodes and meta_nodes[0].strip():
title = meta_nodes[0].strip()
# 最终清理标题
title = title.strip()
# 移除多余的空格和特殊字符
title = re.sub(r'\s+', ' ', title)
# 移除常见的网站后缀
title = re.sub(r'\s*-\s*两个BT.*$', '', title)
title = re.sub(r'\s*\|\s*两个BT.*$', '', title)
title = re.sub(r'\s*-\s*TwoBT.*$', '', title)
title = re.sub(r'\s*\|\s*TwoBT.*$', '', title)
print(f'[两个BT] 提取标题: {title}')
img_nodes = doc.xpath('//div[contains(@class,"movie-poster")]//img/@src | //div[contains(@class,"movie-poster")]//img/@data-src | //meta[@property="og:image"]/@content')
pic = ""
for img in img_nodes:
if "placeholder" not in img.lower():
pic = img
break
if not pic and img_nodes:
pic = img_nodes[0]
remarks_nodes = doc.xpath('//span[contains(text(),"") and contains(text(),"")]/text()')
remarks = remarks_nodes[0].strip() if remarks_nodes else ""
director_nodes = doc.xpath('//div[text()="导演"]/following-sibling::div[1]/text()')
director = director_nodes[0].strip() if director_nodes else ""
actor_nodes = doc.xpath('//div[text()="主演"]/following-sibling::div[1]/text()')
actor = actor_nodes[0].strip() if actor_nodes else ""
content_nodes = doc.xpath('//h3[contains(text(),"剧情简介")]/parent::div/p/text()')
content = content_nodes[0].strip() if content_nodes else ""
episodes = []
# 尝试缩小选择范围,优先寻找播放列表区块
list_nodes = doc.xpath('//div[contains(@class, "episode")] | //div[contains(@class, "playlist")] | //div[contains(@class, "video-list")]')
if list_nodes:
links = list_nodes[0].xpath('.//a[contains(@href, "/play/")] | .//a[contains(@class, "episode-link")]')
else:
links = doc.xpath('//a[contains(@class, "episode-link")] | //a[contains(@href, "/play/")]')
seen_hrefs = set()
# 提取当前视频的核心ID,用于二次校验,阻断无关剧集的链接
core_id = "".join([c for c in str(vid) if c.isdigit()])
if not core_id:
core_id = str(vid).split('/')[-1].split('-')[0].split('.')[0]
for link in links:
href = link.xpath('./@href')[0]
if href in seen_hrefs or not href.startswith('/play/'):
continue
# 若提取到了核心ID且链接中不包含该ID,跳过(防止抓到推荐视频)
if core_id and core_id not in href:
continue
seen_hrefs.add(href)
# 直接提取标签内所有文本
name_list = link.xpath('.//text()')
name = "".join([n.strip() for n in name_list if n.strip()])
name = " ".join(name.split())
if not name:
name = link.xpath('./@data-episode')[0] if link.xpath('./@data-episode') else f"{len(episodes)+1}"
episodes.append(f"{name}${href}")
return {
'list': [{
'vod_id': vid,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remarks,
'vod_director': director,
'vod_actor': actor,
'vod_content': content,
'vod_play_from': '两个BT',
'vod_play_url': '#'.join(episodes) if episodes else f"正片${detail_url.replace(self.host, '')}"
}]
}
except Exception as e:
print(f'detailContent 错误: {e}')
return {'list': []}
def searchContent(self, key, quick, pg="1"):
try:
url = f"{self.host}/search?q={urllib.parse.quote(key)}"
if pg != "1":
url += f"&page={pg}"
rsp = self.fetch(url, headers=self.headers)
doc = self.html(rsp.text)
return {
'list': self._get_search_videos(doc),
'page': int(pg),
'pagecount': 9999,
'limit': 20
}
except Exception as e:
print(f'searchContent 错误: {e}')
return {'list': []}
def playerContent(self, flag, id, vipFlags):
# WASM 播放器直连
play_url = id if id.startswith('http') else self.host + id
return {'parse': 1, 'url': play_url, 'header': self.headers}
def localProxy(self, param):
return [200, "video/MP2T", ""]
def _get_videos(self, doc):
videos = []
nodes = doc.xpath('//div[@data-vod-id] | //a[contains(@href, "/play/")]/ancestor::div[contains(@class, "group")][1]')
seen_ids = set()
for node in nodes:
v_id = ""
if node.xpath('./@data-vod-id'):
v_id = f"/play/{node.xpath('./@data-vod-id')[0]}"
else:
hrefs = node.xpath('.//a[contains(@href, "/play/")]/@href')
if hrefs:
v_id = hrefs[0]
if not v_id or v_id in seen_ids:
continue
seen_ids.add(v_id)
# 修复列表页标题提取
name = "未知"
# 1. 尝试h3标签
name_nodes = node.xpath('.//h3/text()')
if name_nodes and name_nodes[0].strip():
name = name_nodes[0].strip()
else:
# 2. 尝试a标签的title属性
name_nodes = node.xpath('.//a[contains(@href, "/play/")]/@title')
if name_nodes and name_nodes[0].strip():
name = name_nodes[0].strip()
else:
# 3. 尝试img的alt属性
name_nodes = node.xpath('.//img/@alt')
if name_nodes and name_nodes[0].strip():
name = name_nodes[0].strip()
# 清理名称
name = re.sub(r'\s+', ' ', name).strip()
img_nodes = node.xpath('.//img/@src | .//img/@data-src | .//img/@data-original')
v_pic = ""
for img in img_nodes:
if "placeholder" not in img.lower():
v_pic = img
break
if not v_pic and img_nodes:
v_pic = img_nodes[0]
remarks_nodes = node.xpath('.//span[contains(@class,"text-text-secondary")]/text() | .//span[contains(text(),"更新")]/text() | .//span[contains(@class,"bg-gradient-to-r")]/text() | .//div[contains(@class,"text-green-500")]/text()')
v_remarks = remarks_nodes[0].strip().replace('更新', '') if remarks_nodes else ""
videos.append({
'vod_id': v_id,
'vod_name': name,
'vod_pic': v_pic,
'vod_remarks': v_remarks
})
return videos
def _get_search_videos(self, doc):
return self._get_videos(doc)
def _get_filters(self):
"""生成筛选器配置(使用中文名称,但实际会使用数字ID)"""
# 从映射中提取选项
areas = [{'n': k, 'v': k} for k in self._filter_ids['areas'].keys()]
types = [{'n': k, 'v': k} for k in self._filter_ids['types'].keys()]
years = [{'n': k, 'v': k} for k in self._filter_ids['years'].keys()]
# 排序:全部放在第一位
areas.sort(key=lambda x: (x['v'] != '', x['n']))
types.sort(key=lambda x: (x['v'] != '', x['n']))
years.sort(key=lambda x: (x['v'] != '', x['n']))
base = [
{'key': 'area', 'name': '地区', 'value': areas},
{'key': 'type', 'name': '类型', 'value': types},
{'key': 'year', 'name': '年份', 'value': years}
]
return {'1': base, '2': base, '3': base}
+1659
View File
@@ -0,0 +1,1659 @@
/*
@header({
searchable: 1,
filterable: 0,
quickSearch: 0,
title: '⚽ 体育cat|聚合',
author: 'OpenClaw',
lang: 'cat',
style: { type: 'rect', ratio: 0.75 }
})
*/
const __sports_src_0 = (function () {
let host = 'https://www.88kanqiu.la';
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36';
const headers = {
'User-Agent': UA,
'Referer': host + '/',
'Accept': 'text/html,application/json,*/*'
};
const B64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function stripHtml(s) {
return String(s || '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/\s+/g, ' ')
.trim();
}
function absUrl(url) {
url = String(url || '').trim();
if (!url) return '';
if (/^https?:\/\//i.test(url)) return url;
if (url.indexOf('//') === 0) return 'https:' + url;
if (url.charAt(0) === '/') return host + url;
return host + '/' + url;
}
function safeJson(text, def) {
try { return JSON.parse(text || '{}'); } catch (e) { return def || {}; }
}
// TV 端 JS 环境不一定有 atob/TextDecoder;这里按 PHP 源的 base64_decode 逻辑手写 UTF-8 解码。
function base64ToUtf8(str) {
str = String(str || '').replace(/[^A-Za-z0-9+/=]/g, '');
const bytes = [];
for (let i = 0; i < str.length; i += 4) {
const c1 = B64_CHARS.indexOf(str.charAt(i));
const c2 = B64_CHARS.indexOf(str.charAt(i + 1));
const c3 = B64_CHARS.indexOf(str.charAt(i + 2));
const c4 = B64_CHARS.indexOf(str.charAt(i + 3));
if (c1 < 0 || c2 < 0) continue;
const n = (c1 << 18) | (c2 << 12) | ((c3 < 0 ? 0 : c3) << 6) | (c4 < 0 ? 0 : c4);
bytes.push((n >> 16) & 255);
if (str.charAt(i + 2) !== '=') bytes.push((n >> 8) & 255);
if (str.charAt(i + 3) !== '=') bytes.push(n & 255);
}
let out = '';
for (let i = 0; i < bytes.length;) {
const b1 = bytes[i++];
if (b1 < 0x80) {
out += String.fromCharCode(b1);
} else if (b1 >= 0xc0 && b1 < 0xe0) {
const b2 = bytes[i++] || 0;
out += String.fromCharCode(((b1 & 0x1f) << 6) | (b2 & 0x3f));
} else if (b1 >= 0xe0 && b1 < 0xf0) {
const b2 = bytes[i++] || 0;
const b3 = bytes[i++] || 0;
out += String.fromCharCode(((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f));
} else {
const b2 = bytes[i++] || 0;
const b3 = bytes[i++] || 0;
const b4 = bytes[i++] || 0;
const cp = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f);
const u = cp - 0x10000;
out += String.fromCharCode(0xd800 + (u >> 10), 0xdc00 + (u & 0x3ff));
}
}
return out;
}
function decodePlaySource(raw) {
raw = String(raw || '').trim();
if (!raw) return null;
for (let prefix = 0; prefix <= 8; prefix++) {
for (let suffix = 0; suffix <= 4; suffix++) {
if (raw.length <= prefix + suffix) continue;
const candidate = raw.slice(prefix, raw.length - suffix);
try {
const decoded = base64ToUtf8(candidate);
const pos = decoded.indexOf('{');
if (pos < 0) continue;
const obj = JSON.parse(decoded.slice(pos));
if (obj && typeof obj === 'object') return obj;
} catch (e) {}
}
}
return null;
}
function toDirectPlayUrl(url) {
url = String(url || '').trim();
if (!url) return '';
const m = url.match(/[?&]url=([^&]+)/);
if (m && m[1]) {
try { return decodeURIComponent(m[1]); } catch (e) { return m[1]; }
}
return url;
}
async function fetchText(url, referer) {
const hd = {
'User-Agent': UA,
'Referer': referer || host + '/',
'Accept': 'text/html,application/json,*/*'
};
// 部分壳只有 Java.req,部分壳只有 req;两种都兼容。
if (typeof Java !== 'undefined' && Java && Java.req) {
const r = Java.req(url, { headers: hd });
return String((r && (r.body || r.content)) || '');
}
const r2 = await req(url, { headers: hd });
return String((r2 && (r2.content || r2.body)) || '');
}
function getClasses() {
return [
{ type_id: 'live', type_name: '正在直播' },
{ type_id: '0', type_name: '全部直播' },
{ type_id: '1', type_name: '篮球直播' },
{ type_id: '8', type_name: '足球直播' },
{ type_id: '21', type_name: '其他直播' }
];
}
function parseList(html, onlyLiving) {
const list = [];
const reg = /<li[^>]*class=["'][^"']*list-group-item[^"']*["'][^>]*>[\s\S]*?<\/li>/gi;
let m;
while ((m = reg.exec(String(html || ''))) !== null) {
const item = m[0];
const hrefMatch = item.match(/<a[^>]+href=["']([^"']*\/live\/\d+\/play[^"']*)["'][^>]*>/i);
if (!hrefMatch) continue;
const time = stripHtml((item.match(/class=["'][^"']*category-game-time[^"']*["'][^>]*>([\s\S]*?)<\//i) || [])[1]);
const gameType = stripHtml((item.match(/class=["'][^"']*game-type[^"']*["'][^>]*>([\s\S]*?)<\//i) || [])[1]);
const teams = [];
const teamReg = /class=["'][^"']*team-name[^"']*["'][^>]*>([\s\S]*?)<\//gi;
let tm;
while ((tm = teamReg.exec(item)) !== null) teams.push(stripHtml(tm[1]));
let title = [time, gameType, teams.length >= 2 ? teams[0] + ' vs ' + teams[1] : teams.join(' vs ')].filter(Boolean).join(' ').trim();
if (!title || title === 'vs') title = stripHtml(item).replace(/直播中|观看|高清|视频直播/g, '').trim();
if (!title) continue;
const btnTextMatch = item.match(/<a[^>]+href=["'][^"']*\/live\/\d+\/play[^"']*["'][^>]*>([\s\S]*?)<\/a>/i);
const remark = stripHtml(btnTextMatch ? btnTextMatch[1] : '') || '直播中';
if (onlyLiving && remark !== '直播中') continue;
const picMatch = item.match(/<img[^>]+(?:data-src|src)=["']([^"']+)["']/i);
list.push({
vod_id: absUrl(hrefMatch[1]) + '###' + encodeURIComponent(title),
vod_name: title,
vod_pic: absUrl(picMatch ? picMatch[1] : '/static/img/default.png'),
vod_remarks: remark
});
}
return list;
}
async function fetchGroupList(group) {
let paths = ['/'];
if (group === 'basketball') {
paths = ['/match/1/live', '/match/2/live', '/match/20/live', '/match/4/live'];
} else if (group === 'football') {
paths = ['/match/3/live', '/match/8/live', '/match/9/live', '/match/10/live', '/match/14/live', '/match/15/live', '/match/12/live', '/match/13/live', '/match/7/live', '/match/11/live', '/match/27/live', '/match/26/live', '/match/31/live', '/match/23/live'];
} else if (group === 'other') {
paths = ['/match/21/live', '/match/29/live', '/match/25/live', '/match/19/live', '/match/38/live'];
}
const all = [];
const seen = {};
for (let i = 0; i < paths.length; i++) {
try {
const html = await fetchText(host + paths[i], host + '/');
const items = parseList(html);
for (let j = 0; j < items.length; j++) {
const key = items[j].vod_id;
if (seen[key]) continue;
seen[key] = true;
all.push(items[j]);
}
} catch (e) {}
}
return all;
}
async function init(cfg) {
if (cfg && cfg.ext && String(cfg.ext).indexOf('http') === 0) host = String(cfg.ext).trim().replace(/\/$/, '');
}
async function home(filter) {
return JSON.stringify({ class: getClasses(), filters: {} });
}
async function homeVod() {
return await category('0', 1, false, {});
}
async function category(tid, pg, filter, extend) {
tid = String((extend && extend.cateId) || tid || '0');
pg = parseInt(pg) || 1;
let group = '';
if (tid === '1' || tid === 'basketball') group = 'basketball';
else if (tid === '8' || tid === 'football') group = 'football';
else if (tid === '21' || tid === 'other') group = 'other';
let list = [];
try {
const onlyLiving = tid === 'live';
list = group ? await fetchGroupList(group) : parseList(await fetchText(host + '/', host + '/'), onlyLiving);
} catch (e) {
list = [];
}
return JSON.stringify({ code: 1, msg: '数据列表', page: pg, pagecount: 1, limit: 20, total: list.length, list });
}
async function detail(id) {
id = Array.isArray(id) ? id[0] : id;
let realId = String(id || '');
let displayName = '赛事直播';
if (realId.indexOf('###') >= 0) {
const parts = realId.split('###');
realId = parts[0];
try { displayName = decodeURIComponent(parts[1] || '赛事直播'); } catch (e) { displayName = parts[1] || '赛事直播'; }
}
const match = realId.match(/\/live\/(\d+)\/play/);
const gameId = match ? match[1] : '';
if (!gameId) return JSON.stringify({ code: 1, page: 1, pagecount: 1, limit: 0, total: 0, list: [] });
let playUrls = '';
try {
const content = await fetchText(host + '/live/' + gameId + '/source', realId);
const json = safeJson(content, {});
const data = decodePlaySource(json.data || '');
const links = (data && data.links) || [];
const arr = [];
for (let i = 0; i < links.length; i++) {
const u = toDirectPlayUrl(links[i].url || '');
if (u) arr.push((links[i].name || ('线路' + (i + 1))) + '$' + u);
}
playUrls = arr.join('#');
} catch (e) {
playUrls = '';
}
return JSON.stringify({
code: 1,
msg: '数据列表',
page: 1,
pagecount: 1,
limit: 1,
total: 1,
list: [{
vod_id: realId,
vod_name: displayName,
vod_pic: host + '/static/img/default.png',
vod_remarks: '直播中',
vod_play_from: '88看球',
vod_play_url: playUrls,
vod_content: '实时体育直播'
}]
});
}
async function search(wd, quick, pg) {
return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: 20, total: 0, list: [] });
}
async function play(flag, id, flags) {
const isDirect = /\.(m3u8|mp4)(\?|$)/i.test(String(id || ''));
return JSON.stringify({ parse: isDirect ? 0 : 1, url: id, header: headers });
}
// OK影视/部分 CAT 壳兼容入口。
async function homeContent(filter) { return safeJson(await home(filter), { class: [], filters: {} }); }
async function homeVideoContent() { return safeJson(await homeVod(), { list: [] }); }
async function categoryContent(tid, pg, filter, extend) { return safeJson(await category(tid, pg, filter, extend || {}), { list: [] }); }
async function detailContent(ids) { return safeJson(await detail(ids), { list: [] }); }
async function searchContent(wd, quick, pg) { return safeJson(await search(wd, quick, pg || 1), { list: [] }); }
async function playerContent(flag, id, flags) { return safeJson(await play(flag, id, flags), { parse: 1, url: id }); }
return { init, home, homeVod, category, search, detail, play, homeContent, homeVideoContent, categoryContent, detailContent, searchContent, playerContent };
})();
const __sports_src_1 = (function () {
let host = 'https://kafeizhibo.cc';
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36';
const headers = {
'User-Agent': UA,
'Referer': host + '/pc',
'Accept': 'application/json, text/plain, */*'
};
function safeJson(text, def) {
try { return JSON.parse(text || '{}'); } catch (e) { return def || {}; }
}
function absUrl(url) {
url = String(url || '').trim();
if (!url) return '';
if (/^https?:\/\//i.test(url)) return url;
if (url.indexOf('//') === 0) return 'https:' + url;
if (url.charAt(0) === '/') return host + url;
return host + '/' + url;
}
function clean(s) {
return String(s || '').replace(/<[^>]+>/g, '').replace(/\s+/g, ' ').trim();
}
async function fetchJson(url) {
const r = await req(url, { headers });
return safeJson((r && (r.content || r.body)) || '{}', {});
}
function getClasses() {
return [
{ type_id: 'all', type_name: '全部直播' },
{ type_id: 'hot', type_name: '热门直播' },
{ type_id: 'nba', type_name: 'NBA' },
{ type_id: '1', type_name: '足球直播' },
{ type_id: '2', type_name: '篮球直播' },
{ type_id: '3', type_name: '网球直播' },
{ type_id: '19', type_name: '台球直播' },
{ type_id: 'schedule', type_name: '赛程列表' },
{ type_id: 'recordings', type_name: '录像' }
];
}
function titleOf(it) {
const mi = it.match_info || {};
const league = it.league_name || mi.league_name || it.league || '';
const home = it.home_team || mi.home_team || (it.homeTeam && it.homeTeam.name) || '';
const away = it.away_team || mi.away_team || (it.awayTeam && it.awayTeam.name) || '';
const title = it.title || it.name || '';
if (title && title !== it.name) return clean(title);
if (league && home && away) return clean(league + ' ' + home + ' vs ' + away);
return clean(title || it.name || ('直播间 ' + (it.room_id || it.id || '')));
}
function remarkOf(it) {
const parts = [];
const status = it.status || (it.match_info && it.match_info.status) || '';
if (status === 'live' || it.is_live) parts.push('直播中');
else if (status === 'online') parts.push('在线');
else if (status === 'upcoming') parts.push('未开赛');
else if (status) parts.push(status);
const score = (it.home_score !== undefined && it.away_score !== undefined) ? (it.home_score + '-' + it.away_score) : '';
if (score && score !== '0-0') parts.push(score);
if (it.heat) parts.push('热度:' + it.heat);
if (it.name && it.title && it.name !== it.title) parts.push(it.name);
return clean(parts.join(' ')) || '直播';
}
function picOf(it) {
return absUrl(it.screenshot || it.avatar || it.home_team_logo || it.away_team_logo || (it.homeTeam && it.homeTeam.logo) || (it.awayTeam && it.awayTeam.logo) || '/images/logo.png');
}
function itemToVod(it) {
const roomId = it.room_id || (it.archor && it.archor.room_id) || (Array.isArray(it.archors) && it.archors[0] && it.archors[0].room_id) || '';
if (!roomId) return null;
return {
vod_id: String(roomId) + '###' + encodeURIComponent(titleOf(it)),
vod_name: titleOf(it),
vod_pic: picOf(it),
vod_remarks: remarkOf(it)
};
}
function recordingToVod(it) {
const matchId = it.match_id || it.id || '';
if (!matchId) return null;
const name = titleOf(it);
return {
vod_id: 'rec$' + String(matchId) + '###' + encodeURIComponent(name),
vod_name: name,
vod_pic: absUrl(it.cover_image || it.screenshot || it.home_team_logo || it.away_team_logo || '/images/logo.png'),
vod_remarks: clean([it.start_time || '', it.recording_count ? ('录像:' + it.recording_count) : '录像'].filter(Boolean).join(' '))
};
}
async function init(cfg) {
if (cfg && cfg.ext && String(cfg.ext).indexOf('http') === 0) host = String(cfg.ext).trim().replace(/\/$/, '');
}
async function home(filter) {
return JSON.stringify({ class: getClasses(), filters: {} });
}
async function homeVod() {
return await category('all', 1, false, {});
}
async function category(tid, pg, filter, extend) {
tid = String((extend && extend.cateId) || tid || 'all');
pg = parseInt(pg) || 1;
const size = 30;
let apiUrl = '';
if (tid === 'schedule') {
apiUrl = host + '/api/v1/schedule?type=all&page=' + pg + '&size=' + size + '&_t=' + Date.now();
} else if (tid === 'recordings') {
apiUrl = host + '/api/v1/recordings?page=' + pg + '&size=' + size + '&_t=' + Date.now();
} else if (tid === 'nba') {
// 官网没有单独 nba 参数;用篮球赛程聚合后按 NBA 关键字过滤,有 NBA 时显示 NBA,无 NBA 时为空不混入其他篮球。
apiUrl = host + '/api/v1/schedule?type=2&page=' + pg + '&size=100&_t=' + Date.now();
} else {
const type = tid === 'all' ? '' : tid;
apiUrl = host + '/api/v1/archor?type=' + encodeURIComponent(type) + '&_t=' + Date.now();
}
let list = [];
let total = 0;
try {
const json = await fetchJson(apiUrl);
const data = Array.isArray(json.data) ? json.data : [];
const seen = {};
for (let i = 0; i < data.length; i++) {
if (tid === 'recordings') {
const vod = recordingToVod(data[i]);
if (vod && !seen[vod.vod_id]) { seen[vod.vod_id] = true; list.push(vod); }
} else if (tid === 'nba') {
const title = titleOf(data[i]);
if (!/NBA|美职篮|美国职业篮球/i.test(title)) continue;
if (Array.isArray(data[i].archors) && data[i].archors.length) {
for (let j = 0; j < data[i].archors.length; j++) {
const merged = Object.assign({}, data[i], data[i].archors[j], {
title,
screenshot: data[i].screenshot || data[i].archors[j].screenshot
});
const vod = itemToVod(merged);
if (vod && !seen[vod.vod_id]) { seen[vod.vod_id] = true; list.push(vod); }
}
} else {
const vod = itemToVod(data[i]);
if (vod && !seen[vod.vod_id]) { seen[vod.vod_id] = true; list.push(vod); }
}
} else if (tid === 'schedule' && Array.isArray(data[i].archors) && data[i].archors.length) {
for (let j = 0; j < data[i].archors.length; j++) {
const merged = Object.assign({}, data[i], data[i].archors[j], {
title: titleOf(data[i]),
screenshot: data[i].screenshot || data[i].archors[j].screenshot
});
const vod = itemToVod(merged);
if (vod && !seen[vod.vod_id]) { seen[vod.vod_id] = true; list.push(vod); }
}
} else {
const vod = itemToVod(data[i]);
if (vod && !seen[vod.vod_id]) { seen[vod.vod_id] = true; list.push(vod); }
}
}
total = tid === 'nba' ? list.length : (json.total || list.length);
} catch (e) {
list = [];
}
return JSON.stringify({ code: 1, msg: '数据列表', page: pg, pagecount: 1, limit: size, total, list });
}
async function detail(id) {
id = Array.isArray(id) ? id[0] : id;
let roomId = String(id || '');
let displayName = '咖啡直播';
if (roomId.indexOf('###') >= 0) {
const parts = roomId.split('###');
roomId = parts[0];
try { displayName = decodeURIComponent(parts[1] || displayName); } catch (e) { displayName = parts[1] || displayName; }
}
if (roomId.indexOf('rec$') === 0) {
const matchId = roomId.slice(4);
let vod = {
vod_id: roomId,
vod_name: displayName,
vod_pic: host + '/images/logo.png',
vod_remarks: '录像',
vod_play_from: '咖啡录像',
vod_play_url: '',
vod_content: '咖啡直播赛事录像'
};
try {
const json = await fetchJson(host + '/api/v1/match/' + encodeURIComponent(matchId) + '/recordings?_t=' + Date.now());
const data = json.data || {};
const match = data.match || {};
const urls = [];
const replays = Array.isArray(data.replays) ? data.replays : [];
const highlights = Array.isArray(data.highlights) ? data.highlights : [];
for (let i = 0; i < replays.length; i++) {
if (replays[i].video_url) urls.push(clean(replays[i].title || ('录像' + (i + 1))) + '$' + replays[i].video_url);
}
for (let i = 0; i < highlights.length; i++) {
if (highlights[i].video_url) urls.push(clean(highlights[i].title || ('集锦' + (i + 1))) + '$' + highlights[i].video_url);
}
vod = {
vod_id: roomId,
vod_name: titleOf(match) || displayName,
vod_pic: absUrl((replays[0] && replays[0].cover_image) || match.cover_image || match.home_team_logo || match.away_team_logo || '/images/logo.png'),
vod_remarks: clean([match.start_time || '', match.home_score !== undefined ? (match.home_score + '-' + match.away_score) : ''].filter(Boolean).join(' ')) || '录像',
vod_play_from: '咖啡录像',
vod_play_url: urls.join('#'),
vod_content: '咖啡直播赛事录像'
};
} catch (e) {}
return JSON.stringify({ code: 1, msg: '数据列表', page: 1, pagecount: 1, limit: 1, total: 1, list: [vod] });
}
if (!roomId) return JSON.stringify({ code: 1, page: 1, pagecount: 1, limit: 0, total: 0, list: [] });
let vod = {
vod_id: roomId,
vod_name: displayName,
vod_pic: host + '/images/logo.png',
vod_remarks: '直播',
vod_play_from: '咖啡直播',
vod_play_url: '',
vod_content: '咖啡直播实时体育直播'
};
try {
const json = await fetchJson(host + '/api/v1/room/' + encodeURIComponent(roomId) + '?_t=' + Date.now());
const data = json.data || {};
const room = data.room_info || {};
const archor = data.archor || {};
const signals = Array.isArray(data.signals) ? data.signals : [];
const urls = [];
const seen = {};
function addLine(name, url) {
url = String(url || '').trim();
if (!url || seen[url]) return;
seen[url] = true;
urls.push(clean(name || ('线路' + (urls.length + 1))) + '$' + url);
}
for (let i = 0; i < signals.length; i++) addLine(signals[i].name, signals[i].stream_url);
addLine(archor.name, archor.stream_url);
const title = room.title || displayName;
vod = {
vod_id: roomId,
vod_name: clean(title),
vod_pic: absUrl(archor.screenshot || room.avatar || archor.avatar || '/images/logo.png'),
vod_remarks: remarkOf(Object.assign({}, room, archor)),
vod_play_from: '咖啡直播',
vod_play_url: urls.join('#'),
vod_content: clean(room.notice || room.notice_h5 || '咖啡直播实时体育直播')
};
} catch (e) {}
return JSON.stringify({ code: 1, msg: '数据列表', page: 1, pagecount: 1, limit: 1, total: 1, list: [vod] });
}
async function search(wd, quick, pg) {
return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: 20, total: 0, list: [] });
}
async function play(flag, id, flags) {
return JSON.stringify({ parse: 0, url: id, header: headers });
}
async function homeContent(filter) { return safeJson(await home(filter), { class: [], filters: {} }); }
async function homeVideoContent() { return safeJson(await homeVod(), { list: [] }); }
async function categoryContent(tid, pg, filter, extend) { return safeJson(await category(tid, pg, filter, extend || {}), { list: [] }); }
async function detailContent(ids) { return safeJson(await detail(ids), { list: [] }); }
async function searchContent(wd, quick, pg) { return safeJson(await search(wd, quick, pg || 1), { list: [] }); }
async function playerContent(flag, id, flags) { return safeJson(await play(flag, id, flags), { parse: 0, url: id }); }
return { init, home, homeVod, category, search, detail, play, homeContent, homeVideoContent, categoryContent, detailContent, searchContent, playerContent };
})();
const __sports_src_2 = (function () {
const API_BASE = 'https://01cs01.fusk39cd.com/api/web/live_lists';
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
const headers = {
'User-Agent': UA,
'Accept': 'application/json, text/plain, */*',
'Referer': 'https://01cs01.fusk39cd.com/'
};
const PAGE_SIZE = 20;
function safeString(v) {
if (v === null || v === undefined || v === 'null') return '';
return String(v);
}
function safeJson(text, def) {
if (text && typeof text === 'object') return text;
try { return JSON.parse(text || '{}'); } catch (e) { return def || {}; }
}
async function fetchJson(url) {
if (typeof Java !== 'undefined' && Java && Java.req) {
const r = Java.req(url, { headers });
return safeJson((r && (r.body || r.content)) || '{}', {});
}
const r2 = await req(url, { headers });
return safeJson((r2 && (r2.content || r2.body)) || '{}', {});
}
function getClasses() {
return [
{ type_id: '1', type_name: '全部' },
{ type_id: '2', type_name: '足球' },
{ type_id: '3', type_name: '篮球' }
];
}
async function init(cfg) {}
async function home(filter) {
return JSON.stringify({ class: getClasses(), filters: {} });
}
async function homeVod() {
return await category('1', 1, false, {});
}
async function category(tid, pg, filter, extend) {
const page = Math.max(parseInt(pg || '1', 10) || 1, 1);
const typeId = safeString(tid || '1') || '1';
const list = [];
try {
const url = API_BASE + '/' + encodeURIComponent(typeId);
const json = await fetchJson(url);
const rows = json && json.code === 200 && json.data && Array.isArray(json.data.data) ? json.data.data : [];
for (let i = 0; i < rows.length; i++) {
const item = rows[i];
if (!item.tournament_id) continue;
const type = safeString(item.type || typeId);
const tournamentId = safeString(item.tournament_id);
const memberId = safeString(item.member_id);
const homeTeam = safeString(item.home_team_zh);
const awayTeam = safeString(item.away_team_zh);
const name = [homeTeam, awayTeam].filter(Boolean).join(' VS ') || safeString(item.league_name_zh) || tournamentId;
list.push({
vod_id: type + '|' + tournamentId + '|' + memberId,
vod_name: name,
vod_pic: safeString(item.cover),
vod_remarks: safeString(item.league_name_zh) || safeString(item.match_time) || safeString(item.status)
});
}
} catch (e) {
return JSON.stringify({ code: 1, page, pagecount: 1, limit: PAGE_SIZE, total: 0, list: [] });
}
return JSON.stringify({
code: 1,
msg: '数据列表',
page,
pagecount: list.length >= PAGE_SIZE ? page + 1 : page,
limit: PAGE_SIZE,
total: list.length,
list
});
}
async function detail(id) {
const idList = Array.isArray(id) ? id : String(id || '').split(',').filter(Boolean);
const list = [];
for (let n = 0; n < idList.length; n++) {
const vid = safeString(idList[n]).trim();
const parts = vid.split('|');
if (parts.length !== 3) continue;
const type = parts[0];
const tournamentId = parts[1];
const memberId = parts[2];
try {
const url = API_BASE + '/' + encodeURIComponent(type) + '/detail/' + encodeURIComponent(tournamentId) + '?member_id=' + encodeURIComponent(memberId);
const json = await fetchJson(url);
if (!json || json.code !== 200) continue;
const detailObj = json.data && json.data.detail ? json.data.detail : {};
const moreArr = json.data && Array.isArray(json.data.more) ? json.data.more : [];
const homeTeam = safeString(detailObj.home_team_zh);
const awayTeam = safeString(detailObj.away_team_zh);
const vodName = [homeTeam, awayTeam].filter(Boolean).join(' VS ') || safeString(detailObj.league_name_zh) || vid;
const playFromList = [];
const playUrlList = [];
for (let i = 0; i < moreArr.length; i++) {
const source = moreArr[i];
const username = safeString(source.username) || ('线路' + (i + 1));
const urls = [];
const flv = safeString(source.screen_url);
const m3u8 = safeString(source.screen_url_m3u8);
if (flv) urls.push('线路一$' + flv);
if (m3u8) urls.push('线路二$' + m3u8);
if (!urls.length) continue;
playFromList.push(username.replace(/\$|#/g, ''));
playUrlList.push(urls.join('#'));
}
list.push({
vod_id: vid,
vod_name: vodName,
vod_pic: safeString(detailObj.cover),
type_name: safeString(detailObj.league_name_zh),
vod_remarks: safeString(detailObj.match_time) || safeString(detailObj.status),
vod_content: safeString(detailObj.room_notice) || '体育直播',
vod_play_from: playFromList.join('$$$') || '919体育',
vod_play_url: playUrlList.join('$$$') || ('暂无播放$' + vid)
});
} catch (e) {}
}
return JSON.stringify({ code: 1, msg: '数据列表', page: 1, pagecount: 1, limit: list.length, total: list.length, list });
}
async function search(wd, quick, pg) {
return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: 20, total: 0, list: [] });
}
async function play(flag, id, flags) {
const url = safeString(id).replace(/\*\*\*/g, '#');
if (!/^https?:\/\//i.test(url)) return JSON.stringify({ parse: 0, jx: 0, url: '', msg: '暂无播放地址' });
return JSON.stringify({ parse: 0, jx: 0, url, header: headers });
}
async function homeContent(filter) { return safeJson(await home(filter), { class: [], filters: {} }); }
async function homeVideoContent() { return safeJson(await homeVod(), { list: [] }); }
async function categoryContent(tid, pg, filter, extend) { return safeJson(await category(tid, pg, filter, extend || {}), { list: [] }); }
async function detailContent(ids) { return safeJson(await detail(ids), { list: [] }); }
async function searchContent(wd, quick, pg) { return safeJson(await search(wd, quick, pg || 1), { list: [] }); }
async function playerContent(flag, id, flags) { return safeJson(await play(flag, id, flags), { parse: 0, url: id }); }
return { init, home, homeVod, category, search, detail, play, homeContent, homeVideoContent, categoryContent, detailContent, searchContent, playerContent };
})();
const __sports_src_3 = (function () {
let host = 'https://m.jrskk.com';
const hosts = ['https://m.jrskk.com', 'https://m.jrs21.com', 'https://www.jrs33.com', 'https://3.swjrzx.com'];
const UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';
const defaultPic = 'https://im-imgs-bucket.oss-accelerate.aliyuncs.com/icon-192.png';
let cacheTime = 0;
let cacheHtml = '';
const B64_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function stripHtml(s) {
return String(s || '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/\s+/g, ' ')
.trim();
}
function cleanText(s) { return String(s || '').replace(/\s+/g, ' ').trim(); }
function absUrl(url, base) {
url = String(url || '').trim();
base = base || host;
if (!url) return '';
if (/^https?:\/\//i.test(url)) return url;
if (url.indexOf('//') === 0) return 'https:' + url;
if (url.charAt(0) === '/') return base + url;
return base + '/' + url;
}
function utf8ToBase64Url(str) {
str = unescape(encodeURIComponent(String(str || '')));
let out = '';
for (let i = 0; i < str.length; i += 3) {
const c1 = str.charCodeAt(i);
const c2 = str.charCodeAt(i + 1);
const c3 = str.charCodeAt(i + 2);
out += B64_CHARS.charAt(c1 >> 2);
out += B64_CHARS.charAt(((c1 & 3) << 4) | ((c2 || 0) >> 4));
out += isNaN(c2) ? '=' : B64_CHARS.charAt(((c2 & 15) << 2) | ((c3 || 0) >> 6));
out += isNaN(c3) ? '=' : B64_CHARS.charAt(c3 & 63);
}
return out.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function base64UrlToUtf8(str) {
str = String(str || '').replace(/-/g, '+').replace(/_/g, '/');
while (str.length % 4) str += '=';
let bytes = [];
for (let i = 0; i < str.length; i += 4) {
const c1 = B64_CHARS.indexOf(str.charAt(i));
const c2 = B64_CHARS.indexOf(str.charAt(i + 1));
const c3 = B64_CHARS.indexOf(str.charAt(i + 2));
const c4 = B64_CHARS.indexOf(str.charAt(i + 3));
if (c1 < 0 || c2 < 0) continue;
const n = (c1 << 18) | (c2 << 12) | ((c3 < 0 ? 0 : c3) << 6) | (c4 < 0 ? 0 : c4);
bytes.push((n >> 16) & 255);
if (str.charAt(i + 2) !== '=') bytes.push((n >> 8) & 255);
if (str.charAt(i + 3) !== '=') bytes.push(n & 255);
}
let raw = '';
for (let j = 0; j < bytes.length; j++) raw += String.fromCharCode(bytes[j]);
try { return decodeURIComponent(escape(raw)); } catch (e) { return raw; }
}
function safeJson(text, def) { try { return JSON.parse(text || '{}'); } catch (e) { return def || {}; } }
function matchCategory(tid, league, name, stype, hot) {
tid = String(tid || 'all');
const text = league + ' ' + name;
if (tid === 'all' || tid === 'live') return true;
if (tid === 'hot') return !!hot || /(NBA|CBA|英超|西甲|意甲|德甲|法甲|欧冠|中超|世界杯|世俱杯|亚冠|热门)/i.test(text);
if (tid === 'basketball') return stype === 'lq' || /(NBA|CBA|WNBA|NBL|篮球|篮)/i.test(text);
if (tid === 'football') return stype === 'zq' || (/(足球|英超|西甲|意甲|德甲|法甲|欧冠|欧联|中超|亚冠|足协|世界杯|世俱|巴西甲|巴西乙|日职|韩K|联赛|杯)/i.test(text) && !/(NBA|CBA|WNBA|NBL|篮球|篮)/i.test(text));
if (tid === 'other') return !matchCategory('basketball', league, name, stype, hot) && !matchCategory('football', league, name, stype, hot);
return true;
}
async function fetchText(url, referer) {
const hd = { 'User-Agent': UA, 'Referer': referer || host + '/', 'Accept': 'text/html,application/json,*/*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' };
if (typeof Java !== 'undefined' && Java && Java.req) {
const r = await Java.req(url, { headers: hd });
if (typeof r === 'string') return r;
return String((r && (r.body || r.content || r.data)) || '');
}
const r2 = await req(url, { headers: hd });
return String((r2 && (r2.content || r2.body)) || '');
}
async function fetchHome(force) {
if (!force && cacheHtml && Date.now() - cacheTime < 60000) return cacheHtml;
for (let i = 0; i < hosts.length; i++) {
try {
const html = await fetchText(hosts[i] + '/', hosts[i] + '/');
if (html && (/loc_match|lab_team|JRKAN|play\/steam/i).test(html)) {
host = hosts[i]; cacheHtml = html; cacheTime = Date.now(); return html;
}
} catch (e) {}
}
return cacheHtml || '';
}
function firstMatch(text, reg) { const m = String(text || '').match(reg); return m ? m[1] : ''; }
function parseList(html, tid) {
const list = [];
const reg = /<ul\b[^>]*class=["'][^"']*item[^"']*["'][^>]*>[\s\S]*?<\/ul>/gi;
let m;
while ((m = reg.exec(String(html || ''))) !== null) {
const item = m[0];
if (!/class=["'][^"']*ok[^"']*me[^"']*["']/i.test(item)) continue;
const links = [];
const areg = /<a\b([^>]*class=["'][^"']*ok[^"']*me[^"']*["'][^>]*)>([\s\S]*?)<\/a>/gi;
let am;
while ((am = areg.exec(item)) !== null) {
const attrs = am[1];
const href = firstMatch(attrs, /href=["']([^"']+)["']/i) || firstMatch(attrs, /data-play=["']([^"']+)["']/i);
if (!href || href === 'javascript:void(0)') continue;
const name = stripHtml(am[2]) || ('线路' + (links.length + 1));
links.push({ name, url: absUrl(href, host) });
}
if (!links.length) continue;
const league = stripHtml(firstMatch(item, /class=["'][^"']*lab_events[^"']*["'][\s\S]*?<span[^>]*class=["']name["'][^>]*>([\s\S]*?)<\/span>/i));
const time = stripHtml(firstMatch(item, /class=["'][^"']*lab_time[^"']*["'][^>]*>([\s\S]*?)<\/li>/i));
const home = stripHtml(firstMatch(item, /class=["'][^"']*lab_team_home[^"']*["'][\s\S]*?<strong[^>]*class=["']name["'][^>]*>([\s\S]*?)<\/strong>/i));
const away = stripHtml(firstMatch(item, /class=["'][^"']*lab_team_away[^"']*["'][\s\S]*?<strong[^>]*class=["']name["'][^>]*>([\s\S]*?)<\/strong>/i));
const stype = firstMatch(item, /data-stype=["']([^"']+)["']/i);
const hot = /class=["'][^"']*hot[^"']*["']/i.test(item);
let name = [time, league, [home, away].filter(Boolean).join(' vs ')].filter(Boolean).join(' ');
if (!name) name = stripHtml(item).slice(0, 80) || '赛事直播';
if (!matchCategory(tid, league, name, stype, hot)) continue;
const pic = absUrl(firstMatch(item, /<img[^>]+src=["']([^"']+)["']/i) || defaultPic, host);
const payload = { name, pic, links };
list.push({ vod_id: 'jrs$' + utf8ToBase64Url(JSON.stringify(payload)), vod_name: name, vod_pic: pic, vod_remarks: links.map(x => x.name).slice(0, 3).join('/') || '直播' });
}
return list;
}
function getClasses() {
return [
{ type_id: 'live', type_name: '正在直播' },
{ type_id: 'all', type_name: '全部赛程' },
{ type_id: 'basketball', type_name: '篮球直播' },
{ type_id: 'football', type_name: '足球直播' },
{ type_id: 'hot', type_name: '热门赛事' },
{ type_id: 'other', type_name: '其他赛事' }
];
}
async function init(cfg) { if (cfg && cfg.ext && String(cfg.ext).indexOf('http') === 0) host = String(cfg.ext).replace(/\/$/, ''); }
async function home(filter) { return JSON.stringify({ class: getClasses(), filters: {} }); }
async function homeVod() { return await category('live', 1, false, {}); }
async function category(tid, pg, filter, extend) {
const html = await fetchHome(false);
const list = parseList(html, tid || 'all');
return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: list.length, total: list.length, list });
}
async function detail(id) {
id = Array.isArray(id) ? id[0] : id;
let payload = null;
id = String(id || '');
if (id.indexOf('jrs$') === 0) payload = safeJson(base64UrlToUtf8(id.slice(4)), null);
if (!payload && /^https?:\/\//i.test(id)) payload = { name: '赛事直播', pic: defaultPic, links: [{ name: '直播', url: id }] };
if (!payload) return JSON.stringify({ code: 1, list: [], page: 1, pagecount: 1, total: 0 });
const playUrl = (payload.links || []).map((x, i) => (x.name || ('线路' + (i + 1))) + '$' + x.url).join('#');
return JSON.stringify({ code: 1, msg: '数据列表', page: 1, pagecount: 1, limit: 1, total: 1, list: [{ vod_id: id, vod_name: payload.name || '赛事直播', vod_pic: payload.pic || defaultPic, vod_remarks: '直播', vod_content: 'JRKAN 体育赛事直播。部分线路为网页播放器,播放器可嗅探播放。', vod_play_from: 'JRS直播', vod_play_url: playUrl }] });
}
async function search(wd, quick, pg) { return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: 20, total: 0, list: [] }); }
async function play(flag, id, flags) { return JSON.stringify({ parse: /\.(m3u8|mp4|flv)(\?|$)/i.test(String(id || '')) ? 0 : 1, url: id, header: { 'User-Agent': UA, 'Referer': host + '/' } }); }
async function homeContent(filter) { return safeJson(await home(filter), { class: [], filters: {} }); }
async function homeVideoContent() { return safeJson(await homeVod(), { list: [] }); }
async function categoryContent(tid, pg, filter, extend) { return safeJson(await category(tid, pg, filter, extend || {}), { list: [] }); }
async function detailContent(ids) { return safeJson(await detail(ids), { list: [] }); }
async function searchContent(wd, quick, pg) { return safeJson(await search(wd, quick, pg || 1), { list: [] }); }
async function playerContent(flag, id, flags) { return safeJson(await play(flag, id, flags), { parse: 1, url: id }); }
return { init, home, homeVod, category, search, detail, play, homeContent, homeVideoContent, categoryContent, detailContent, searchContent, playerContent };
})();
const __sports_src_4 = (function () {
let host = 'https://www.iqzhibo.com';
const dbHost = 'https://www.doubaozhibo.com';
const dbScheduleApi = dbHost + '/api/v1/schedules/public/local';
const dbPlaybackApi = dbHost + '/api/v1/playbacks';
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36';
function getHeaders(referer) {
return {
'User-Agent': UA,
'Referer': referer || host + '/',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
};
}
function getHeaderValue(headers, name) {
if (!headers) return '';
const lower = String(name || '').toLowerCase();
for (const k in headers) {
if (String(k).toLowerCase() === lower) {
const v = headers[k];
return Array.isArray(v) ? String(v[0] || '') : String(v || '');
}
}
return '';
}
function stripHtml(s) {
return String(s || '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/\s+/g, ' ')
.trim();
}
function cleanText(s) {
return stripHtml(s).replace(/\s+/g, ' ').trim();
}
function absUrl(url, base) {
url = String(url || '').trim();
base = base || host;
if (!url) return '';
if (/^https?:\/\//i.test(url)) return url;
if (url.indexOf('//') === 0) return 'https:' + url;
if (url.charAt(0) === '/') return base.replace(/\/$/, '') + url;
return base.replace(/\/$/, '') + '/' + url;
}
function safeJson(text, def) {
try { return JSON.parse(text || '{}'); } catch (e) { return def || {}; }
}
async function fetchText(url, referer) {
const hd = getHeaders(referer || host + '/');
if (typeof Java !== 'undefined' && Java && Java.req) {
const r = await Java.req(url, { headers: hd });
if (typeof r === 'string') return r;
const code = Number((r && (r.statusCode || r.status || r.code)) || 0);
const loc = getHeaderValue(r && r.headers, 'location');
if (loc && code >= 300 && code < 400) return await fetchText(absUrl(loc, url.indexOf(dbHost) === 0 ? dbHost : host), url);
return String((r && (r.body || r.content || r.data)) || '');
}
const r2 = await req(url, { headers: hd });
if (typeof r2 === 'string') return r2;
const code2 = Number((r2 && (r2.statusCode || r2.status || r2.code)) || 0);
const loc2 = getHeaderValue(r2 && r2.headers, 'location');
if (loc2 && code2 >= 300 && code2 < 400) return await fetchText(absUrl(loc2, url.indexOf(dbHost) === 0 ? dbHost : host), url);
return String((r2 && (r2.content || r2.body || r2.data)) || '');
}
function getClasses() {
return [
{ type_id: 'zuqiu', type_name: '足球直播' },
{ type_id: 'lanqiu', type_name: '篮球直播' },
{ type_id: 'sssc', type_name: '实时赛程' },
{ type_id: 'huifang', type_name: '赛事回放' },
{ type_id: 'fifa', type_name: '世界杯' },
{ type_id: 'zhongchao', type_name: '中超' },
{ type_id: 'yingchao', type_name: '英超' },
{ type_id: 'xijia', type_name: '西甲' },
{ type_id: 'dejia', type_name: '德甲' },
{ type_id: 'yijia', type_name: '意甲' },
{ type_id: 'fajia', type_name: '法甲' }
];
}
function parseIqList(html) {
html = String(html || '');
const list = [];
const reg = /<li>\s*<div class=["']liveinfo["'][\s\S]*?<\/li>/gi;
let m;
while ((m = reg.exec(html)) !== null) {
const item = m[0];
const title = cleanText((item.match(/class=["']title["'][^>]*>([\s\S]*?)<\/div>/i) || [])[1]);
const homeBlock = (item.match(/class=["']team-zhu["'][^>]*>([\s\S]*?)<\/div>/i) || [])[1] || '';
const awayBlock = (item.match(/class=["']team-ke["'][^>]*>([\s\S]*?)<\/div>/i) || [])[1] || '';
const home = cleanText(homeBlock.replace(/<img[\s\S]*$/i, ''));
const away = cleanText(awayBlock.replace(/^[\s\S]*?<img[^>]*>/i, ''));
const hrefMatch = item.match(/<div class=["']livesource["'][\s\S]*?<a[^>]+href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/i);
if (!hrefMatch) continue;
const imgMatch = item.match(/<img[^>]+src=["']([^"']+)["']/i);
const teams = [home, away].filter(Boolean).join(' vs ');
const name = [title, teams].filter(Boolean).join(' ');
list.push({
vod_id: absUrl(hrefMatch[1], host),
vod_name: name || teams || title || '赛事直播',
vod_pic: absUrl(imgMatch ? imgMatch[1] : '/logo.png', host),
vod_remarks: cleanText(hrefMatch[2]) || '高清'
});
}
return list;
}
function matchCategory(tid, league, name, dataType) {
tid = String(tid || '');
league = cleanText(league);
name = cleanText(name);
dataType = String(dataType || '');
const text = league + ' ' + name;
if (!tid || tid === 'sssc' || tid === 'huifang') return true;
if (tid === 'zuqiu') return dataType === 'football' || (/(足|超|甲|乙|冠|欧联|日职|韩K|世界杯|世俱|足球|杯)/i.test(text) && !/(篮|NBA|CBA|WNBA|NBL|篮球)/i.test(text));
if (tid === 'lanqiu') return dataType === 'basketball' || /(篮|NBA|CBA|WNBA|NBL|篮球)/i.test(text);
const rules = {
fifa: /(世界杯|世俱杯|世预赛|国际足联|FIFA)/i,
zhongchao: /(中超|足协杯|中国超级联赛)/i,
yingchao: /(英超|英格兰超级联赛)/i,
xijia: /(西甲|西班牙甲级联赛)/i,
dejia: /(德甲|德国甲级联赛)/i,
yijia: /(意甲|意大利甲级联赛)/i,
fajia: /(法甲|法国甲级联赛)/i
};
return rules[tid] ? rules[tid].test(text) : true;
}
function formatTimeText(matchTime) {
const d = new Date(matchTime);
if (!matchTime || isNaN(d.getTime())) return '';
const pad = function (n) { return String(n).padStart(2, '0'); };
return pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
}
async function fetchJson(url, referer) {
const text = await fetchText(url, referer || dbHost + '/');
try { return JSON.parse(text || '{}'); } catch (e) { return {}; }
}
function parseApiSchedule(json, tid) {
const days = json && json.data && Array.isArray(json.data.days) ? json.data.days : [];
const list = [];
for (let i = 0; i < days.length; i++) {
const rows = Array.isArray(days[i].live) ? days[i].live : [];
for (let j = 0; j < rows.length; j++) {
const item = rows[j];
const signals = Array.isArray(item.signals) ? item.signals : [];
if (!signals.length) continue;
const name = [formatTimeText(item.matchTime), cleanText(item.league), cleanText(item.teamA) + ' vs ' + cleanText(item.teamB)].filter(Boolean).join(' ');
if (!matchCategory(tid, item.league, name, item.dataType)) continue;
const links = signals.map(function (s, idx) {
return { name: cleanText(s.name || s.label) || ('信号' + (idx + 1)), url: dbHost + '/play/' + s.playId + '?device=pc_web' };
});
list.push({
vod_id: 'db$' + encodeURIComponent(JSON.stringify({ name: name, links: links })),
vod_name: name,
vod_pic: item.teamAImage || item.teamBImage || host + '/logo.png',
vod_remarks: links.map(function (x) { return x.name; }).join('/') || '直播'
});
}
}
return list;
}
function parsePlaybackList(json, tid) {
const rows = json && json.data && Array.isArray(json.data.list) ? json.data.list : [];
const list = [];
for (let i = 0; i < rows.length; i++) {
const s = rows[i].schedule || {};
const p = rows[i].playback || {};
const lines = Array.isArray(p.lines) ? p.lines : [];
if (!lines.length) continue;
const fullName = cleanText(p.title) || [formatTimeText(s.matchTime), cleanText(s.league), cleanText(s.teamA) + ' vs ' + cleanText(s.teamB), '回放'].filter(Boolean).join(' ');
if (!matchCategory(tid, s.league, fullName, s.dataType)) continue;
const shortName = (cleanText(s.teamA) + ' vs ' + cleanText(s.teamB)).replace(/^\s*vs\s*|\s*vs\s*$/g, '') || fullName.replace(/^\d{4}年\d{1,2}月\d{1,2}日\s*/, '').replace(/\s*赛事回放$/, '');
const remarkPrefix = [formatTimeText(s.matchTime), cleanText(s.league)].filter(Boolean).join(' ');
const links = lines.map(function (line, idx) {
return { name: cleanText(line.title) || ('回放' + (idx + 1)), url: absUrl(line.proxyUrl, dbHost) };
});
list.push({
vod_id: 'db$' + encodeURIComponent(JSON.stringify({ name: fullName, links: links })),
vod_name: shortName,
vod_pic: s.teamAImage || s.teamBImage || host + '/logo.png',
vod_remarks: [remarkPrefix, links.map(function (x) { return x.name; }).join('/')].filter(Boolean).join(' · ') || '回放'
});
}
return list;
}
function parseProxySchedule(html, tid) {
html = String(html || '');
const list = [];
const reg = /<article[^>]*class=["'][^"']*px-3 py-3 sm:px-4[^"']*["'][^>]*>[\s\S]*?<\/article>/gi;
let m;
while ((m = reg.exec(html)) !== null) {
const item = m[0];
const ps = [];
const pReg = /<p[^>]*>([\s\S]*?)<\/p>/gi;
let pm;
while ((pm = pReg.exec(item)) !== null) ps.push(cleanText(pm[1]));
const time = ps[0] || '';
const league = ps[1] || '';
const teams = [];
const spanReg = /<span[^>]*class=["'][^"']*truncate[^"']*["'][^>]*>([\s\S]*?)<\/span>/gi;
let sm;
while ((sm = spanReg.exec(item)) !== null) {
const t = cleanText(sm[1]);
if (t && !/^信号|高清|返回/.test(t) && teams.indexOf(t) < 0) teams.push(t);
}
if (teams.length < 2) continue;
const name = [time, league, teams[0] + ' vs ' + teams[1]].filter(Boolean).join(' ');
if (!matchCategory(tid, league, name)) continue;
const links = [];
const aReg = /<a[^>]+href=["']([^"']+)["'][^>]*class=["'][^"']*home-signal-link[^"']*["'][^>]*>([\s\S]*?)<\/a>/gi;
let am;
while ((am = aReg.exec(item)) !== null) {
const title = (am[0].match(/title=["']([^"']+)["']/i) || [])[1] || cleanText(am[2]) || ('信号' + (links.length + 1));
links.push({ name: title, url: absUrl(am[1], dbHost) });
}
if (!links.length) continue;
const img = (item.match(/<img[^>]+src=["']([^"']+)["']/i) || [])[1] || '/logo.png';
list.push({
vod_id: 'db$' + encodeURIComponent(JSON.stringify({ name: name, links: links })),
vod_name: name,
vod_pic: absUrl(img, dbHost),
vod_remarks: links.map(function (x) { return x.name; }).join('/') || '直播'
});
}
return list;
}
async function init(cfg) {
if (cfg && cfg.ext && String(cfg.ext).indexOf('http') === 0) host = String(cfg.ext).trim().replace(/\/$/, '');
}
async function home(filter) {
return JSON.stringify({ class: getClasses(), filters: {} });
}
async function homeVod() {
return await category('zuqiu', 1, false, {});
}
async function category(tid, pg, filter, extend) {
tid = String((extend && extend.cateId) || tid || 'zuqiu');
pg = parseInt(pg) || 1;
let list = [];
try {
if (tid === 'huifang') {
list = parsePlaybackList(await fetchJson(dbPlaybackApi + '?page=' + pg + '&pageSize=20&dataType=all', dbHost + '/'), tid);
} else {
list = parseApiSchedule(await fetchJson(dbScheduleApi, dbHost + '/'), tid);
if (!list.length && tid !== 'lanqiu') {
list = parseProxySchedule(await fetchText(host + '/proxy.php', host + '/sssc/'), tid);
}
}
} catch (e) {
list = [];
}
return JSON.stringify({ code: 1, msg: '数据列表', page: pg, pagecount: list.length >= 20 ? pg + 1 : 1, limit: 50, total: list.length, list: list });
}
function parseNuxtProxy(html) {
html = String(html || '');
const hls = html.match(/(?:\\u002F|\/)hls(?:\\u002F|\/)[A-Za-z0-9._-]+\.m3u8/i);
if (hls) return absUrl(hls[0].replace(/\\u002F/g, '/'), dbHost);
const m = html.match(/"proxyUrl"\s*:\s*"((?:\\.|[^"\\])*)"/);
if (m) {
try { return absUrl(JSON.parse('"' + m[1] + '"'), dbHost); } catch (e) { return absUrl(m[1].replace(/\\u002F/g, '/'), dbHost); }
}
const iframe = html.match(/https?:\/\/www\.kanqiuge\.com\/embed\/play\/[A-Za-z0-9._-]+/i);
if (iframe) return iframe[0];
return '';
}
async function resolvePlayUrl(playUrl) {
playUrl = String(playUrl || '');
if (/\.(m3u8|flv|mp4)(\?|$)/i.test(playUrl)) return playUrl;
try {
const html = await fetchText(playUrl, host + '/sssc/');
return parseNuxtProxy(html) || playUrl;
} catch (e) {
return playUrl;
}
}
async function detail(id) {
id = Array.isArray(id) ? id[0] : id;
id = String(id || '');
if (id.indexOf('db$') === 0) {
let data = { name: '实时赛程', links: [] };
try { data = JSON.parse(decodeURIComponent(id.slice(3))); } catch (e) {}
const playUrls = (data.links || []).map(function (x) { return (x.name || '线路') + '$' + x.url; }).join('#');
return JSON.stringify({
code: 1,
msg: '数据列表',
page: 1,
pagecount: 1,
limit: 1,
total: 1,
list: [{
vod_id: id,
vod_name: data.name || '实时赛程',
vod_pic: host + '/logo.png',
vod_remarks: '直播',
vod_play_from: '爱球直播',
vod_play_url: playUrls,
vod_content: '爱球直播实时赛程,播放时解析豆包直播信号。'
}]
});
}
let name = '赛事直播';
let time = '';
let playUrls = '';
try {
const html = await fetchText(id, host + '/');
name = cleanText((html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i) || [])[1]) || name;
time = cleanText((html.match(/<div class=["']match-header["'][\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/i) || [])[1]);
const arr = [];
const aReg = /<div class=["']gohome["'][\s\S]*?<\/div>/i;
const block = (html.match(aReg) || [])[0] || '';
const linkReg = /<a[^>]+href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
let lm;
while ((lm = linkReg.exec(block)) !== null) {
const text = cleanText(lm[2]) || ('信号' + (arr.length + 1));
if (/赛程|返回|首页/.test(text)) continue;
const href = lm[1];
arr.push(text + '$' + (href === '/sssc/' || href.indexOf('/sssc/') >= 0 ? host + '/proxy.php' : absUrl(href, host)));
}
if (!arr.length) arr.push('赛程$' + host + '/proxy.php');
playUrls = arr.join('#');
} catch (e) {
playUrls = '赛程$' + host + '/proxy.php';
}
return JSON.stringify({
code: 1,
msg: '数据列表',
page: 1,
pagecount: 1,
limit: 1,
total: 1,
list: [{
vod_id: id,
vod_name: name,
vod_pic: host + '/logo.png',
vod_remarks: time,
vod_play_from: '爱球直播',
vod_play_url: playUrls,
vod_content: time || '爱球直播赛事导航'
}]
});
}
async function search(wd, quick, pg) {
pg = parseInt(pg) || 1;
let list = [];
try {
const html = await fetchText(host + '/search.html?keywords=' + encodeURIComponent(wd || '') + '&method=1', host + '/');
list = parseIqList(html);
} catch (e) {
list = [];
}
return JSON.stringify({ code: 1, msg: '数据列表', page: pg, pagecount: 1, limit: 20, total: list.length, list: list });
}
async function play(flag, id, flags) {
const url = await resolvePlayUrl(id);
const direct = /\.(m3u8|flv|mp4)(\?|$)/i.test(String(url || ''));
return JSON.stringify({
parse: direct ? 0 : 1,
url: url,
header: {
'User-Agent': UA,
'Referer': String(url || '').indexOf('doubaozhibo.com') >= 0 ? dbHost + '/' : host + '/'
}
});
}
async function homeContent(filter) { return safeJson(await home(filter), { class: [], filters: {} }); }
async function homeVideoContent() { return safeJson(await homeVod(), { list: [] }); }
async function categoryContent(tid, pg, filter, extend) { return safeJson(await category(tid, pg, filter, extend || {}), { list: [] }); }
async function detailContent(ids) { return safeJson(await detail(ids), { list: [] }); }
async function searchContent(wd, quick, pg) { return safeJson(await search(wd, quick, pg || 1), { list: [] }); }
async function playerContent(flag, id, flags) { return safeJson(await play(flag, id, flags), { parse: 1, url: id }); }
return { init, home, homeVod, category, search, detail, play, homeContent, homeVideoContent, categoryContent, detailContent, searchContent, playerContent };
})();
const __sports_src_5 = (function () {
const API_BASE = 'https://aapi2.xbncs.com/api';
const HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
'Accept': 'application/json, text/plain, */*',
'Referer': 'https://aapi2.xbncs.com/'
};
const PAGE_SIZE = 30;
function safeString(v) { if (v === null || v === undefined || v === 'null') return ''; return String(v); }
function safeJson(text, def) { if (text && typeof text === 'object') return text; try { return JSON.parse(text || '{}'); } catch (e) { return def || {}; } }
async function requestJson(url) {
if (typeof Java !== 'undefined' && Java && Java.req) {
const r = await Java.req(url, { headers: HEADERS });
if (typeof r === 'string') return safeJson(r, {});
return safeJson((r && (r.body || r.content || r.data)) || '{}', {});
}
const r2 = await req(url, { headers: HEADERS });
return safeJson((r2 && (r2.content || r2.body || r2.data)) || '{}', {});
}
function getClasses() { return [{ type_id: '-1', type_name: '全部' }, { type_id: '1', type_name: '足球' }, { type_id: '2', type_name: '篮球' }]; }
async function init(cfg) {}
async function home(filter) { return JSON.stringify({ class: getClasses(), filters: {} }); }
async function homeVod() { return await category('-1', 1, false, {}); }
async function category(tid, pg, filter, extend) {
const page = Math.max(parseInt(pg || '1', 10) || 1, 1);
const navId = tid === '-1' || tid === undefined || tid === null ? '' : String(tid);
const url = API_BASE + '/room/page?roomType=&navId=' + encodeURIComponent(navId) + '&roomId=&word=&page=' + page + '&pageSize=' + PAGE_SIZE + '&channelId=3&platform=1';
const list = [];
try {
const json = await requestJson(url);
const rows = json && json.data && Array.isArray(json.data.list) ? json.data.list : [];
for (let i = 0; i < rows.length; i++) {
const item = rows[i];
const roomId = safeString(item.roomId);
if (!roomId) continue;
list.push({
vod_id: roomId,
vod_name: safeString(item.title) || safeString(item.nickName) || ('房间 ' + roomId),
vod_pic: safeString(item.cover),
vod_remarks: safeString(item.navName) || safeString(item.statusName) || safeString(item.liveStatus) || '直播'
});
}
} catch (e) {}
return JSON.stringify({ code: 1, msg: '数据列表', page: page, pagecount: list.length >= PAGE_SIZE ? page + 1 : page, limit: PAGE_SIZE, total: list.length, list: list });
}
async function detail(id) {
id = Array.isArray(id) ? id[0] : id;
const roomId = safeString(id).trim();
if (!roomId) return JSON.stringify({ code: 1, list: [], page: 1, pagecount: 1, total: 0 });
const vod = { vod_id: roomId, vod_name: '房间 ' + roomId, vod_pic: '', vod_content: '体育直播', type_name: '', vod_play_from: '球通', vod_play_url: '' };
try {
const json = await requestJson(API_BASE + '/room/info?roomId=' + encodeURIComponent(roomId) + '&channelId=3&platform=1');
const data = json && json.data ? json.data : null;
if (data) {
vod.vod_name = safeString(data.title) || vod.vod_name;
vod.vod_pic = safeString(data.cover);
vod.vod_content = safeString(data.description) || safeString(data.notice) || '体育直播';
vod.type_name = safeString(data.nickName) || safeString(data.navName);
const playUrls = [];
const pushUrl = safeString(data.pushUrl);
const pullUrl = safeString(data.pullUrl);
if (pushUrl) playUrls.push('flv$' + pushUrl);
if (pullUrl) playUrls.push('m3u8$' + pullUrl);
vod.vod_play_url = playUrls.join('#');
}
} catch (e) {}
if (!vod.vod_play_url) vod.vod_play_url = '暂无播放$' + roomId;
return JSON.stringify({ code: 1, msg: '数据列表', page: 1, pagecount: 1, limit: 1, total: 1, list: [vod] });
}
async function search(wd, quick, pg) { return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: 0, total: 0, list: [] }); }
async function play(flag, id, flags) { id = safeString(id).replace(/\*\*\*/g, '#'); return JSON.stringify({ parse: /^https?:\/\//i.test(id) ? 0 : 1, jx: 0, url: /^https?:\/\//i.test(id) ? id : '', msg: /^https?:\/\//i.test(id) ? '' : '暂无播放地址', header: HEADERS }); }
async function homeContent(filter) { return safeJson(await home(filter), { class: [], filters: {} }); }
async function homeVideoContent() { return safeJson(await homeVod(), { list: [] }); }
async function categoryContent(tid, pg, filter, extend) { return safeJson(await category(tid, pg, filter, extend || {}), { list: [] }); }
async function detailContent(ids) { return safeJson(await detail(ids), { list: [] }); }
async function searchContent(wd, quick, pg) { return safeJson(await search(wd, quick, pg || 1), { list: [] }); }
async function playerContent(flag, id, flags) { return safeJson(await play(flag, id, flags), { parse: 1, url: id }); }
return { init, home, homeVod, category, search, detail, play, homeContent, homeVideoContent, categoryContent, detailContent, searchContent, playerContent };
})();
const SOURCES = [
{ id: 's0', name: '88看球', api: __sports_src_0 },
{ id: 's1', name: '咖啡直播', api: __sports_src_1 },
{ id: 's2', name: '919体育', api: __sports_src_2 },
{ id: 's3', name: 'JRS直播', api: __sports_src_3 },
{ id: 's4', name: '爱球直播', api: __sports_src_4 },
{ id: 's5', name: '球通体育', api: __sports_src_5 }
];
const SOURCE_CATS = {
s0: [
{ n: '正在直播', v: 'live' },
{ n: '全部直播', v: '0' },
{ n: '篮球直播', v: '1' },
{ n: '足球直播', v: '8' },
{ n: '其他直播', v: '21' }
],
s1: [
{ n: '全部直播', v: 'all' },
{ n: '热门直播', v: 'hot' },
{ n: 'NBA', v: 'nba' },
{ n: '足球直播', v: '1' },
{ n: '篮球直播', v: '2' },
{ n: '网球直播', v: '3' },
{ n: '台球直播', v: '19' },
{ n: '赛程列表', v: 'schedule' },
{ n: '录像', v: 'recordings' }
],
s2: [
{ n: '全部', v: '1' },
{ n: '足球', v: '2' },
{ n: '篮球', v: '3' }
],
s3: [
{ n: '正在直播', v: 'live' },
{ n: '全部赛程', v: 'all' },
{ n: '篮球直播', v: 'basketball' },
{ n: '足球直播', v: 'football' },
{ n: '热门赛事', v: 'hot' },
{ n: '其他赛事', v: 'other' }
],
s4: [
{ n: '足球直播', v: 'zuqiu' },
{ n: '篮球直播', v: 'lanqiu' },
{ n: '实时赛程', v: 'sssc' },
{ n: '赛事回放', v: 'huifang' },
{ n: '世界杯', v: 'fifa' },
{ n: '中超', v: 'zhongchao' },
{ n: '英超', v: 'yingchao' },
{ n: '西甲', v: 'xijia' },
{ n: '德甲', v: 'dejia' },
{ n: '意甲', v: 'yijia' },
{ n: '法甲', v: 'fajia' }
],
s5: [
{ n: '全部', v: '-1' },
{ n: '足球', v: '1' },
{ n: '篮球', v: '2' }
]
};
function __safeJson(text, def) {
if (text && typeof text === 'object') return text;
try { return JSON.parse(text || '{}'); } catch (e) { return def || {}; }
}
function __packId(srcId, id) {
return srcId + '@@@' + String(id || '');
}
function __splitId(id) {
const s = String(Array.isArray(id) ? id[0] : (id || ''));
const p = s.indexOf('@@@');
if (p < 0) return { srcId: '', raw: s };
return { srcId: s.slice(0, p), raw: s.slice(p + 3) };
}
function __srcById(srcId) {
for (let i = 0; i < SOURCES.length; i++) if (SOURCES[i].id === srcId) return SOURCES[i];
return null;
}
function __firstCat(srcId) {
const arr = SOURCE_CATS[srcId] || [];
return arr.length ? arr[0].v : 'home';
}
function __classes() {
const arr = [];
for (let i = 0; i < SOURCES.length; i++) {
arr.push({ type_id: SOURCES[i].id, type_name: SOURCES[i].name });
}
return arr;
}
function __filters() {
const filters = {};
for (let i = 0; i < SOURCES.length; i++) {
const src = SOURCES[i];
const values = SOURCE_CATS[src.id] || [];
filters[src.id] = [{ key: 'cat', name: '分类', value: values }];
}
return filters;
}
function __pickExtendCat(extend) {
extend = extend || {};
return String(extend.cat || extend.type || extend.class || extend.cate || '');
}
function __prefixList(list, src) {
list = Array.isArray(list) ? list : [];
const out = [];
for (let i = 0; i < list.length; i++) {
const it = list[i] || {};
const id = String(it.vod_id || '');
if (!id) continue;
out.push(Object.assign({}, it, {
vod_id: __packId(src.id, id),
vod_name: String(it.vod_name || ''),
vod_remarks: it.vod_remarks || src.name
}));
}
return out;
}
async function __callList(src, subTid, pg, filter, extend) {
try {
subTid = subTid || __firstCat(src.id);
const obj = __safeJson(await src.api.category(subTid, pg || 1, filter || false, extend || {}), {});
return __prefixList(obj.list || [], src);
} catch (e) {
return [];
}
}
async function init(cfg) {
for (let i = 0; i < SOURCES.length; i++) {
try { if (SOURCES[i].api.init) await SOURCES[i].api.init(cfg || {}); } catch (e) {}
}
}
async function home(filter) {
return JSON.stringify({ class: __classes(), filters: __filters() });
}
async function homeVod() {
// 首页只加载第一个源的默认二级分类,避免一进源就把所有体育站数据全量请求一遍。
const src = SOURCES[0];
const list = await __callList(src, __firstCat(src.id), 1, false, {});
return JSON.stringify({ code: 1, msg: '数据列表', page: 1, pagecount: 1, limit: list.length, total: list.length, list });
}
async function category(tid, pg, filter, extend) {
tid = String(tid || SOURCES[0].id);
extend = extend || {};
const src = __srcById(tid) || SOURCES[0];
const subTid = __pickExtendCat(extend) || __firstCat(src.id);
const list = await __callList(src, subTid, pg || 1, filter, extend);
return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: list.length, total: list.length, list });
}
async function detail(id) {
const sp = __splitId(id);
const src = __srcById(sp.srcId);
if (!src) return JSON.stringify({ code: 1, list: [], page: 1, pagecount: 1, total: 0 });
try {
const obj = __safeJson(await src.api.detail(sp.raw), {});
const list = Array.isArray(obj.list) ? obj.list : [];
for (let i = 0; i < list.length; i++) {
if (list[i]) list[i].vod_id = __packId(src.id, list[i].vod_id || sp.raw);
}
obj.list = list;
return JSON.stringify(obj);
} catch (e) {
return JSON.stringify({ code: 1, list: [], page: 1, pagecount: 1, total: 0, msg: String(e && e.message || e) });
}
}
async function search(wd, quick, pg) {
const list = [];
for (let i = 0; i < SOURCES.length; i++) {
try {
const obj = __safeJson(await SOURCES[i].api.search(wd, quick, pg || 1), {});
const part = __prefixList(obj.list || [], SOURCES[i]);
for (let j = 0; j < part.length; j++) list.push(part[j]);
} catch (e) {}
}
return JSON.stringify({ code: 1, msg: '数据列表', page: parseInt(pg) || 1, pagecount: 1, limit: list.length, total: list.length, list });
}
async function play(flag, id, flags) {
return JSON.stringify(await playerContent(flag, id, flags));
}
async function playerContent(flag, id, flags) {
const sp = __splitId(id);
const src = __srcById(sp.srcId);
if (!src) return { parse: 1, url: sp.raw || id };
try {
if (src.api.playerContent) return await src.api.playerContent(flag, sp.raw, flags);
return __safeJson(await src.api.play(flag, sp.raw, flags), { parse: 1, url: sp.raw });
} catch (e) {
return { parse: 1, url: sp.raw };
}
}
async function homeContent(filter) { return __safeJson(await home(filter), { class: [], filters: {} }); }
async function homeVideoContent() { return __safeJson(await homeVod(), { list: [] }); }
async function categoryContent(tid, pg, filter, extend) { return __safeJson(await category(tid, pg, filter, extend || {}), { list: [] }); }
async function detailContent(ids) { return __safeJson(await detail(ids), { list: [] }); }
async function searchContent(wd, quick, pg) { return __safeJson(await search(wd, quick, pg || 1), { list: [] }); }
export function __jsEvalReturn() {
return { init, home, homeVod, category, search, detail, play, homeContent, homeVideoContent, categoryContent, detailContent, searchContent, playerContent };
}
+429
View File
@@ -0,0 +1,429 @@
if (typeof Object.assign !== 'function') {
Object.assign = function() {
let target = arguments[0];
for (let i = 1; i < arguments.length; i++) {
let source = arguments[i];
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
}
// 通用免嗅探播放
let common_lazy = `js:
let html = request(input);
let hconf = html.match(/r player_.*?=(.*?)</)[1];
let json = JSON5.parse(hconf);
let url = json.url;
if (json.encrypt == '1') {
url = unescape(url);
} else if (json.encrypt == '2') {
url = unescape(base64Decode(url));
}
if (/\\.(m3u8|mp4|m4a|mp3)/.test(url)) {
input = {
parse: 0,
jx: 0,
url: url,
};
} else {
input = url && url.startsWith('http') && tellIsJx(url) ? {parse:0,jx:1,url:url}:input;
}`;
// 默认嗅探播放
let def_lazy = `js:
input = { parse: 1, url: input, js: '' };`;
// 采集站播放
let cj_lazy = `js:
if (/\\.(m3u8|mp4)/.test(input)) {
input = { parse: 0, url: input };
} else {
if (rule.parse_url.startsWith('json:')) {
let purl = rule.parse_url.replace('json:', '') + input;
let html = request(purl);
let json = JSON.parse(html);
if (json.url) {
input = { parse: 0, url: json.url };
}
} else {
input = rule.parse_url + input;
}
}`;
function getMubans() {
const mubanDict = { // 模板字典
mx: {
title: '',
host: '',
url: '/vodshow/fyclass--------fypage---/',
searchUrl: '/vodsearch/**----------fypage---/',
class_parse: '.top_nav li;a&&Text;a&&href;.*/(.*?)/',
searchable: 2,
quickSearch: 0,
filterable: 0,
headers: {
'User-Agent': 'MOBILE_UA',
},
play_parse: true,
lazy: common_lazy,
limit: 6,
double: true,
推荐: '.cbox_list;*;*;*;*;*',
一级: 'ul.vodlist li;a&&title;a&&data-original;.pic_text&&Text;a&&href',
二级: {
title: 'h2&&Text;.content_detail:eq(1)&&li&&a:eq(2)&&Text',
img: '.vodlist_thumb&&data-original',
desc: '.content_detail:eq(1)&&li:eq(1)&&Text;.content_detail:eq(1)&&li&&a&&Text;.content_detail:eq(1)&&li&&a:eq(1)&&Text;.content_detail:eq(1)&&li:eq(2)&&Text;.content_detail:eq(1)&&li:eq(3)&&Text',
content: '.content_desc&&span&&Text',
tabs: '.play_source_tab&&a',
lists: '.content_playlist:eq(#id) li',
},
搜索: '*',
},
mxpro: {
title: '',
host: '', // homeUrl:'/',
url: '/vodshow/fyclass--------fypage---.html',
searchUrl: '/vodsearch/**----------fypage---.html',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
headers: { //网站的请求头,完整支持所有的,常带ua和cookies
'User-Agent': 'MOBILE_UA', // "Cookie": "searchneed=ok"
},
class_parse: '.navbar-items li:gt(0):lt(10);a&&Text;a&&href;/(\\d+)',
play_parse: true,
lazy: common_lazy,
limit: 6,
double: true, // 推荐内容是否双层定位
推荐: '.tab-list.active;a.module-poster-item.module-item;.module-poster-item-title&&Text;.lazyload&&data-original;.module-item-note&&Text;a&&href',
一级: 'body a.module-poster-item.module-item;a&&title;.lazyload&&data-original;.module-item-note&&Text;a&&href',
二级: {
title: 'h1&&Text;.module-info-tag-link:eq(-1)&&Text',
img: '.lazyload&&data-original||data-src||src',
desc: '.module-info-item:eq(-2)&&Text;.module-info-tag-link&&Text;.module-info-tag-link:eq(1)&&Text;.module-info-item:eq(2)&&Text;.module-info-item:eq(1)&&Text',
content: '.module-info-introduction&&Text',
tabs: '.module-tab-item',
lists: '.module-play-list:eq(#id) a',
tab_text: 'div--small&&Text',
},
搜索: 'body .module-item;.module-card-item-title&&Text;.lazyload&&data-original;.module-item-note&&Text;a&&href;.module-info-item-content&&Text',
},
mxone5: {
title: '',
host: '',
url: '/show/fyclass--------fypage---.html',
searchUrl: '/search/**----------fypage---.html',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
class_parse: '.nav-menu-items&&li;a&&Text;a&&href;.*/(.*?)\.html',
play_parse: true,
lazy: common_lazy,
limit: 6,
double: true, // 推荐内容是否双层定位
推荐: '.module-list;.module-items&&.module-item;a&&title;img&&data-src;.module-item-text&&Text;a&&href',
一级: '.module-items .module-item;a&&title;img&&data-src;.module-item-text&&Text;a&&href',
二级: {
title: 'h1&&Text;.tag-link&&Text',
img: '.module-item-pic&&img&&data-src',
desc: '.video-info-items:eq(3)&&Text;.tag-link:eq(2)&&Text;.tag-link:eq(1)&&Text;.video-info-items:eq(1)&&Text;.video-info-items:eq(0)&&Text',
content: '.vod_content&&Text',
tabs: '.module-tab-item',
lists: '.module-player-list:eq(#id)&&.scroll-content&&a',
tab_text: 'div--small&&Text',
},
搜索: '.module-items .module-search-item;a&&title;img&&data-src;.video-serial&&Text;a&&href',
},
首图: {
title: '',
host: '',
url: '/vodshow/fyclass--------fypage---/',
searchUrl: '/vodsearch/**----------fypage---.html',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
headers: { //网站的请求头,完整支持所有的,常带ua和cookies
'User-Agent': 'MOBILE_UA', // "Cookie": "searchneed=ok"
},
class_parse: '.myui-header__menu li.hidden-sm:gt(0):lt(7);a&&Text;a&&href;/(\\d+).html',
play_parse: true,
lazy: common_lazy,
limit: 6,
double: true, // 推荐内容是否双层定位
推荐: 'ul.myui-vodlist.clearfix;li;a&&title;a&&data-original;.pic-text&&Text;a&&href',
一级: '.myui-vodlist li;a&&title;a&&data-original;.pic-text&&Text;a&&href',
二级: {
title: '.myui-content__detail .title--span&&Text;.myui-content__detail p.data:eq(3)&&Text',
img: '.myui-content__thumb .lazyload&&data-original',
desc: '.myui-content__detail p.otherbox&&Text;.year&&Text;.myui-content__detail p.data:eq(4)&&Text;.myui-content__detail p.data:eq(2)&&Text;.myui-content__detail p.data:eq(0)&&Text',
content: '.content&&Text',
tabs: '.myui-panel__head&&li',
// tabs: '.nav-tabs&&li',
lists: '.myui-content__list:eq(#id) li',
},
搜索: '#searchList li;a&&title;.lazyload&&data-original;.pic-text&&Text;a&&href;.detail&&Text',
},
首图2: {
title: '',
host: '',
url: '/list/fyclass-fypage.html',
searchUrl: '/vodsearch/**----------fypage---.html',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
headers: {
'User-Agent': 'UC_UA', // "Cookie": ""
},
class_parse: '.stui-header__menu li:gt(0):lt(7);a&&Text;a&&href;.*/(.*?).html',
play_parse: true,
lazy: common_lazy,
limit: 6,
double: true, // 推荐内容是否双层定位
推荐: 'ul.stui-vodlist.clearfix;li;a&&title;.lazyload&&data-original;.pic-text&&Text;a&&href',
一级: '.stui-vodlist li;a&&title;a&&data-original;.pic-text&&Text;a&&href',
二级: {
title: '.stui-content__detail .title&&Text;.stui-content__detail&&p:eq(-2)&&a&&Text',
title1: '.stui-content__detail .title&&Text;.stui-content__detail&&p&&Text',
img: '.stui-content__thumb .lazyload&&data-original',
desc: '.stui-content__detail p&&Text;.stui-content__detail&&p:eq(-2)&&a:eq(2)&&Text;.stui-content__detail&&p:eq(-2)&&a:eq(1)&&Text;.stui-content__detail p:eq(2)&&Text;.stui-content__detail p:eq(1)&&Text',
desc1: '.stui-content__detail p:eq(4)&&Text;;;.stui-content__detail p:eq(1)&&Text',
content: '.detail&&Text',
tabs: '.stui-pannel__head h3',
tabs1: '.stui-vodlist__head h3',
lists: '.stui-content__playlist:eq(#id) li',
},
搜索: 'ul.stui-vodlist__media,ul.stui-vodlist,#searchList li;a&&title;.lazyload&&data-original;.pic-text&&Text;a&&href;.detail&&Text',
},
默认: {
title: '',
host: '',
url: '',
searchUrl: '',
searchable: 2,
quickSearch: 0,
filterable: 0,
filter: '',
filter_url: '',
filter_def: {},
headers: {
'User-Agent': 'MOBILE_UA',
},
timeout: 5000,
class_parse: '#side-menu li;a&&Text;a&&href;/(.*?)\.html',
cate_exclude: '',
play_parse: true,
lazy: def_lazy,
double: true,
推荐: '列表1;列表2;标题;图片;描述;链接;详情',
一级: '列表;标题;图片;描述;链接;详情',
二级: {
title: 'vod_name;vod_type',
img: '图片链接',
desc: '主要信息;年代;地区;演员;导演',
content: '简介',
tabs: '',
lists: 'xx:eq(#id)&&a',
tab_text: 'body&&Text',
list_text: 'body&&Text',
list_url: 'a&&href',
},
搜索: '列表;标题;图片;描述;链接;详情',
},
vfed: {
title: '',
host: '',
url: '/index.php/vod/show/id/fyclass/page/fypage.html',
searchUrl: '/index.php/vod/search/page/fypage/wd/**.html',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
headers: {
'User-Agent': 'UC_UA',
},
class_parse: '.fed-pops-navbar&&ul.fed-part-rows&&a;a&&Text;a&&href;.*/(.*?).html',
play_parse: true,
lazy: common_lazy,
limit: 6,
double: true, // 推荐内容是否双层定位
推荐: 'ul.fed-list-info.fed-part-rows;li;a.fed-list-title&&Text;a&&data-original;.fed-list-remarks&&Text;a&&href',
一级: '.fed-list-info&&li;a.fed-list-title&&Text;a&&data-original;.fed-list-remarks&&Text;a&&href',
二级: {
title: 'h1.fed-part-eone&&Text;.fed-deta-content&&.fed-part-rows&&li&&Text',
img: '.fed-list-info&&a&&data-original',
desc: '.fed-deta-content&&.fed-part-rows&&li:eq(1)&&Text;.fed-deta-content&&.fed-part-rows&&li:eq(2)&&Text;.fed-deta-content&&.fed-part-rows&&li:eq(3)&&Text',
content: '.fed-part-esan&&Text',
tabs: '.fed-drop-boxs&&.fed-part-rows&&li',
lists: '.fed-play-item:eq(#id)&&ul:eq(1)&&li',
},
搜索: '.fed-deta-info;h1&&Text;.lazyload&&data-original;.fed-list-remarks&&Text;a&&href;.fed-deta-content&&Text',
},
海螺3: {
title: '',
host: '',
searchUrl: '/v_search/**----------fypage---.html',
url: '/vod_____show/fyclass--------fypage---.html',
headers: {
'User-Agent': 'MOBILE_UA',
},
timeout: 5000,
class_parse: 'body&&.hl-nav li:gt(0);a&&Text;a&&href;.*/(.*?).html',
cate_exclude: '明星|专题|最新|排行',
limit: 40,
play_parse: true,
lazy: common_lazy,
double: true,
推荐: '.hl-vod-list;li;a&&title;a&&data-original;.remarks&&Text;a&&href',
一级: '.hl-vod-list&&.hl-list-item;a&&title;a&&data-original;.remarks&&Text;a&&href',
二级: {
title: '.hl-dc-title&&Text;.hl-dc-content&&li:eq(6)&&Text',
img: '.hl-lazy&&data-original',
desc: '.hl-dc-content&&li:eq(10)&&Text;.hl-dc-content&&li:eq(4)&&Text;.hl-dc-content&&li:eq(5)&&Text;.hl-dc-content&&li:eq(2)&&Text;.hl-dc-content&&li:eq(3)&&Text',
content: '.hl-content-text&&Text',
tabs: '.hl-tabs&&a',
tab_text: 'a--span&&Text',
lists: '.hl-plays-list:eq(#id)&&li',
},
搜索: '.hl-list-item;a&&title;a&&data-original;.remarks&&Text;a&&href',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
},
海螺2: {
title: '',
host: '',
searchUrl: '/index.php/vod/search/page/fypage/wd/**/',
url: '/index.php/vod/show/id/fyclass/page/fypage/',
headers: {
'User-Agent': 'MOBILE_UA',
},
timeout: 5000,
class_parse: '#nav-bar li;a&&Text;a&&href;id/(.*?)/',
limit: 40,
play_parse: true,
lazy: common_lazy,
double: true,
推荐: '.list-a.size;li;a&&title;.lazy&&data-original;.bt&&Text;a&&href',
一级: '.list-a&&li;a&&title;.lazy&&data-original;.list-remarks&&Text;a&&href',
二级: {
title: 'h2&&Text;.deployment&&Text',
img: '.lazy&&data-original',
desc: '.deployment&&Text',
content: '.ec-show&&Text',
tabs: '#tag&&a',
lists: '.play_list_box:eq(#id)&&li',
},
搜索: '.search-list;a&&title;.lazy&&data-original;.deployment&&Text;a&&href',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
},
短视: {
title: '',
host: '', // homeUrl:'/',
url: '/channel/fyclass-fypage.html',
searchUrl: '/search.html?wd=**',
searchable: 2, //是否启用全局搜索,
quickSearch: 0, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
headers: { //网站的请求头,完整支持所有的,常带ua和cookies
'User-Agent': 'MOBILE_UA', // "Cookie": "searchneed=ok"
},
class_parse: '.menu_bottom ul li;a&&Text;a&&href;.*/(.*?).html',
cate_exclude: '解析|动态',
play_parse: true,
lazy: common_lazy,
limit: 6,
double: true, // 推荐内容是否双层定位
推荐: '.indexShowBox;ul&&li;a&&title;img&&data-src;.s1&&Text;a&&href',
一级: '.pic-list&&li;a&&title;img&&data-src;.s1&&Text;a&&href',
二级: {
title: 'h1&&Text;.content-rt&&p:eq(0)&&Text',
img: '.img&&img&&data-src',
desc: '.content-rt&&p:eq(1)&&Text;.content-rt&&p:eq(2)&&Text;.content-rt&&p:eq(3)&&Text;.content-rt&&p:eq(4)&&Text;.content-rt&&p:eq(5)&&Text',
content: '.zkjj_a&&Text',
tabs: '.py-tabs&&option',
lists: '.player:eq(#id) li',
},
搜索: '.sr_lists&&ul&&li;h3&&Text;img&&data-src;.int&&p:eq(0)&&Text;a&&href',
},
短视2: {
title: '',
host: '',
class_name: '电影&电视剧&综艺&动漫',
class_url: '1&2&3&4',
searchUrl: '/index.php/ajax/suggest?mid=1&wd=**&limit=50',
searchable: 2,
quickSearch: 0,
headers: {
'User-Agent': 'MOBILE_UA'
},
url: '/index.php/api/vod#type=fyclass&page=fypage',
filterable: 0, //是否启用分类筛选,
filter_url: '',
filter: {},
filter_def: {},
detailUrl: '/index.php/vod/detail/id/fyid.html',
play_parse: true,
lazy: common_lazy,
limit: 6,
推荐: '.list-vod.flex .public-list-box;a&&title;.lazy&&data-original;.public-list-prb&&Text;a&&href',
一级: 'js:let body=input.split("#")[1];let t=Math.round(new Date/1e3).toString();let key=md5("DS"+t+"DCC147D11943AF75");let url=input.split("#")[0];body=body+"&time="+t+"&key="+key;print(body);fetch_params.body=body;let html=post(url,fetch_params);let data=JSON.parse(html);VODS=data.list.map(function(it){it.vod_pic=urljoin2(input.split("/i")[0],it.vod_pic);return it});',
二级: {
title: '.slide-info-title&&Text;.slide-info:eq(2)--strong&&Text',
img: '.detail-pic&&data-original',
desc: '.slide-info-remarks&&Text;.slide-info-remarks:eq(1)&&Text;.slide-info-remarks:eq(2)&&Text;.slide-info:eq(1)--strong&&Text;.info-parameter&&ul&&li:eq(3)&&Text',
content: '#height_limit&&Text',
tabs: '.anthology.wow.fadeInUp.animated&&.swiper-wrapper&&a',
tab_text: 'a--span&&Text',
lists: '.anthology-list-box:eq(#id) li',
},
搜索: 'json:list;name;pic;;id',
},
采集1: {
title: '',
host: '',
homeTid: '13',
homeUrl: '/api.php/provide/vod/?ac=detail&t={{rule.homeTid}}',
detailUrl: '/api.php/provide/vod/?ac=detail&ids=fyid',
searchUrl: '/api.php/provide/vod/?wd=**&pg=fypage',
url: '/api.php/provide/vod/?ac=detail&pg=fypage&t=fyclass',
headers: {
'User-Agent': 'MOBILE_UA'
},
timeout: 5000, // class_name: '电影&电视剧&综艺&动漫',
// class_url: '1&2&3&4',
// class_parse:'js:let html=request(input);input=JSON.parse(html).class;',
class_parse: 'json:class;',
limit: 20,
multi: 1,
searchable: 2, //是否启用全局搜索,
quickSearch: 1, //是否启用快速搜索,
filterable: 0, //是否启用分类筛选,
play_parse: true,
parse_url: '',
lazy: cj_lazy,
推荐: '*',
一级: 'json:list;vod_name;vod_pic;vod_remarks;vod_id;vod_play_from',
二级: `js:
let html=request(input);
html=JSON.parse(html);
let data=html.list;
VOD=data[0];`,
搜索: '*',
},
};
return JSON.parse(JSON.stringify(mubanDict));
}
var mubanDict = getMubans();
var muban = getMubans();
export default {
muban,
getMubans
};
+279
View File
@@ -0,0 +1,279 @@
# coding=utf-8
# !/usr/bin/python
# by嗷呜(finally)
import sys
import os
sys.path.append("..")
import re
import hashlib
import hmac
import random
import string
from Crypto.Util.Padding import unpad
from concurrent.futures import ThreadPoolExecutor
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, AES
from base64 import b64encode, b64decode
import json
import time
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "电影猎手"
def init(self, extend=""):
self.device = self.device_id()
self.host = self.gethost()
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def action(self, action):
pass
def destroy(self):
pass
t = str(int(time.time()))
def homeContent(self, filter):
result = {}
filters = {}
classes = []
bba = self.url()
data = self.fetch(f"{self.host}/api/v1/app/config?pack={bba[0]}&signature={bba[1]}", headers=self.header()).text
data1 = self.aes(data)
dy = {"class":"类型","area":"地区","lang":"语言","year":"年份","letter":"字母","by":"排序","sort":"排序"}
data1['data']['movie_screen']['sort'].pop(0)
for item in data1['data']['movie_screen']['sort']:
item['n'] = item.pop('name')
item['v'] = item.pop('value')
for item in data1['data']['movie_screen']['filter']:
has_non_empty_field = False
classes.append({"type_name": item["name"], "type_id": str(item["id"])})
for key in dy:
if key in item and item[key]:
has_non_empty_field = True
break
if has_non_empty_field:
filters[str(item["id"])] = []
filters[str(item["id"])].append(
{"key": 'sort', "name": '排序', "value": data1['data']['movie_screen']['sort']})
for dkey in item:
if dkey in dy and item[dkey]:
item[dkey].pop(0)
value_array = [
{"n": value.strip(), "v": value.strip()}
for value in item[dkey]
if value.strip() != ""
]
filters[str(item["id"])].append(
{"key": dkey, "name": dy[dkey], "value": value_array}
)
result["class"] = classes
result["filters"] = filters
return result
def homeVideoContent(self):
bba = self.url()
url = f'{self.host}/api/v1/movie/index_recommend?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()
videos = []
for item in data['data']:
if len(item['list']) > 0:
for it in item['list']:
try:
videos.append(self.voides(it))
except Exception as e:
continue
result = {"list": videos}
return result
def categoryContent(self, tid, pg, filter, extend):
body = {"type_id": tid, "sort": extend.get("sort", "by_default"), "class": extend.get("class", "类型"),
"area": extend.get("area", "地区"), "year": extend.get("year", "年份"), "page": str(pg),
"pageSize": "21"}
result = {}
list = []
bba = self.url(body)
url = f"{self.host}/api/v1/movie/screen/list?pack={bba[0]}&signature={bba[1]}"
data = self.fetch(url, headers=self.header()).json()['data']['list']
for item in data:
list.append(self.voides(item))
result["list"] = list
result["page"] = pg
result["pagecount"] = 9999
result["limit"] = 90
result["total"] = 999999
return result
def detailContent(self, ids):
body = {"id": ids[0]}
bba = self.url(body)
url = f'{self.host}/api/v1/movie/detail?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()['data']
video = {'vod_name': data.get('name'),'type_name': data.get('type_name'),'vod_year': data.get('year'),'vod_area': data.get('area'),'vod_remarks': data.get('dynami'),'vod_content': data.get('content')}
play = []
names = []
tasks = []
for itt in data["play_from"]:
name = itt["name"]
a = []
if len(itt["list"]) > 0:
names.append(name)
play.append(self.playeach(itt['list']))
else:
tasks.append({"movie_id": ids[0], "from_code": itt["code"]})
names.append(name)
if tasks:
with ThreadPoolExecutor(max_workers=len(tasks)) as executor:
results = executor.map(self.playlist, tasks)
for result in results:
if result:
play.append(result)
else:
play.append("")
video["vod_play_from"] = "$$$".join(names)
video["vod_play_url"] = "$$$".join(play)
result = {"list": [video]}
return result
def searchContent(self, key, quick, pg=1):
body = {"keyword": key, "sort": "", "type_id": "0", "page": str(pg), "pageSize": "10",
"res_type": "by_movie_name"}
bba = self.url(body)
url = f"{self.host}/api/v1/movie/search?pack={bba[0]}&signature={bba[1]}"
data = self.fetch(url, headers=self.header()).json()['data'].get('list')
videos = []
for it in data:
try:
videos.append(self.voides(it))
except Exception as e:
continue
result = {"list": videos, "page": pg}
return result
def playerContent(self, flag, id, vipFlags):
url = id
if "m3u8" not in url and "mp4" not in url:
try:
add = id.split('|||')
data = {"from_code": add[0], "play_url": add[1], "episode_id": add[2], "type": "play"}
bba = self.url(data)
data2 = self.fetch(f"{self.host}/api/v1/movie_addr/parse_url?pack={bba[0]}&signature={bba[1]}",
headers=self.header()).json()['data']
url = data2.get('play_url') or data2.get('download_url')
try:
url1 = self.fetch(url, headers=self.header(), allow_redirects=False).headers['Location']
if url1 and "http" in url1:
url = url1
except:
pass
except Exception as e:
pass
if '.jpg' in url or '.jpeg' in url or '.png' in url:
url = self.getProxyUrl() + "&url=" + b64encode(url.encode('utf-8')).decode('utf-8') + "&type=m3u8"
result = {}
result["parse"] = 0
result["url"] = url
result["header"] = {'user-agent': 'okhttp/4.9.2'}
return result
def localProxy(self, param):
url = b64decode(param["url"]).decode('utf-8')
durl = url[:url.rfind('/')]
data = self.fetch(url, headers=self.header()).content.decode("utf-8")
lines = data.strip().split('\n')
for index, string in enumerate(lines):
# if 'URI="' in string and 'http' not in string:
# lines[index] = index
# 暂时预留,貌似用不到
if '#EXT' not in string and 'http' not in string:
lines[index] = durl + ('' if string.startswith('/') else '/') + string
data = '\n'.join(lines)
return [200, "application/vnd.apple.mpegur", data]
def device_id(self):
characters = string.ascii_lowercase + string.digits
random_string = ''.join(random.choices(characters, k=32))
return random_string
def gethost(self):
headers = {
'User-Agent': 'okhttp/4.9.2',
'Connection': 'Keep-Alive',
}
response = self.fetch('https://app-site.ecoliving168.com/domain_v5.json', headers=headers).json()
url = response['api_service'].replace('/api/', '')
return url
def header(self):
headers = {
'User-Agent': 'Android',
'Accept': 'application/prs.55App.v2+json',
'timestamp': self.t,
'x-client-setting': '{"pure-mode":1}',
'x-client-uuid': '{"device_id":' + self.device + '}, "type":1,"brand":"Redmi", "model":"M2012K10C", "system_version":30, "sdk_version":"3.1.0.7"}',
'x-client-version': '3096 '
}
return headers
def url(self, id=None):
if not id:
id = {}
id["timestamp"] = self.t
public_key = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA02F/kPg5A2NX4qZ5JSns+bjhVMCC6JbTiTKpbgNgiXU+Kkorg6Dj76gS68gB8llhbUKCXjIdygnHPrxVHWfzmzisq9P9awmXBkCk74Skglx2LKHa/mNz9ivg6YzQ5pQFUEWS0DfomGBXVtqvBlOXMCRxp69oWaMsnfjnBV+0J7vHbXzUIkqBLdXSNfM9Ag5qdRDrJC3CqB65EJ3ARWVzZTTcXSdMW9i3qzEZPawPNPe5yPYbMZIoXLcrqvEZnRK1oak67/ihf7iwPJqdc+68ZYEmmdqwunOvRdjq89fQMVelmqcRD9RYe08v+xDxG9Co9z7hcXGTsUquMxkh29uNawIDAQAB'
encrypted_text = json.dumps(id)
public_key = RSA.import_key(b64decode(public_key))
cipher = PKCS1_v1_5.new(public_key)
encrypted_message = cipher.encrypt(encrypted_text.encode('utf-8'))
encrypted_message_base64 = b64encode(encrypted_message).decode('utf-8')
result = encrypted_message_base64.replace('+', '-').replace('/', '_').replace('=', '')
key = '635a580fcb5dc6e60caa39c31a7bde48'
sign = hmac.new(key.encode(), result.encode(), hashlib.md5).hexdigest()
return result, sign
def playlist(self, body):
try:
bba = self.url(body)
url = f'{self.host}/api/v1/movie_addr/list?pack={bba[0]}&signature={bba[1]}'
data = self.fetch(url, headers=self.header()).json()['data']
return self.playeach(data)
except Exception:
return []
def playeach(self,data):
play_urls = []
for it in data:
if re.search(r"mp4|m3u8", it["play_url"]):
play_urls.append(f"{it['episode_name']}${it['play_url']}")
else:
play_urls.append(
f"{it['episode_name']}${it['from_code']}|||{it['play_url']}|||{it['episode_id']}"
)
return '#'.join(play_urls)
def voides(self, item):
if item['name'] or item['title']:
voide = {
"vod_id": item.get('id') or item.get('click'),
'vod_name': item.get('name') or item.get('title'),
'vod_pic': item.get('cover') or item.get('image'),
'vod_year': item.get('year') or item.get('label'),
'vod_remarks': item.get('dynamic') or item.get('sub_title')
}
return voide
def aes(self, text):
text = text.replace('-', '+').replace('_', '/') + '=='
key = b"e6d5de5fcc51f53d"
iv = b"2f13eef7dfc6c613"
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(b64decode(text)), AES.block_size).decode("utf-8")
return json.loads(pt)