上传文件至「js」
This commit is contained in:
+228
@@ -0,0 +1,228 @@
|
||||
//本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
|
||||
//严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
|
||||
import {
|
||||
load as t
|
||||
} from "assets://js/lib/cat.js";
|
||||
let e, a = "https://www.985djw.com",
|
||||
i = "",
|
||||
n = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36",
|
||||
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"accept-language": "zh-CN,zh;q=0.9",
|
||||
"cache-control": "no-cache",
|
||||
pragma: "no-cache",
|
||||
priority: "u=0, i",
|
||||
"sec-ch-ua": '"Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"',
|
||||
"sec-ch-ua-mobile": "?0",
|
||||
"sec-ch-ua-platform": '"Windows"',
|
||||
"sec-fetch-dest": "document",
|
||||
"sec-fetch-mode": "navigate",
|
||||
"upgrade-insecure-requests": "1"
|
||||
};
|
||||
async function r(t) {
|
||||
"function" == typeof js2Proxy && "function" == typeof desX && "function" != typeof getProxy && (e = !0);
|
||||
let n = t.ext;
|
||||
if ("object" == typeof n && null !== n) {
|
||||
let t = n.host || "";
|
||||
t && t.startsWith("http") && (a = t), n.picProxy && n.picProxy.startsWith("http") && (i = n.picProxy)
|
||||
} else "string" == typeof n && n.startsWith("http") && (a = n);
|
||||
a.endsWith("/") && (a = a.slice(0, -1))
|
||||
}
|
||||
async function s(i) {
|
||||
let n = [],
|
||||
r = {},
|
||||
s = await u(a + "/"),
|
||||
o = t(s);
|
||||
if (o(".stui-header__menu ul li a").each((t, e) => {
|
||||
let a = o(e).attr("href"),
|
||||
i = o(e).text().trim();
|
||||
if (a && "/" !== a && -1 === a.indexOf(".html")) {
|
||||
let t = a.match(/\/nl\/([^\/]+)\//);
|
||||
t && n.push({
|
||||
type_id: t[1],
|
||||
type_name: i
|
||||
})
|
||||
}
|
||||
}), e && n.unshift({
|
||||
type_id: "home",
|
||||
type_name: "首页"
|
||||
}), n.length > 0) {
|
||||
let i = e && n.length > 1 ? n[1].type_id : n[0].type_id,
|
||||
s = await u(a + "/nl/" + i + "/", a + "/"),
|
||||
o = t(s),
|
||||
l = {
|
||||
"类型": "class",
|
||||
"地区": "area",
|
||||
"年份": "year",
|
||||
"语言": "lang",
|
||||
"排序": "by"
|
||||
},
|
||||
c = {
|
||||
class: 3,
|
||||
area: 1,
|
||||
year: 11,
|
||||
lang: 4,
|
||||
by: 2
|
||||
},
|
||||
p = [];
|
||||
o(".stui-screen__list").each((t, e) => {
|
||||
let a = o(e).find("li:first-child span.text-muted").text().replace("按", "").trim(),
|
||||
i = l[a];
|
||||
if (i) {
|
||||
let t = [],
|
||||
n = "";
|
||||
o(e).find("a").each((e, a) => {
|
||||
let r = o(a).text().trim(),
|
||||
s = (o(a).attr("href") || "").split("-")[c[i]] || "";
|
||||
"" === r && "" === s || ("全部" === r && (n = s), t.push({
|
||||
n: r,
|
||||
v: s
|
||||
}))
|
||||
}), t.length > 1 && p.push({
|
||||
key: i,
|
||||
name: a,
|
||||
init: n,
|
||||
value: t
|
||||
})
|
||||
}
|
||||
});
|
||||
for (let t = 0; t < n.length; t++) "home" !== n[t].type_id && (r[n[t].type_id] = p)
|
||||
}
|
||||
return JSON.stringify({
|
||||
class: n,
|
||||
filters: r
|
||||
})
|
||||
}
|
||||
async function o() {
|
||||
return await l("home", 1, null, {})
|
||||
}
|
||||
async function l(e, i, n, r) {
|
||||
let s = i || 1;
|
||||
if ("home" === e) {
|
||||
let e = await u(a + "/"),
|
||||
i = h(t(e));
|
||||
return JSON.stringify({
|
||||
page: 1,
|
||||
pagecount: 1,
|
||||
list: i
|
||||
})
|
||||
}
|
||||
let o = r || {},
|
||||
l = o.area || "",
|
||||
c = o.by || "",
|
||||
p = o.class || "",
|
||||
d = o.lang || "",
|
||||
m = o.letter || "",
|
||||
f = o.year || "",
|
||||
_ = a + `/vodshow/${e}-${l}-${c}-${p}-${d}-${m}---${s}---${f}/`,
|
||||
y = await u(_, a + "/nl/" + e + "/"),
|
||||
g = t(y),
|
||||
v = h(g),
|
||||
x = s,
|
||||
w = g(".stui-page .num").text();
|
||||
return w && w.includes("/") && (x = parseInt(w.split("/")[1])), JSON.stringify({
|
||||
page: parseInt(s),
|
||||
pagecount: parseInt(x),
|
||||
list: v
|
||||
})
|
||||
}
|
||||
async function c(e, i, n) {
|
||||
let r = n || 1,
|
||||
s = "";
|
||||
s = "1" === String(r) ? a + "/vodsearch/-------------/?wd=" + encodeURIComponent(e) + "&submit=" : a + `/vodsearch/${encodeURIComponent(e)}----------${r}---/`;
|
||||
let o = await u(s, a + "/"),
|
||||
l = t(o),
|
||||
c = h(l),
|
||||
p = r,
|
||||
d = l(".stui-page .num").text();
|
||||
return d && d.includes("/") && (p = parseInt(d.split("/")[1])), JSON.stringify({
|
||||
page: parseInt(r),
|
||||
pagecount: parseInt(p),
|
||||
list: c
|
||||
})
|
||||
}
|
||||
async function p(e) {
|
||||
let i = e.startsWith("http") ? e : a + e,
|
||||
n = await u(i, a + "/"),
|
||||
r = t(n),
|
||||
s = {
|
||||
vod_id: e,
|
||||
vod_name: r("h1.title a").text().trim(),
|
||||
vod_pic: m(r(".stui-content__thumb img").attr("data-original")),
|
||||
type_name: "",
|
||||
vod_year: "",
|
||||
vod_area: "",
|
||||
vod_remarks: "",
|
||||
vod_actor: "",
|
||||
vod_director: "",
|
||||
vod_content: r(".stui-content__desc").text().trim(),
|
||||
vod_play_from: "",
|
||||
vod_play_url: ""
|
||||
};
|
||||
r(".stui-content__detail p.data").each((t, e) => {
|
||||
let a = r(e).text();
|
||||
a.includes("类型:") && (s.type_name = r(e).find("a").text().trim()), a.includes("地区:") && (s.vod_area = r(e).find("a").text().trim()), a.includes("年份:") && (s.vod_year = r(e).find("a").text().trim()), a.includes("主演:") && (s.vod_actor = a.replace("主演:", "").trim()), a.includes("导演:") && (s.vod_director = a.replace("导演:", "").trim())
|
||||
});
|
||||
let o = [];
|
||||
return r(".stui-pannel").each((t, e) => {
|
||||
r(e).find(".stui-pannel__head h3.title").text().includes("手机在线播放器") && (s.vod_play_from = "碎光在线", r(e).find("ul.stui-content__playlist li a").each((t, e) => {
|
||||
let a = r(e).attr("href"),
|
||||
i = r(e).text().trim();
|
||||
a && o.push(i + "$" + a)
|
||||
}))
|
||||
}), s.vod_play_url = o.join("#"), JSON.stringify({
|
||||
list: [s]
|
||||
})
|
||||
}
|
||||
async function d(t, e, i) {
|
||||
let r = e.startsWith("http") ? e : a + e,
|
||||
s = "",
|
||||
o = (await u(r, a + "/")).match(/player_aaaa\s*=\s*(\{[\s\S]*?\})/);
|
||||
if (o) try {
|
||||
s = JSON.parse(o[1]).url || ""
|
||||
} catch (t) {
|
||||
console.error("解析 player_aaaa 核心数组失败: " + t.message)
|
||||
}
|
||||
return JSON.stringify({
|
||||
parse: 0,
|
||||
url: s,
|
||||
header: {
|
||||
"User-Agent": n["User-Agent"]
|
||||
}
|
||||
})
|
||||
}
|
||||
async function u(t, e) {
|
||||
let a = Object.assign({}, n);
|
||||
return e ? (a.referer = e, a["sec-fetch-site"] = "same-origin") : a["sec-fetch-site"] = "none", (await req(t, {
|
||||
headers: a
|
||||
})).content
|
||||
}
|
||||
|
||||
function h(t) {
|
||||
let e = [];
|
||||
return t(".stui-vodlist__item").each((a, i) => {
|
||||
let n = t(i).find("a.stui-vodlist__thumb"),
|
||||
r = n.attr("href");
|
||||
r && e.push({
|
||||
vod_id: r,
|
||||
vod_name: n.attr("title") || "",
|
||||
vod_pic: m(n.attr("data-original")),
|
||||
vod_remarks: n.find(".pic-text").text().trim() || ""
|
||||
})
|
||||
}), e
|
||||
}
|
||||
|
||||
function m(t) {
|
||||
return t ? (t.startsWith("//") ? t = "http:" + t : t.startsWith("/") && (t = a + t), i ? i + t : t) : ""
|
||||
}
|
||||
export function __jsEvalReturn() {
|
||||
return {
|
||||
init: r,
|
||||
home: s,
|
||||
homeVod: o,
|
||||
category: l,
|
||||
search: c,
|
||||
detail: p,
|
||||
play: d
|
||||
}
|
||||
}
|
||||
+446
@@ -0,0 +1,446 @@
|
||||
import { load, Crypto } from 'assets://js/lib/cat.js';
|
||||
|
||||
var HOST;
|
||||
const PC_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36';
|
||||
const DefHeader = { 'User-Agent': PC_UA };
|
||||
const KParams = {
|
||||
headers: { 'User-Agent': PC_UA },
|
||||
timeout: 5000
|
||||
};
|
||||
|
||||
async function init(cfg) {
|
||||
try {
|
||||
let host = cfg.ext?.host?.trim() || 'https://www.agedm.io';
|
||||
HOST = host.replace(/\/$/, '');
|
||||
KParams.headers.Referer = HOST;
|
||||
const parseTimeout = parseInt(cfg.ext?.timeout?.trim(), 10);
|
||||
KParams.timeout = parseTimeout > 0 ? parseTimeout : 5000;
|
||||
KParams.resHtml = await request(HOST);
|
||||
} catch (e) {
|
||||
console.error('初始化参数失败:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function home(filter) {
|
||||
try {
|
||||
const classes = [
|
||||
{ type_id: 'all', type_name: '全部' },
|
||||
{ type_id: 'TV', type_name: 'TV' },
|
||||
{ type_id: '剧场版', type_name: '剧场版' },
|
||||
{ type_id: 'OVA', type_name: 'OVA' }
|
||||
];
|
||||
const filterConfig = [
|
||||
{
|
||||
key: "area", name: "地区",
|
||||
value: [
|
||||
{ n: "全部", v: "all" }, { n: "日本", v: "日本" },
|
||||
{ n: "中国", v: "中国" }, { n: "欧美", v: "欧美" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "letter", name: "首字母",
|
||||
value: [
|
||||
{ n: "全部", v: "all" },
|
||||
{ n: "A", v: "A" }, { n: "B", v: "B" }, { n: "C", v: "C" }, { n: "D", v: "D" },
|
||||
{ n: "E", v: "E" }, { n: "F", v: "F" }, { n: "H", v: "H" }, { n: "I", v: "I" },
|
||||
{ n: "J", v: "J" }, { n: "K", v: "K" }, { n: "L", v: "L" }, { n: "M", v: "M" },
|
||||
{ n: "N", v: "N" }, { n: "O", v: "O" }, { n: "P", v: "P" }, { n: "Q", v: "Q" },
|
||||
{ n: "R", v: "R" }, { n: "S", v: "S" }, { n: "T", v: "T" }, { n: "U", v: "U" },
|
||||
{ n: "V", v: "V" }, { n: "W", v: "W" }, { n: "X", v: "X" }, { n: "Y", v: "Y" },
|
||||
{ n: "Z", v: "Z" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "year", name: "年份",
|
||||
value: [
|
||||
{ n: "全部", v: "all" },
|
||||
{ n: "2026", v: "2026" }, { n: "2025", v: "2025" }, { n: "2024", v: "2024" },
|
||||
{ n: "2023", v: "2023" }, { n: "2022", v: "2022" }, { n: "2021", v: "2021" },
|
||||
{ n: "2020", v: "2020" }, { n: "2019", v: "2019" }, { n: "2018", v: "2018" },
|
||||
{ n: "2017", v: "2017" }, { n: "2016", v: "2016" }, { n: "2015", v: "2015" },
|
||||
{ n: "2014", v: "2014" }, { n: "2013", v: "2013" }, { n: "2012", v: "2012" },
|
||||
{ n: "2011", v: "2011" }, { n: "2010", v: "2010" }, { n: "2009", v: "2009" },
|
||||
{ n: "2008", v: "2008" }, { n: "2007", v: "2007" }, { n: "2006", v: "2006" },
|
||||
{ n: "2005", v: "2005" }, { n: "2004", v: "2004" }, { n: "2003", v: "2003" },
|
||||
{ n: "2002", v: "2002" }, { n: "2001", v: "2001" }, { n: "2000以前", v: "2000以前" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "quarter", name: "季度",
|
||||
value: [
|
||||
{ n: "全部", v: "all" },
|
||||
{ n: "1月", v: "1" }, { n: "4月", v: "4" },
|
||||
{ n: "7月", v: "7" }, { n: "10月", v: "10" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "status", name: "状态",
|
||||
value: [
|
||||
{ n: "全部", v: "all" },
|
||||
{ n: "连载", v: "连载" }, { n: "完结", v: "完结" }, { n: "未播放", v: "未播放" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "type", name: "类型",
|
||||
value: [
|
||||
{ n: "全部", v: "all" },
|
||||
{ n: "搞笑", v: "搞笑" },{ n: "运动", v: "运动" },{ n: "励志", v: "励志" },
|
||||
{ n: "热血", v: "热血" },{ n: "战斗", v: "战斗" },{ n: "竞技", v: "竞技" },
|
||||
{ n: "校园", v: "校园" },{ n: "青春", v: "青春" },{ n: "爱情", v: "爱情" },
|
||||
{ n: "恋爱", v: "恋爱" },{ n: "冒险", v: "冒险" },{ n: "后宫", v: "后宫" },
|
||||
{ n: "百合", v: "百合" },{ n: "治愈", v: "治愈" },{ n: "萝莉", v: "萝莉" },
|
||||
{ n: "魔法", v: "魔法" },{ n: "悬疑", v: "悬疑" },{ n: "推理", v: "推理" },
|
||||
{ n: "奇幻", v: "奇幻" },{ n: "科幻", v: "科幻" },{ n: "游戏", v: "游戏" },
|
||||
{ n: "神魔", v: "神魔" },{ n: "恐怖", v: "恐怖" },{ n: "血腥", v: "血腥" },
|
||||
{ n: "机战", v: "机战" },{ n: "战争", v: "战争" },{ n: "犯罪", v: "犯罪" },
|
||||
{ n: "历史", v: "历史" },{ n: "社会", v: "社会" },{ n: "职场", v: "职场" },
|
||||
{ n: "剧情", v: "剧情" },{ n: "伪娘", v: "伪娘" },{ n: "耽美", v: "耽美" },
|
||||
{ n: "童年", v: "童年" },{ n: "教育", v: "教育" },{ n: "亲子", v: "亲子" },
|
||||
{ n: "真人", v: "真人" },{ n: "歌舞", v: "歌舞" },{ n: "肉番", v: "肉番" },
|
||||
{ n: "美少女", v: "美少女" },{ n: "轻小说", v: "轻小说" },{ n: "吸血鬼", v: "吸血鬼" },
|
||||
{ n: "女性向", v: "女性向" },{ n: "泡面番", v: "泡面番" },{ n: "欢乐向", v: "欢乐向" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "source", name: "资源",
|
||||
value: [
|
||||
{ n: "全部", v: "all" },
|
||||
{ n: "BDRIP", v: "BDRIP" }, { n: "AGE-RIP", v: "AGE-RIP" }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "sort", name: "排序",
|
||||
value: [
|
||||
{ n: "全部", v: "time" },
|
||||
{ n: "时间", v: "time" }, { n: "点击量", v: "点击量" }
|
||||
]
|
||||
}
|
||||
];
|
||||
const filters = { all: filterConfig, TV: filterConfig, "剧场版": filterConfig, OVA: filterConfig };
|
||||
return JSON.stringify({ class: classes, filters });
|
||||
} catch (e) {
|
||||
console.error('获取分类失败:', e.message);
|
||||
return JSON.stringify({ class: [], filters: {} });
|
||||
}
|
||||
}
|
||||
|
||||
async function homeVod() {
|
||||
try {
|
||||
const VODS = getVodList(KParams.resHtml, true);
|
||||
return JSON.stringify({ list: VODS });
|
||||
} catch (e) {
|
||||
console.error('推荐页获取失败:', e.message);
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
|
||||
async function category(tid, pg, filter, extend) {
|
||||
try {
|
||||
pg = parseInt(pg, 10) || 1;
|
||||
const year = extend?.year ? encodeURIComponent(extend.year) : 'all';
|
||||
const letter = extend?.letter ? encodeURIComponent(extend.letter) : 'all';
|
||||
const type = extend?.type ? encodeURIComponent(extend.type) : 'all';
|
||||
const source = extend?.source ? encodeURIComponent(extend.source) : 'all';
|
||||
const sort = extend?.sort ? encodeURIComponent(extend.sort) : 'time';
|
||||
const area = extend?.area ? encodeURIComponent(extend.area) : 'all';
|
||||
const quarter = extend?.quarter ? encodeURIComponent(extend.quarter) : 'all';
|
||||
const status = extend?.status ? encodeURIComponent(extend.status) : 'all';
|
||||
const cateUrl = `${HOST}/catalog/${tid}-${year}-${letter}-${type}-${source}-${sort}-${pg}-${area}-${quarter}-${status}`;
|
||||
const resHtml = await request(cateUrl);
|
||||
const VODS = getVodList(resHtml);
|
||||
return JSON.stringify({ list: VODS, page: pg, pagecount: 999, limit: 30, total: 30 * 999 });
|
||||
} catch (e) {
|
||||
console.error('类别页获取失败:', e.message);
|
||||
return JSON.stringify({ list: [], page: 1, pagecount: 0, limit: 30, total: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg) {
|
||||
try {
|
||||
pg = parseInt(pg, 10) || 1;
|
||||
const searchUrl = `${HOST}/search?query=${encodeURIComponent(wd)}&page=${pg}`;
|
||||
const resHtml = await request(searchUrl);
|
||||
const VODS = getVodList(resHtml);
|
||||
return JSON.stringify({ list: VODS, page: pg, pagecount: 10, limit: 30, total: 300 });
|
||||
} catch (e) {
|
||||
console.error('搜索页获取失败:', e.message);
|
||||
return JSON.stringify({ list: [], page: 1, pagecount: 0, limit: 30, total: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
function getVodList(khtml, hm = false) {
|
||||
try {
|
||||
if (!khtml) throw new Error('源码为空');
|
||||
const kvods = [];
|
||||
const $ = load(khtml);
|
||||
const listArr = hm ? $('.video_item').get() : $('.video_cover').get();
|
||||
for (const it of listArr) {
|
||||
const $it = $(it);
|
||||
const a = $it.find('a');
|
||||
const kname = dealStr(a.text().trim() || a.attr('title'), '名称');
|
||||
const kurl = dealStr(a.attr('href'), 'href');
|
||||
const poster = $it.find('img');
|
||||
let pic = poster.attr('data-original') || poster.attr('src');
|
||||
if (pic && pic.startsWith('/')) pic = HOST + pic;
|
||||
const kpic = dealStr(pic, '图片');
|
||||
const kremarks = dealStr($it.find('span').text(), '状态');
|
||||
kvods.push({
|
||||
vod_name: kname,
|
||||
vod_pic: kpic,
|
||||
vod_remarks: kremarks,
|
||||
vod_id: `${kurl}@${kname}@${kpic}@${kremarks}`
|
||||
});
|
||||
}
|
||||
return kvods;
|
||||
} catch (e) {
|
||||
console.error(`生成视频列表失败:`, e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function detail(ids) {
|
||||
try {
|
||||
const [id, kname, kpic, kremarks] = ids.split('@');
|
||||
const rawUrl = /^http/.test(id) ? id : `${HOST}${id}`;
|
||||
const detailUrl = rawUrl.replace(/https?:\/\/[^\/]+/, HOST);
|
||||
const resHtml = await request(detailUrl);
|
||||
if (!resHtml) throw new Error('源码请求失败');
|
||||
const $ = load(resHtml);
|
||||
const intros = $('.detail_imform_list').html() || '';
|
||||
const ktabs = $('.nav-item:has(button)').get().map((it, idx) => {
|
||||
const btn = $(it).find('button');
|
||||
return dealStr(`${btn.text()}(${btn.attr('data-bs-target').replace('#playlist-source-','')})`, `线-${idx+1}`);
|
||||
});
|
||||
const kurls = $('.video_detail_episode').get().map(item => {
|
||||
const arr = $(item).find('li a').get().map(it => {
|
||||
const $it = $(it);
|
||||
return dealStr($it.text(), 'noEpi') + '$' + dealStr($it.attr('href'), 'noUrl');
|
||||
});
|
||||
return arr.join('#');
|
||||
});
|
||||
const VOD = {
|
||||
vod_id: detailUrl,
|
||||
vod_name: kname,
|
||||
vod_pic: kpic,
|
||||
type_name: cutStr(intros, '剧情类型:', '</li', '类型'),
|
||||
vod_remarks: kremarks,
|
||||
vod_year: cutStr(intros, '首播时间:', '</li', '1000'),
|
||||
vod_area: cutStr(intros, '地区:', '</li', '地区'),
|
||||
vod_lang: '语言',
|
||||
vod_director: cutStr(intros, '原作:', '</li', '原作'),
|
||||
vod_actor: cutStr(intros, '主演:', '</li', '主演'),
|
||||
vod_content: cutStr(intros, 'video_detail_desc£>', '</div>', '简介'),
|
||||
vod_play_from: ktabs.join('$$$'),
|
||||
vod_play_url: kurls.join('$$$')
|
||||
};
|
||||
return JSON.stringify({ list: [VOD] });
|
||||
} catch (e) {
|
||||
console.error('详情页获取失败:', e.message);
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
|
||||
function uuid() {
|
||||
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
const arr = [];
|
||||
arr[8] = arr[13] = arr[18] = arr[23] = "-";
|
||||
arr[14] = "4";
|
||||
for (let i = 0; i < 36; i++) {
|
||||
if (!arr[i]) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
arr[i] = chars[(i === 19 ? (r & 0x3 | 0x8) : r)];
|
||||
}
|
||||
}
|
||||
return arr.join("");
|
||||
}
|
||||
|
||||
async function play(flag, ids, flags) {
|
||||
try {
|
||||
const playUrl = /^http/.test(ids) ? ids : `${HOST}${ids}`;
|
||||
let kurl = '';
|
||||
const resHtml = await request(playUrl);
|
||||
const $ = load(resHtml);
|
||||
const iframesrc = $('#iframeForVideo').attr('src') || '';
|
||||
if (iframesrc) {
|
||||
const iframeresHtml = await request(iframesrc);
|
||||
let rawVurl = cutStr(iframeresHtml, 'Vurl = ', ';');
|
||||
let Vurl = rawVurl.replace(/^["']|["']$/g, '').trim();
|
||||
if (!/m3u8|mp4|mkv/.test(Vurl)) {
|
||||
const Ref = cutStr(iframeresHtml, 'Ref = "', '";');
|
||||
const Time = cutStr(iframeresHtml, 'Time = "', '";');
|
||||
const Version = cutStr(iframeresHtml, 'Version = "', '";');
|
||||
const idList = cutStr(iframeresHtml, 'id="', '"', 'null', true, 1, true);
|
||||
const mockHtmlMeta = { contentTypeId: idList[0], viewportId: idList[1] };
|
||||
const apiUrl = 'https://jx.wuzhoupai.com:8443/vip/Api.php';
|
||||
const jxhost = 'jx.wuzhoupai.com';
|
||||
const currentUuid = uuid();
|
||||
const reqData = { url: Vurl, wap: '0', ios: '0', host: jxhost, referer: Ref, time: Time };
|
||||
const encryptParams = hxm_encrypt(JSON.stringify(reqData)).toUpperCase();
|
||||
const signRaw = `${jxhost} | ${currentUuid} | ${Time} | ${Version} | ${encryptParams}`;
|
||||
const sign = hxm_encrypt(signRaw);
|
||||
const apiRes = await request(apiUrl, {
|
||||
method: 'post',
|
||||
postType: 'json',
|
||||
headers: {
|
||||
'x-requested-with': 'XMLHttpRequest',
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||
'Video-Parse-Uuid': currentUuid,
|
||||
'Video-Parse-Time': Time,
|
||||
'Video-Parse-Version': Version,
|
||||
'Video-Parse-Sign': sign
|
||||
},
|
||||
data: { Params: encryptParams }
|
||||
});
|
||||
const apiResjson = safeParseJSON(apiRes);
|
||||
const result = decryptData(apiResjson, mockHtmlMeta);
|
||||
kurl = result.url;
|
||||
} else {
|
||||
kurl = Vurl;
|
||||
}
|
||||
}
|
||||
return JSON.stringify({ jx: 0, parse: 0, url: kurl, header: DefHeader });
|
||||
} catch (e) {
|
||||
console.error('播放失败:', e.message);
|
||||
return JSON.stringify({ jx: 0, parse: 0, url: '', header: {} });
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
function dealStr(str, defaultValue = '') {
|
||||
if (str === null || typeof str === 'undefined' || String(str).trim() === '') return defaultValue;
|
||||
return String(str).replace(/( |\u00A0|\s)+/g, ' ').trim().replace(/\s+/g, ' ') || defaultValue;
|
||||
}
|
||||
|
||||
function cutStr(str, prefix = '', suffix = '', defaultVal = 'cutFaile', clean = true, i = 1, all = false) {
|
||||
try {
|
||||
if (typeof str !== 'string' || !str) throw new Error('被截取对象需为非空字符串');
|
||||
const cleanStr = cs => String(cs).replace(/<[^>]*?>/g, ' ').replace(/( |\u00A0|\s)+/g, ' ').trim().replace(/\s+/g, ' ');
|
||||
const esc = s => String(s).replace(/[.*+?${}()|[\]\\/^]/g, '\\$&');
|
||||
const pre = esc(prefix).replace(/£/g, '[^]*?');
|
||||
const end = esc(suffix);
|
||||
const regex = new RegExp(`${pre ? pre : '^'}([^]*?)${end ? end : '$'}`, 'g');
|
||||
const matchIterator = str.matchAll(regex);
|
||||
if (all) {
|
||||
const matchArr = [...matchIterator];
|
||||
return matchArr.length ? matchArr.map(it => {
|
||||
const val = it[1] ?? defaultVal;
|
||||
return clean && val !== defaultVal ? cleanStr(val) : val;
|
||||
}) : [defaultVal];
|
||||
}
|
||||
i = parseInt(i, 10);
|
||||
if (isNaN(i) || i < 1) throw new Error('序号必须为正整数');
|
||||
let tgIdx = i - 1, matchIdx = 0;
|
||||
for (const match of matchIterator) {
|
||||
if (matchIdx++ === tgIdx) {
|
||||
const result = match[1] ?? defaultVal;
|
||||
return clean && result !== defaultVal ? cleanStr(result) : result;
|
||||
}
|
||||
}
|
||||
return defaultVal;
|
||||
} catch (e) {
|
||||
console.error(`字符串截取失败:`, e.message);
|
||||
return all ? ['cutErr'] : 'cutErr';
|
||||
}
|
||||
}
|
||||
|
||||
function safeParseJSON(jStr) {
|
||||
try { return JSON.parse(jStr); } catch (e) { return null; }
|
||||
}
|
||||
|
||||
async function request(reqUrl, options = {}) {
|
||||
try {
|
||||
if (typeof reqUrl !== 'string' || !reqUrl.trim()) throw new Error('reqUrl需为字符串且非空');
|
||||
if (typeof options !== 'object' || Array.isArray(options) || !options) throw new Error('options类型需为非null对象');
|
||||
options.method = options.method?.toLowerCase() || 'get';
|
||||
if (['get', 'head'].includes(options.method)) {
|
||||
delete options.data;
|
||||
delete options.postType;
|
||||
} else {
|
||||
options.data = options.data ?? '';
|
||||
options.postType = options.postType?.toLowerCase() || 'form';
|
||||
}
|
||||
let { headers, timeout, toBase64 = false, ...restOpts } = options;
|
||||
const optObj = {
|
||||
headers: (headers && typeof headers === 'object' && !Array.isArray(headers)) ? headers : KParams.headers,
|
||||
timeout: parseInt(timeout, 10) > 0 ? parseInt(timeout, 10) : KParams.timeout,
|
||||
buffer: toBase64 ? 2 : 0,
|
||||
...restOpts
|
||||
};
|
||||
const res = await req(reqUrl, optObj);
|
||||
if (options.withHeaders) {
|
||||
const resHeaders = res.headers && typeof res.headers === 'object' && !Array.isArray(res.headers) ? res.headers : {};
|
||||
return JSON.stringify({ ...resHeaders, body: res?.content ?? '' });
|
||||
}
|
||||
return res?.content ?? '';
|
||||
} catch (e) {
|
||||
console.error(`${reqUrl}→请求失败:`, e.message);
|
||||
return options?.withHeaders ? JSON.stringify({ body: '' }) : '';
|
||||
}
|
||||
}
|
||||
|
||||
// AES解密工具
|
||||
function decryptOuterAesCbc(hexCipherText, md5String) {
|
||||
const hexWords = Crypto.enc.Hex.parse(hexCipherText);
|
||||
const base64CipherText = Crypto.enc.Base64.stringify(hexWords);
|
||||
const rawKey = md5String.substring(0, 16);
|
||||
const rawIv = md5String.substring(16, 32);
|
||||
const cryptoKey = Crypto.enc.Latin1.parse(rawKey);
|
||||
const cryptoIv = Crypto.enc.Latin1.parse(rawIv);
|
||||
const decryptedWords = Crypto.AES.decrypt(base64CipherText, cryptoKey, { iv: cryptoIv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 });
|
||||
return Crypto.enc.Utf8.stringify(decryptedWords);
|
||||
}
|
||||
|
||||
function hxm_decrypt(encryptedUrl) {
|
||||
if (!encryptedUrl) return "";
|
||||
const secretStr = "ni po jie ni ** ";
|
||||
const cryptoKey = Crypto.enc.Utf8.parse(secretStr);
|
||||
const cryptoIv = Crypto.enc.Utf8.parse(secretStr);
|
||||
let finalCipherText = encryptedUrl;
|
||||
if (/^[0-9a-fA-F]+$/.test(encryptedUrl)) {
|
||||
const hexWords = Crypto.enc.Hex.parse(encryptedUrl);
|
||||
finalCipherText = Crypto.enc.Base64.stringify(hexWords);
|
||||
}
|
||||
const decryptedWords = Crypto.AES.decrypt(finalCipherText, cryptoKey, { iv: cryptoIv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 });
|
||||
return Crypto.enc.Utf8.stringify(decryptedWords);
|
||||
}
|
||||
|
||||
function hxm_encrypt(encryptedUrl) {
|
||||
if (!encryptedUrl) return "";
|
||||
const secretStr = "ni po jie ni ** ";
|
||||
const cryptoKey = Crypto.enc.Utf8.parse(secretStr);
|
||||
const cryptoIv = Crypto.enc.Utf8.parse(secretStr);
|
||||
let finalCipherText = encryptedUrl;
|
||||
if (/^[0-9a-fA-F]+$/.test(encryptedUrl)) {
|
||||
const hexWords = Crypto.enc.Hex.parse(encryptedUrl);
|
||||
finalCipherText = Crypto.enc.Base64.stringify(hexWords);
|
||||
}
|
||||
const encrypted = Crypto.AES.encrypt(finalCipherText, cryptoKey, { iv: cryptoIv, mode: Crypto.mode.CBC, padding: Crypto.pad.Pkcs7 });
|
||||
return Crypto.enc.Hex.stringify(encrypted.ciphertext).toUpperCase();
|
||||
}
|
||||
|
||||
function decryptData(responseData, htmlMeta = { contentTypeId: '', viewportId: '' }) {
|
||||
let decryptedInfo = {};
|
||||
if (responseData.Status !== 1) throw new Error("Status 不为 1,数据异常");
|
||||
if (responseData.Code === 10) {
|
||||
let metaFeature = (htmlMeta.contentTypeId + htmlMeta.viewportId).replace("viewport", "");
|
||||
const rawSaltKey = responseData.Code + metaFeature + responseData.Appkey + responseData.Version;
|
||||
const md5String = md5X(rawSaltKey);
|
||||
const decryptedStr = decryptOuterAesCbc(responseData.Data, md5String);
|
||||
decryptedInfo = JSON.parse(decryptedStr);
|
||||
if (!decryptedInfo.url.startsWith('http')) decryptedInfo.url = hxm_decrypt(decryptedInfo.url);
|
||||
decryptedInfo.url = decodeURIComponent(decryptedInfo.url);
|
||||
} else {
|
||||
const rawSaltKey = responseData.Code + responseData.Appkey + responseData.Version;
|
||||
const md5String = md5X(rawSaltKey);
|
||||
const decryptedStr = decryptOuterAesCbc(responseData.Data, md5String);
|
||||
decryptedInfo = JSON.parse(decryptedStr);
|
||||
if (!decryptedInfo.url.startsWith('http')) decryptedInfo.url = hxm_decrypt(decryptedInfo.url);
|
||||
decryptedInfo.url = decodeURIComponent(decryptedInfo.url);
|
||||
}
|
||||
return decryptedInfo;
|
||||
}
|
||||
|
||||
export function __jsEvalReturn() {
|
||||
return { init, home, homeVod, category, search, detail, play, proxy: null };
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
let host = 'https://a123tv.com';
|
||||
let headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Linux; Android 13; M2102J2SC Build/TKQ1.221114.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/144.0.7559.31 Mobile Safari/537.36',
|
||||
'Referer': host
|
||||
};
|
||||
|
||||
function getList(html) {
|
||||
let videos = [];
|
||||
pdfa(html, '.w4-item-wrap').forEach(it => {
|
||||
const id = pdfh(it, 'a.w4-item&&href') || '',
|
||||
name = pdfh(it, 'img&&alt') || '',
|
||||
pic = pdfh(it, 'img&&data-src') || pdfh(it, 'img&&src') || '',
|
||||
remark = pdfh(it, '.w4-item-cover .s span&&Text') || '';
|
||||
if (id && name) {
|
||||
let vod_pic = pic.startsWith('//') ? `https:${pic}` : pic.startsWith('/') ? `${host}${pic}` : pic;
|
||||
vod_pic = vod_pic || 'data:image/gif;base64,R0lGODdhAQABAPAAAMPDwwAAACwAAAAAAQABAAACAkQBADs=';
|
||||
videos.push({
|
||||
vod_id: id.replace(host, '').replace(/^\//, ''),
|
||||
vod_name: name.trim(),
|
||||
vod_pic,
|
||||
vod_remarks: remark.trim()
|
||||
});
|
||||
}
|
||||
});
|
||||
return videos;
|
||||
}
|
||||
|
||||
async function init(cfg) {}
|
||||
async function home(filter) {
|
||||
return JSON.stringify({
|
||||
class: [{ "type_id": "10", "type_name": "电影" }, { "type_id": "11", "type_name": "连续剧" },
|
||||
{ "type_id": "12", "type_name": "综艺" }, { "type_id": "13", "type_name": "动漫" },
|
||||
{ "type_id": "15", "type_name": "福利" }],
|
||||
filters: {}
|
||||
});
|
||||
}
|
||||
async function homeVod() {
|
||||
let resp = await req(host, { headers });
|
||||
return JSON.stringify({ list: getList(resp.content) });
|
||||
}
|
||||
async function category(tid, pg, filter, extend) {
|
||||
let p = parseInt(pg) || 1;
|
||||
let resp = await req(`${host}/t/${tid}/p${p}.html`, { headers });
|
||||
return JSON.stringify({ list: getList(resp.content), page: p, pagecount: p + 5, limit: 20 });
|
||||
}
|
||||
|
||||
async function detail(id) {
|
||||
const playFrom = [], playList = [];
|
||||
let detailUrl = id.startsWith('http') ? id : `${host}/${id.replace(/^\//, '')}`;
|
||||
let { content: html = '' } = await req(detailUrl, { headers });
|
||||
|
||||
// 基础信息
|
||||
let vod_name = (pdfh(html, 'li.on h1&&Text') || pdfh(html, 'h1.title&&Text') || '未知片名').trim();
|
||||
let vod_pic = pdfh(html, '#awp1&&data-poster') || '';
|
||||
vod_pic = vod_pic.startsWith('//') ? `https:${vod_pic}` : vod_pic.startsWith('/') ? `${host}${vod_pic}` : vod_pic;
|
||||
let vod_remarks = pdfh(html, '.w4-line-cover .i&&Text')?.split(' / ')[1] || '720p';
|
||||
|
||||
// 解析meta中的导演/演员/地区/年代/类型/简介
|
||||
let vod_area = '', vod_year = '', type_name = '', vod_actor = '', vod_director = '', vod_content = '';
|
||||
// 提取description
|
||||
const descMatch = html.match(/<meta name="description" content="([^"]+)"[^>]*>/i);
|
||||
const descText = descMatch ? descMatch[1] : '';
|
||||
// 从标题提取类型和年代
|
||||
const titleMatch = html.match(/《[^》]+》\s*-\s*([^-]+)\s*-\s*(\d+)年/i);
|
||||
if (titleMatch) {
|
||||
type_name = titleMatch[1].trim();
|
||||
vod_year = titleMatch[2].trim();
|
||||
}
|
||||
// 解析描述中的地区、导演、演员、剧情
|
||||
if (descText) {
|
||||
const areaMatch = descText.match(/地区:([^。]+)。/);
|
||||
const dirMatch = descText.match(/导演:([^。]+)。/);
|
||||
const actorMatch = descText.match(/演员:([^。]+)。/);
|
||||
const contentMatch = descText.match(/剧情:([\s\S]+)/);
|
||||
|
||||
vod_area = areaMatch ? areaMatch[1].trim() : '';
|
||||
vod_director = dirMatch ? dirMatch[1].trim().replace(/、/g, '/') : '';
|
||||
vod_actor = actorMatch ? actorMatch[1].trim().replace(/、/g, '/') : '';
|
||||
vod_content = contentMatch ? contentMatch[1].trim() : descText;
|
||||
}
|
||||
|
||||
const ppMatch = html.match(/var\s+pp\s*=\s*({[\s\S]*?});/);
|
||||
if (ppMatch) {
|
||||
const { la = [] } = JSON.parse(ppMatch[1]);
|
||||
const episodeNames = pdfa(html, '.w4-episode-list .w a').map((a, i) =>
|
||||
(pdfh(a, 'a&&Text') || pdfh(a, 'a&&title') || `第${i+1}集`).trim()
|
||||
);
|
||||
|
||||
la.forEach((item, index) => {
|
||||
if (Array.isArray(item) && item.length >= 5) {
|
||||
const [, , episodeNum, , m3u8] = item;
|
||||
const realEpNum = parseInt(episodeNum) || episodeNames.length;
|
||||
const lineEpisodes = [];
|
||||
for (let i = 0; i < realEpNum; i++) {
|
||||
let epName = episodeNames[i] || `第${i+1}集`;
|
||||
let realM3u8 = m3u8.startsWith('//') ? `https:${m3u8}` : m3u8;
|
||||
lineEpisodes.push(`${epName}$${realM3u8}`);
|
||||
}
|
||||
playFrom.push(`✨推荐✨${index + 1}`);
|
||||
playList.push(lineEpisodes.join('#'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
list: [{
|
||||
vod_id: id, vod_name, vod_pic, vod_remarks, vod_year, vod_area,
|
||||
type_name, vod_actor, vod_director, vod_content,
|
||||
vod_play_from: playFrom.join('$$$'), vod_play_url: playList.join('$$$')
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg) {
|
||||
let p = parseInt(pg) || 1;
|
||||
let resp = await req(`${host}/s/${encodeURIComponent(wd)}/p${p}.html`, { headers });
|
||||
return JSON.stringify({ list: getList(resp.content), page: p, pagecount: p + 5, limit: 20 });
|
||||
}
|
||||
|
||||
async function play(flag, id, flags) {
|
||||
let url = id.startsWith('http') ? id : `${host}/${id.replace(/^\//, '')}`;
|
||||
let { content: html = '' } = await req(url, { headers });
|
||||
|
||||
const playerVarMatch = html.match(/var\s+player_aaaa\s*=\s*({[^;]+});/);
|
||||
if (playerVarMatch) {
|
||||
let jsonStr = playerVarMatch[1].replace(/(\w+):/g, '"$1":').replace(/\\/g, '');
|
||||
let { url: playUrl } = JSON.parse(jsonStr);
|
||||
if (playUrl) return JSON.stringify({ parse: playUrl.includes('.m3u8') ? 0 : 1, url: playUrl, header: headers });
|
||||
}
|
||||
|
||||
let m3u8Match = html.match(/"url":"([^"]+\.m3u8(\?[^"]*)?)"/) || html.match(/'url':'([^']+\.m3u8(\?[^']*)?)'/) ||
|
||||
html.match(/"(https?:\/\/[^"]+\.m3u8[^"]*)"/) || html.match(/'(https?:\/\/[^']+\.m3u8[^']*)'/) ||
|
||||
html.match(/https?:\/\/[^\s"]+\.m3u8[^\s"]*/);
|
||||
if (m3u8Match) {
|
||||
let playUrl = m3u8Match[1] || m3u8Match[0];
|
||||
return JSON.stringify({ parse: 0, url: playUrl.replace(/\\/g, ""), header: headers });
|
||||
}
|
||||
|
||||
let iframeMatch = html.match(/<iframe[^>]*src="([^"]+)"[^>]*>/i) || html.match(/<video[^>]*src="([^"]+)"[^>]*>/i);
|
||||
if (iframeMatch) {
|
||||
let iframeUrl = iframeMatch[1];
|
||||
iframeUrl = iframeUrl.startsWith('//') ? `https:${iframeUrl}` : !iframeUrl.startsWith('http') ? `${host}${iframeUrl.startsWith('/') ? '' : '/'}${iframeUrl}` : iframeUrl;
|
||||
return JSON.stringify({ parse: 1, url: iframeUrl, header: headers });
|
||||
}
|
||||
|
||||
return JSON.stringify({ parse: 1, url: url, header: headers });
|
||||
}
|
||||
|
||||
export default { init, home, homeVod, category, detail, search, play };
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
import { load } from 'assets://js/lib/cat.js';
|
||||
import 'assets://js/lib/crypto-js.js';
|
||||
|
||||
var BILI_COOKIE = '';
|
||||
var BILI_GUEST_COOKIE = ''; // 自动获取的访客 Cookie
|
||||
var extConfig = {};
|
||||
|
||||
const PLACEHOLDER = 'https://i0.hdslb.com/bfs/static/jinkela/video/asserts/nocover.png';
|
||||
const DEFAULT_TYPE = '音乐#新闻#白噪音#沙雕动漫#虾仁#短剧#电影#歌曲#演唱会#鬼畜#搞笑#脑洞乌托邦#相声#小品#戏曲#音乐#MV#舞曲#舞蹈#纪录片#健身#帕梅拉#武术#太极拳#广场舞#体育#球星#世界杯#UP主#小姐姐#女优#美食#食谱#荒野求生#旅游#风景#游戏#解说#演讲#考公考试#平面设计#软件教学'; // 分类兜底
|
||||
|
||||
function decodeHtml(str) {
|
||||
if (!str) return '';
|
||||
return str.replace(/"/g, '"')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/'/g, "'")
|
||||
.replace(/ /g, ' ');
|
||||
}
|
||||
|
||||
function fmtDuration(str) {
|
||||
if (!str) return '';
|
||||
let parts = str.split(':').map(Number);
|
||||
let minutes = 0;
|
||||
if (parts.length === 3) {
|
||||
minutes = parts[0] * 60 + parts[1] + Math.round(parts[2] / 60);
|
||||
} else if (parts.length === 2) {
|
||||
minutes = parts[0] + Math.round(parts[1] / 60);
|
||||
}
|
||||
return minutes > 0 ? minutes + '分钟' : '';
|
||||
}
|
||||
|
||||
function formatCount(num) {
|
||||
if (!num && num !== 0) return '';
|
||||
let n = Number(num);
|
||||
if (n >= 10000) {
|
||||
let w = n / 10000;
|
||||
return (w % 1 === 0 ? w.toFixed(0) : w.toFixed(1)) + '万';
|
||||
}
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
const mixinKeyEncTab = [
|
||||
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5,
|
||||
49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55,
|
||||
40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62,
|
||||
11, 36, 20, 34, 44, 52
|
||||
];
|
||||
|
||||
let wbiKeyCache = { key: '', hour: -1 };
|
||||
|
||||
function getMixinKey(orig) {
|
||||
let temp = '';
|
||||
for (let i = 0; i < mixinKeyEncTab.length; i++) {
|
||||
if (mixinKeyEncTab[i] < orig.length) temp += orig[mixinKeyEncTab[i]];
|
||||
}
|
||||
return temp.slice(0, 32);
|
||||
}
|
||||
|
||||
async function updateWbiKey() {
|
||||
const now = new Date();
|
||||
const hour = now.getHours();
|
||||
if (wbiKeyCache.key && wbiKeyCache.hour === hour) return;
|
||||
|
||||
// 用当前有效的 Cookie 请求 wbi 密钥
|
||||
const ck = BILI_COOKIE || BILI_GUEST_COOKIE || 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc';
|
||||
try {
|
||||
const resp = await req('https://api.bilibili.com/x/web-interface/nav', {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer': 'https://www.bilibili.com',
|
||||
'Origin': 'https://www.bilibili.com',
|
||||
'Cookie': ck
|
||||
}
|
||||
});
|
||||
const data = JSON.parse(resp.content);
|
||||
const imgUrl = data.data.wbi_img.img_url;
|
||||
const subUrl = data.data.wbi_img.sub_url;
|
||||
const imgKey = imgUrl.split('/').pop().split('.')[0];
|
||||
const subKey = subUrl.split('/').pop().split('.')[0];
|
||||
wbiKeyCache.key = getMixinKey(imgKey + subKey);
|
||||
wbiKeyCache.hour = hour;
|
||||
} catch (e) {
|
||||
wbiKeyCache.key = '';
|
||||
}
|
||||
}
|
||||
|
||||
function encWbi(params, key) {
|
||||
if (!key) return '';
|
||||
const sortedKeys = Object.keys(params).sort();
|
||||
const query = sortedKeys.map(k => {
|
||||
const v = params[k];
|
||||
return encodeURIComponent(k) + '=' + encodeURIComponent(v).replace(/[!'()*]/g, '');
|
||||
}).join('&');
|
||||
const w_rid = CryptoJS.MD5(query + key).toString();
|
||||
return query + '&w_rid=' + w_rid;
|
||||
}
|
||||
|
||||
async function fetchGuestCookie() {
|
||||
try {
|
||||
const resp = await req('https://space.bilibili.com/2/video', {
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
||||
},
|
||||
timeout: 8000
|
||||
});
|
||||
if (resp && resp.headers) {
|
||||
let cookies = resp.headers['set-cookie'] || [];
|
||||
if (typeof cookies === 'string') cookies = [cookies];
|
||||
const parts = cookies.map(c => c.split(';')[0].trim()).filter(c => c);
|
||||
if (parts.length > 0) BILI_GUEST_COOKIE = parts.join('; ');
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
function getHeaders(useCookie) {
|
||||
let ck = BILI_COOKIE || BILI_GUEST_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',
|
||||
'Referer': 'https://www.bilibili.com',
|
||||
'Origin': 'https://www.bilibili.com',
|
||||
'Cookie': useCookie ? ck : 'buvid3=84B0395D-C9F2-C490-E92E-A09AB48FE26E71636infoc'
|
||||
};
|
||||
}
|
||||
|
||||
function searchHeaders() {
|
||||
let ck = BILI_COOKIE || BILI_GUEST_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/117.0.0.0 Safari/537.36',
|
||||
'Referer': 'https://www.bilibili.com',
|
||||
'Origin': 'https://www.bilibili.com',
|
||||
'Cookie': ck
|
||||
};
|
||||
}
|
||||
|
||||
async function init(config) {
|
||||
let cfg = typeof config === 'string' ? JSON.parse(config) : (config || {});
|
||||
extConfig = cfg;
|
||||
let cookieSource = cfg.cookie || ''; // 这里填cookie或外链URL
|
||||
if (cookieSource) {
|
||||
if (cookieSource.startsWith('http://') || cookieSource.startsWith('https://')) {
|
||||
try {
|
||||
let resp = await req(cookieSource, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000 });
|
||||
if (resp && resp.content) BILI_COOKIE = resp.content.trim();
|
||||
} catch (e) {}
|
||||
} else {
|
||||
BILI_COOKIE = cookieSource;
|
||||
}
|
||||
}
|
||||
|
||||
if (!BILI_COOKIE) {
|
||||
await fetchGuestCookie();
|
||||
}
|
||||
|
||||
await updateWbiKey();
|
||||
return '{}';
|
||||
}
|
||||
|
||||
async function home(filter) {
|
||||
let typeStr = extConfig.type || ''; // 这里填分类URL
|
||||
for (let retry = 0; retry < 2; retry++) {
|
||||
if (typeStr.startsWith('http')) {
|
||||
try {
|
||||
let resp = await req(typeStr, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000 });
|
||||
if (resp && resp.content) {
|
||||
let data = JSON.parse(resp.content);
|
||||
if (data && data.class) return JSON.stringify(data);
|
||||
}
|
||||
} catch (e) {
|
||||
if (retry === 1) print('网络分类加载失败,使用内置分类');
|
||||
else await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
} else break;
|
||||
}
|
||||
let names = (typeStr.indexOf('#') > -1 ? typeStr : DEFAULT_TYPE).split('#').filter(n => n.trim());
|
||||
let classes = names.map(n => ({ type_id: n, type_name: n }));
|
||||
|
||||
let filters = {};
|
||||
names.forEach(n => {
|
||||
filters[n] = [
|
||||
{
|
||||
key: 'order',
|
||||
name: '排序',
|
||||
value: [
|
||||
{ n: '综合排序', v: 'totalrank' },
|
||||
{ n: '最多点击', v: 'click' },
|
||||
{ n: '最新发布', v: 'pubdate' },
|
||||
{ n: '最多弹幕', v: 'dm' },
|
||||
{ n: '最多收藏', v: 'stow' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'duration',
|
||||
name: '时长',
|
||||
value: [
|
||||
{ n: '全部时长', v: '0' },
|
||||
{ n: '60分钟以上', v: '4' },
|
||||
{ n: '30~60分钟', v: '3' },
|
||||
{ n: '10~30分钟', v: '2' },
|
||||
{ n: '10分钟以下', v: '1' }
|
||||
]
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
return JSON.stringify({ class: classes, filters: filters });
|
||||
}
|
||||
|
||||
async function homeVod() { return JSON.stringify({ list: [] }); }
|
||||
|
||||
async function category(tid, pg, filterParams, extendParams) {
|
||||
if (typeof tid === 'string' && tid.endsWith('_clicklink')) tid = tid.split('_')[0];
|
||||
let page = parseInt(pg) || 1;
|
||||
let sel = {};
|
||||
if (filterParams) Object.assign(sel, filterParams);
|
||||
if (extendParams) Object.assign(sel, extendParams);
|
||||
let order = sel.order || 'totalrank';
|
||||
let duration = sel.duration || '0';
|
||||
let keyword = tid;
|
||||
if (sel.tid) keyword += ' ' + sel.tid;
|
||||
|
||||
await updateWbiKey();
|
||||
const params = {
|
||||
search_type: 'video',
|
||||
keyword: keyword,
|
||||
order: order,
|
||||
duration: duration,
|
||||
page: page
|
||||
};
|
||||
const query = encWbi(params, wbiKeyCache.key);
|
||||
const url = 'https://api.bilibili.com/x/web-interface/wbi/search/type?' + query;
|
||||
|
||||
for (let retry = 0; retry < 3; retry++) {
|
||||
try {
|
||||
let resp = await req(url, { headers: searchHeaders(), timeout: 8000 });
|
||||
if (!resp || !resp.content) continue;
|
||||
let data = JSON.parse(resp.content);
|
||||
let items = (data.data && data.data.result) || [];
|
||||
let list = items.map(v => {
|
||||
let img = v.pic || '';
|
||||
if (img.startsWith('//')) img = 'https:' + img;
|
||||
if (!img) img = PLACEHOLDER;
|
||||
let durationStr = fmtDuration(v.duration);
|
||||
let playStr = formatCount(v.play);
|
||||
let danmuStr = formatCount(v.danmaku);
|
||||
let remarkParts = [];
|
||||
if (durationStr) remarkParts.push(durationStr);
|
||||
if (playStr) remarkParts.push(playStr + '播放');
|
||||
if (danmuStr) remarkParts.push(danmuStr + '弹幕');
|
||||
return {
|
||||
vod_id: v.bvid + '@' + v.aid,
|
||||
vod_name: decodeHtml(v.title.replace(/<[^>]*>/g, '')),
|
||||
vod_pic: img,
|
||||
vod_remarks: remarkParts.join(' | ')
|
||||
};
|
||||
});
|
||||
let hasMore = list.length >= 20;
|
||||
if (!list.length) list.push({ vod_id: '0', vod_name: '无结果', vod_pic: PLACEHOLDER });
|
||||
return JSON.stringify({ list, page, pagecount: hasMore ? page + 1 : page, limit: 20, total: list.length });
|
||||
} catch (e) {
|
||||
if (retry === 2) {
|
||||
return JSON.stringify({ list: [{ vod_id: '0', vod_name: '请求失败', vod_pic: PLACEHOLDER }], page: 1, pagecount: 1 });
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function detail(ids) {
|
||||
let idStr = Array.isArray(ids) ? ids[0] : ids;
|
||||
if (!idStr || idStr.indexOf('@') === -1) return JSON.stringify({ list: [] });
|
||||
let parts = idStr.split('@'), aid = parts[1];
|
||||
try {
|
||||
let viewUrl = 'https://api.bilibili.com/x/web-interface/view?aid=' + aid;
|
||||
let resp = await req(viewUrl, { headers: getHeaders(true) });
|
||||
let info = JSON.parse(resp.content).data;
|
||||
if (!info) return JSON.stringify({ list: [] });
|
||||
|
||||
let title = decodeHtml((info.title || '').replace(/<[^>]*>/g, ''));
|
||||
let pic = info.pic || PLACEHOLDER;
|
||||
if (pic.startsWith('//')) pic = 'https:' + pic;
|
||||
let desc = info.desc || '';
|
||||
let duration = info.duration || 0;
|
||||
let owner = info.owner || {};
|
||||
let stat = info.stat || {};
|
||||
let tname = info.tname || '';
|
||||
let pubdate = info.pubdate || 0;
|
||||
let upName = owner.name || '未知';
|
||||
|
||||
let follower = '';
|
||||
try {
|
||||
let statUrl = 'https://api.bilibili.com/x/relation/stat?vmid=' + owner.mid;
|
||||
let statResp = await req(statUrl, { headers: getHeaders(false) });
|
||||
let statData = JSON.parse(statResp.content).data;
|
||||
if (statData && statData.follower > 0) {
|
||||
follower = formatCount(statData.follower);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
let director = '🆙 ' + '[a=cr:' + JSON.stringify({id: upName + '_clicklink', name: upName}) + '/]' + upName + '[/a]';
|
||||
if (follower) director += ' 👥 ' + follower;
|
||||
|
||||
let actor = '▶' + (stat.view || 0) + ' 💬' + (stat.danmaku || 0) + ' 👍' + (stat.like || 0) + ' 💰' + (stat.coin || 0) + ' ⭐' + (stat.favorite || 0);
|
||||
let year = pubdate ? new Date(pubdate * 1000).getFullYear().toString() : '';
|
||||
|
||||
let pages = info.pages || [];
|
||||
let bilibiliPlayUrls = pages.map(p => {
|
||||
let epTitle = (p.part || 'P' + p.page).replaceAll('#', '﹟').replaceAll('$', '﹩');
|
||||
return epTitle + '$' + aid + '+' + p.cid;
|
||||
});
|
||||
|
||||
let relatedPlayUrls = [];
|
||||
try {
|
||||
let relatedUrl = 'https://api.bilibili.com/x/web-interface/archive/related?aid=' + aid;
|
||||
let relResp = await req(relatedUrl, { headers: getHeaders(true) });
|
||||
let relData = JSON.parse(relResp.content).data;
|
||||
if (relData && Array.isArray(relData)) {
|
||||
relData.forEach(rd => {
|
||||
let cid = rd.cid;
|
||||
let rdTitle = decodeHtml((rd.title || '').replaceAll('#', '﹟').replaceAll('$', '﹩'));
|
||||
let aaid = rd.aid;
|
||||
relatedPlayUrls.push(rdTitle + '$' + aaid + '+' + cid);
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
let playFrom = 'B站';
|
||||
let playUrl = bilibiliPlayUrls.join('#');
|
||||
if (relatedPlayUrls.length > 0) {
|
||||
playFrom += '$$$相关推荐';
|
||||
playUrl += '$$$' + relatedPlayUrls.join('#');
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
list: [{
|
||||
vod_id: idStr,
|
||||
vod_name: title,
|
||||
vod_pic: pic,
|
||||
vod_remarks: Math.floor(duration / 60) + '分钟',
|
||||
vod_content: desc,
|
||||
vod_play_from: playFrom,
|
||||
vod_play_url: playUrl,
|
||||
vod_director: director,
|
||||
vod_actor: actor,
|
||||
type_name: tname,
|
||||
vod_year: year
|
||||
}]
|
||||
});
|
||||
} catch (e) {
|
||||
return JSON.stringify({ list: [] });
|
||||
}
|
||||
}
|
||||
|
||||
async function play(flag, id) {
|
||||
if (!id || id.indexOf('+') === -1) return JSON.stringify({ parse: 0, url: '' });
|
||||
let parts = id.split('+'), aid = parts[0], cid = parts[1];
|
||||
try {
|
||||
let dashUrl = 'https://api.bilibili.com/x/player/playurl?avid=' + aid + '&cid=' + cid + '&qn=127&fnval=4048&fourk=1';
|
||||
let resp = await req(dashUrl, { headers: getHeaders(true) });
|
||||
let data = JSON.parse(resp.content);
|
||||
if (!data.data || !data.data.dash) return JSON.stringify({ parse: 0, url: '' });
|
||||
|
||||
let dash = data.data.dash;
|
||||
let videos = dash.video || [];
|
||||
let duration = dash.duration || 0;
|
||||
let minBuf = dash.min_buffer_time || 1.5;
|
||||
|
||||
let rawAudios = dash.audio ? [...dash.audio] : [];
|
||||
if (dash.flac && dash.flac.audio) {
|
||||
if (Array.isArray(dash.flac.audio)) rawAudios.push(...dash.flac.audio);
|
||||
else rawAudios.push(dash.flac.audio);
|
||||
}
|
||||
if (dash.dolby && dash.dolby.audio) {
|
||||
if (Array.isArray(dash.dolby.audio)) rawAudios.push(...dash.dolby.audio);
|
||||
else rawAudios.push(dash.dolby.audio);
|
||||
}
|
||||
|
||||
let audioList = [];
|
||||
let seenAudio = new Set();
|
||||
|
||||
const audioPriority = { 30251: 1, 30250: 2, 30280: 3, 30232: 4, 30216: 5 };
|
||||
rawAudios.sort((a, b) => {
|
||||
let pA = audioPriority[a.id] || 99;
|
||||
let pB = audioPriority[b.id] || 99;
|
||||
return pA - pB;
|
||||
});
|
||||
|
||||
for (let a of rawAudios) {
|
||||
if (a && a.id && !seenAudio.has(a.id)) {
|
||||
seenAudio.add(a.id);
|
||||
audioList.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
let formats = data.data.support_formats || [];
|
||||
if (formats.length === 0) {
|
||||
let aq = data.data.accept_quality || [];
|
||||
let ad = data.data.accept_description || [];
|
||||
formats = aq.map((q, i) => ({ quality: q, display_desc: ad[i] || q.toString() }));
|
||||
}
|
||||
let qnMap = {};
|
||||
formats.forEach(f => { qnMap[f.quality] = f.display_desc || f.new_description || f.quality.toString(); });
|
||||
|
||||
let urlsArray = [];
|
||||
let seenStreams = new Set();
|
||||
|
||||
videos.sort((a, b) => b.id - a.id);
|
||||
for (let v of videos) {
|
||||
let qn = v.id;
|
||||
|
||||
let codecName = '';
|
||||
if (v.codecs) {
|
||||
if (v.codecs.includes('avc')) codecName = 'AVC';
|
||||
else if (v.codecs.includes('hev') || v.codecs.includes('hvc')) codecName = 'HEVC';
|
||||
else if (v.codecs.includes('av01')) codecName = 'AV1';
|
||||
else if (v.codecs.includes('dvh') || v.codecs.includes('dve')) codecName = '杜比';
|
||||
}
|
||||
|
||||
let streamKey;
|
||||
if (qn <= 80) {
|
||||
streamKey = qn.toString();
|
||||
codecName = '';
|
||||
} else {
|
||||
streamKey = qn + '_' + codecName;
|
||||
}
|
||||
|
||||
if (!seenStreams.has(streamKey)) {
|
||||
seenStreams.add(streamKey);
|
||||
|
||||
let baseName = qnMap[qn] || ('画质' + qn);
|
||||
if (qn === 126 || codecName === '杜比') {
|
||||
baseName = '杜比视界';
|
||||
} else if (qn === 125) {
|
||||
baseName = 'HDR真彩色(' + (codecName || 'HEVC') + ')';
|
||||
} else {
|
||||
if (codecName) baseName += '(' + codecName + ')';
|
||||
}
|
||||
|
||||
let mpd = buildMpd(v, audioList, duration, minBuf);
|
||||
let dataUri = 'data:application/dash+xml;base64,' + base64Encode(mpd);
|
||||
urlsArray.push({ name: baseName, url: dataUri, qn: qn, bw: v.bandwidth || 0 });
|
||||
}
|
||||
}
|
||||
|
||||
if (urlsArray.length === 0) return JSON.stringify({ parse: 0, url: '' });
|
||||
|
||||
urlsArray.sort((a, b) => {
|
||||
if (b.qn !== a.qn) return b.qn - a.qn;
|
||||
return b.bw - a.bw;
|
||||
});
|
||||
|
||||
let arr = [];
|
||||
urlsArray.forEach(q => {
|
||||
arr.push(q.name);
|
||||
arr.push(q.url);
|
||||
});
|
||||
|
||||
return JSON.stringify({
|
||||
parse: 0,
|
||||
jx: 0,
|
||||
url: arr,
|
||||
danmaku: 'https://api.bilibili.com/x/v1/dm/list.so?oid=' + cid,
|
||||
header: JSON.stringify(getHeaders(true))
|
||||
});
|
||||
} catch (e) { return JSON.stringify({ parse: 0, url: '' }); }
|
||||
}
|
||||
|
||||
function buildMpd(video, audioList, duration, minBuf) {
|
||||
let 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, '&') + '</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 (audioList && audioList.length > 0) {
|
||||
for (let audio of audioList) {
|
||||
let label = '';
|
||||
if (audio.id === 30251) label = '无损Hi-Res';
|
||||
else if (audio.id === 30250) label = '杜比全景声';
|
||||
else if (audio.id === 30280) label = '192k高音质';
|
||||
else if (audio.id === 30232) label = '132k标准';
|
||||
else if (audio.id === 30216) label = '64k流畅';
|
||||
else label = '音质_' + audio.id;
|
||||
|
||||
mpd += ' <AdaptationSet contentType="audio" mimeType="' + audio.mime_type + '" segmentAlignment="true" startWithSAP="0" lang="' + label + '">\n';
|
||||
mpd += ' <Label>' + label + '</Label>\n';
|
||||
mpd += ' <Representation id="audio_' + audio.id + '" bandwidth="' + audio.bandwidth + '" codecs="' + audio.codecs + '">\n';
|
||||
mpd += ' <BaseURL>' + (audio.baseUrl || audio.base_url).replace(/&/g, '&') + '</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) {
|
||||
let bytes = [];
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let 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));
|
||||
}
|
||||
}
|
||||
let base64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
let result = '';
|
||||
for (let i = 0; i < bytes.length; i += 3) {
|
||||
let 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;
|
||||
}
|
||||
|
||||
async function search(keyword, quick, pg) { return category(keyword, pg || '1', {}, {}); }
|
||||
async function searchContent(keyword, quick, page) { return search(keyword, quick, page); }
|
||||
|
||||
export function __jsEvalReturn() {
|
||||
return { init, home, homeVod, category, detail, play, search, searchContent };
|
||||
}
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
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, '&') + '</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, '&') + '</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 };
|
||||
}
|
||||
Reference in New Issue
Block a user