文件
sg/js/cat_哔哩影视.js
T
2026-08-14 11:21:48 +02:00

362 行
17 KiB
JavaScript
原始文件 Blame 文件历史

此文件含有不可见的 Unicode 字符
此文件含有人类无法区分的不可见的 Unicode 字符,但可以由计算机进行不同的处理。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import { load } from 'assets://js/lib/cat.js';
var BILI_COOKIE = '';
var isLogin = false;
var isVip = false;
function getHeaders(needCookie) {
var cookie = BILI_COOKIE || 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc';
return {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
'Referer': 'https://www.bilibili.com',
'Cookie': needCookie ? cookie.replace(/,/g, '%2C') : 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc'
};
}
var FIXED_FILTERS = [
{ name: '排序', key: 'order', value: [
{ v: '0', n: '更新时间' }, { v: '1', n: '弹幕数量' }, { v: '2', n: '播放数量' },
{ v: '3', n: '追看人数' }, { v: '4', n: '最高评分' }, { v: '5', n: '开播时间' }, { v: '6', n: '上映时间' }
]},
{ name: '付费', key: 'season_status', value: [
{ v: '-1', n: '全部' }, { v: '1', n: '免费' }, { v: '2%2C6', n: '付费' }, { v: '4%2C6', n: '大会员' }
]}
];
var ALL_FILTERS = {};
['1','3','4','2','7','5'].forEach(function(t) { ALL_FILTERS[t] = FIXED_FILTERS; });
async function init(config) {
var json = {};
try { json = JSON.parse(config); } catch (e) {}
var cookieSource = json.cookie || ''; // 这里填cookie或外链URL
if (cookieSource) {
if (cookieSource.startsWith('http://') || cookieSource.startsWith('https://')) {
try {
var resp = await req(cookieSource, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000 });
if (resp && resp.content) {
BILI_COOKIE = resp.content.trim();
}
} catch (e) { BILI_COOKIE = ''; }
} else {
BILI_COOKIE = cookieSource;
}
}
try {
var navResp = await req('https://api.bilibili.com/x/web-interface/nav', { headers: getHeaders(true) });
var navData = JSON.parse(navResp.content);
if (navData.data) {
isLogin = navData.data.isLogin || false;
isVip = navData.data.vipStatus === 1;
}
} catch (e) {}
return '{}';
}
async function home(filter) {
var classes = [
{ type_id: '1', type_name: '番剧' },
{ type_id: '4', type_name: '国创' },
{ type_id: '2', type_name: '电影' },
{ type_id: '5', type_name: '电视剧' },
{ type_id: '3', type_name: '纪录' },
{ type_id: '7', type_name: '综艺' }
];
return JSON.stringify({ class: classes, filters: ALL_FILTERS });
}
async function homeVod() { return JSON.stringify({ list: [] }); }
async function category(tid, pg, filterParams, extendParams) {
var page = parseInt(pg) || 1;
var typeId = tid;
var params = { order: '0', season_status: '-1', style_id: -1, sort: -1, area: -1 };
var sel = {};
if (typeof filterParams === 'object') Object.assign(sel, filterParams);
if (typeof extendParams === 'object') Object.assign(sel, extendParams);
if (sel.order) params.order = sel.order;
if (sel.season_status) params.season_status = sel.season_status;
var query = 'order=' + params.order + '&season_status=' + params.season_status +
'&style_id=' + params.style_id + '&sort=' + params.sort + '&area=' + params.area +
'&pagesize=20&type=1&season_type=' + typeId + '&page=' + page;
var url = 'https://api.bilibili.com/pgc/season/index/result?' + query;
var resp = await req(url, { headers: getHeaders(false) });
if (!resp || !resp.content) return JSON.stringify({ list: [] });
var data = JSON.parse(resp.content);
var list = [];
var seasons = (data.data && data.data.list) ? data.data.list : [];
seasons.forEach(function(item) {
list.push({
vod_id: item.season_id,
vod_name: item.title.replace(/<[^>]*>/g, ''),
vod_pic: item.cover,
vod_remarks: item.index_show || ''
});
});
var hasMore = data.data && data.data.has_next === 1;
return JSON.stringify({ list: list, page: page, pagecount: hasMore ? page + 1 : page, limit: 20, total: hasMore ? (page+1)*20 : page*20 });
}
async function detail(ids) {
var seasonId = Array.isArray(ids) ? ids[0] : ids;
if (!seasonId) return JSON.stringify({ list: [] });
var detailUrl = 'https://api.bilibili.com/pgc/view/web/season?season_id=' + seasonId;
var resp = await req(detailUrl, { headers: getHeaders(true) });
if (!resp || !resp.content) return JSON.stringify({ list: [] });
var data = JSON.parse(resp.content);
var season = data.result;
if (!season) return JSON.stringify({ list: [] });
// 基础信息
var title = season.title || '未知剧名';
var cover = season.cover || '';
var desc = season.evaluate || '';
var actors = season.actors || '';
var episodes = season.episodes || [];
var typeName = season.share_sub_title || '';
var areas = (season.areas && season.areas[0]) ? season.areas[0].name : '';
var pubDate = (season.publish && season.publish.pub_time) ? season.publish.pub_time.substring(0, 4) : '';
var newEpDesc = (season.new_ep && season.new_ep.desc) ? season.new_ep.desc : '';
var stat = season.stat || {};
var danmakus = stat.danmakus || 0;
var likes = stat.likes || 0;
var coins = stat.coins || 0;
var favorites = stat.favorites || 0;
var scoreText = '';
if (season.rating && season.rating.score) {
scoreText = '评分: ' + season.rating.score + ' ' + (season.subtitle || '');
} else {
scoreText = '暂无评分' + ' ' + (season.subtitle || '');
}
function zh(num) {
if (Number(num) > 1e8) return (num / 1e8).toFixed(2) + '亿';
if (Number(num) > 1e4) return (num / 1e4).toFixed(2) + '万';
return num.toString();
}
var statusText = '弹幕: ' + zh(danmakus) + ' 点赞: ' + zh(likes) + ' 投币: ' + zh(coins) + ' 追番追剧: ' + zh(favorites);
var playUrls = episodes.map(function(ep) {
var part = (ep.share_copy || ep.title || '剧集').replace('#', '\uff03').replace('$', '\uff04');
return part + '$' + ep.aid + '+' + ep.cid;
}).join('#');
return JSON.stringify({
list: [{
vod_id: seasonId,
vod_name: title,
vod_pic: cover,
vod_remarks: episodes.length + '集 ' + (newEpDesc || ''),
vod_content: desc,
vod_actor: actors,
vod_director: scoreText,
vod_area: areas,
type_name: typeName,
vod_year: pubDate,
vod_play_from: '哔哩影视',
vod_play_url: playUrls
}]
});
}
async function search(keyword, quick, pg) {
var page = pg || '1';
var keywordEnc = encodeURIComponent(keyword);
var ftUrl = 'https://api.bilibili.com/x/web-interface/search/type?search_type=media_ft&keyword=' + keywordEnc + '&page=' + page;
var bangumiUrl = 'https://api.bilibili.com/x/web-interface/search/type?search_type=media_bangumi&keyword=' + keywordEnc + '&page=' + page;
var results = [];
try {
var [ftResp, bangumiResp] = await Promise.all([
req(ftUrl, { headers: getHeaders(false) }),
req(bangumiUrl, { headers: getHeaders(false) })
]);
[ftResp, bangumiResp].forEach(function(resp) {
if (!resp || !resp.content) return;
var data = JSON.parse(resp.content);
var items = (data.data && data.data.result) ? data.data.result : [];
items.forEach(function(item) {
results.push({
vod_id: item.season_id,
vod_name: item.title.replace(/<[^>]*>/g, ''),
vod_pic: item.cover,
vod_remarks: item.index_show || ''
});
});
});
} catch (e) {}
var seen = new Set();
var finalList = [];
results.forEach(function(item) { if (!seen.has(item.vod_id)) { seen.add(item.vod_id); finalList.push(item); } });
return JSON.stringify({ list: finalList, page: parseInt(page), pagecount: 1, limit: finalList.length, total: finalList.length });
}
async function play(flag, id) {
if (!id || id.indexOf('+') === -1) return JSON.stringify({ parse: 0, url: '' });
var parts = id.split('+');
var aid = parts[0], cid = parts[1];
// ===== 优先 DASH =====
try {
var dashUrl = 'https://api.bilibili.com/x/player/playurl?avid=' + aid + '&cid=' + cid + '&qn=127&fnval=4048&fnver=0&fourk=1&platform=pc';
var resp = await req(dashUrl, { headers: getHeaders(true), timeout: 10000 });
var json = JSON.parse(resp.content);
var result = json.result || json.data;
if (result && result.dash) {
var dash = result.dash;
var videos = dash.video || [];
var audios = dash.audio || [];
var duration = dash.duration || 0;
var minBuf = dash.min_buffer_time || 1.5;
var formats = result.support_formats || [];
if (formats.length === 0) {
var aq = result.accept_quality || [];
var ad = result.accept_description || [];
formats = aq.map(function(q, i) { return { quality: q, display_desc: ad[i] || q.toString() }; });
}
var qnMap = {};
formats.forEach(function(f) { qnMap[f.quality] = f.display_desc || f.new_description || f.quality.toString(); });
var audio = null;
for (var i = 0; i < audios.length; i++) {
if (!audio || audios[i].id > audio.id) audio = audios[i];
}
var urlList = [];
var seenQn = new Set();
videos.sort(function(a, b) { return b.id - a.id; });
for (var j = 0; j < videos.length; j++) {
var v = videos[j];
var qn = v.id;
if (qnMap[qn] && !seenQn.has(qn)) {
seenQn.add(qn);
var mpd = buildMpd(v, audio, duration, minBuf);
var dataUri = 'data:application/dash+xml;base64,' + base64Encode(mpd);
urlList.push(qnMap[qn], dataUri);
}
}
if (urlList.length > 0) {
return JSON.stringify({
parse: 0, jx: 0,
url: urlList,
danmaku: 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + cid,
header: JSON.stringify(getHeaders(true))
});
}
}
} catch (e) {}
// ===== FLV 后备:利用真实 quality 去重 =====
try {
var infoUrl = 'https://api.bilibili.com/pgc/player/web/playurl?avid=' + aid + '&cid=' + cid + '&qn=0&fnval=1&fourk=1';
var infoResp = await req(infoUrl, { headers: getHeaders(true), timeout: 5000 });
var infoJson = JSON.parse(infoResp.content);
var infoResult = infoJson.result || infoJson.data;
var acceptQuality = (infoResult && infoResult.accept_quality) ? infoResult.accept_quality : [80, 64, 32, 16];
var acceptDesc = (infoResult && infoResult.accept_description) ? infoResult.accept_description : [];
var descMap = {};
for (var i = 0; i < acceptQuality.length; i++) {
descMap[acceptQuality[i]] = acceptDesc[i] || acceptQuality[i].toString();
}
var collected = {}; // key: 真实qn, value: {url, desc}
// 从高到低遍历
acceptQuality.sort(function(a,b){ return b - a; });
for (var i = 0; i < acceptQuality.length; i++) {
var qn = acceptQuality[i];
try {
var flvUrl = 'https://api.bilibili.com/pgc/player/web/playurl?avid=' + aid + '&cid=' + cid + '&qn=' + qn + '&fnval=1&fourk=1';
var flvResp = await req(flvUrl, { headers: getHeaders(true), timeout: 5000 });
var flvJson = JSON.parse(flvResp.content);
var flvResult = flvJson.result || flvJson.data;
if (flvResult && flvResult.durl && flvResult.durl.length > 0) {
// 以服务器返回的实际质量为准
var realQn = flvResult.quality !== undefined ? flvResult.quality : qn;
if (!collected[realQn]) {
var name = descMap[realQn] || flvResult.format || realQn.toString();
collected[realQn] = { url: flvResult.durl[0].url, desc: name };
}
}
} catch (e2) {}
}
var names = [], urls = [];
// 按真实画质从高到低输出
var keys = Object.keys(collected).sort(function(a,b){ return b - a; });
for (var k = 0; k < keys.length; k++) {
var item = collected[keys[k]];
names.push(item.desc);
urls.push(item.url);
}
if (urls.length > 0) {
var urlList = [];
for (var m = 0; m < names.length; m++) {
urlList.push(names[m], urls[m]);
}
return JSON.stringify({
parse: 0, jx: 0,
url: urlList,
danmaku: 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + cid,
header: JSON.stringify(getHeaders(true))
});
}
} catch (e3) {}
return JSON.stringify({ parse: 0, url: '' });
}
function buildMpd(video, audio, duration, minBuf) {
var mpd = '<?xml version="1.0" encoding="utf-8"?>\n';
mpd += '<MPD xmlns="urn:mpeg:dash:schemas:mpd:2011" minBufferTime="PT' + minBuf + 'S" type="static" mediaPresentationDuration="PT' + duration + 'S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">\n';
mpd += ' <Period duration="PT' + duration + 'S">\n';
mpd += ' <AdaptationSet contentType="video" mimeType="' + video.mime_type + '" segmentAlignment="true" startWithSAP="1">\n';
mpd += ' <Representation id="' + video.id + '" bandwidth="' + video.bandwidth + '" codecs="' + video.codecs + '" width="' + video.width + '" height="' + video.height + '" frameRate="' + (video.frame_rate || video.frameRate || 25) + '" sar="' + (video.sar || '1:1') + '">\n';
mpd += ' <BaseURL>' + (video.baseUrl || video.base_url || '').replace(/&/g, '&amp;') + '</BaseURL>\n';
mpd += ' <SegmentBase indexRange="' + (video.segment_base ? video.segment_base.index_range : '0-0') + '">\n';
mpd += ' <Initialization range="' + (video.segment_base ? video.segment_base.initialization : '0-0') + '"/>\n';
mpd += ' </SegmentBase>\n';
mpd += ' </Representation>\n';
mpd += ' </AdaptationSet>\n';
if (audio) {
mpd += ' <AdaptationSet contentType="audio" mimeType="' + audio.mime_type + '" segmentAlignment="true" startWithSAP="0">\n';
mpd += ' <Representation id="' + audio.id + '" bandwidth="' + audio.bandwidth + '" codecs="' + audio.codecs + '">\n';
mpd += ' <BaseURL>' + (audio.baseUrl || audio.base_url || '').replace(/&/g, '&amp;') + '</BaseURL>\n';
mpd += ' <SegmentBase indexRange="' + (audio.segment_base ? audio.segment_base.index_range : '0-0') + '">\n';
mpd += ' <Initialization range="' + (audio.segment_base ? audio.segment_base.initialization : '0-0') + '"/>\n';
mpd += ' </SegmentBase>\n';
mpd += ' </Representation>\n';
mpd += ' </AdaptationSet>\n';
}
mpd += ' </Period>\n</MPD>';
return mpd;
}
function base64Encode(str) {
var bytes = [];
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 0x80) bytes.push(c);
else if (c < 0x800) bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f));
else if (c < 0x10000) bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
else bytes.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f));
}
var base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var result = '';
for (var i = 0; i < bytes.length; i += 3) {
var b1 = bytes[i], b2 = bytes[i + 1] || 0, b3 = bytes[i + 2] || 0;
result += base64.charAt(b1 >> 2);
result += base64.charAt(((b1 & 0x3) << 4) | (b2 >> 4));
result += (i + 1 < bytes.length) ? base64.charAt(((b2 & 0xf) << 2) | (b3 >> 6)) : '=';
result += (i + 2 < bytes.length) ? base64.charAt(b3 & 0x3f) : '=';
}
return result;
}
export function __jsEvalReturn() {
return { init, home, homeVod, category, detail, search, play };
}