diff --git a/README.md b/README.md
index 2abff2e..3e17d98 100644
--- a/README.md
+++ b/README.md
@@ -32,3 +32,6 @@ Not being maintained and uncertain whether they will be updated.
- iOS only test on `16.0+`.
- Builtin config `*.ipa/Payload/Runner.app/Frameworks/App.framework/flutter_assets/asset/js/config_open.json`
+- Android only test on `8.0+`, maybe not supported to run on emulators, not support TV.
+
+ - Builtin config `*.apk/assets/flutter_assets/asset/js/config_open.json`
diff --git a/open/bili_open.js b/open/bili_open.js
new file mode 100644
index 0000000..c65c7e1
--- /dev/null
+++ b/open/bili_open.js
@@ -0,0 +1,508 @@
+import { Crypto, _ } from './lib/cat.js';
+
+let siteKey = '';
+let siteType = 0;
+
+let cookie = '';
+let login = '';
+let vip = '';
+let extendObj = {};
+let vod_audio_id = {
+ 30280: 192000,
+ 30232: 132000,
+ 30216: 64000,
+};
+let vod_codec = {
+ // 13: 'AV1',
+ 12: 'HEVC',
+ 7: 'AVC',
+};
+
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36';
+
+async function request(reqUrl, ua) {
+ let res = await req(reqUrl, {
+ method: 'get',
+ headers: ua ? ua : { 'User-Agent': UA },
+ timeout: 60000,
+ });
+ return res.content;
+}
+
+function getHeaders() {
+ const headers = {
+ 'User-Agent': UA,
+ };
+ if (!_.isEmpty(cookie)) {
+ headers.cookie = cookie;
+ }
+ return headers;
+}
+
+async function getCookie() {
+ let result = await req('https://www.bilibili.com', {
+ method: 'get',
+ headers: { 'User-Agent': UA },
+ timeout: 60000,
+ });
+ const setCookieHeaders = result.headers['set-cookie'];
+ cookie = setCookieHeaders.map((kk) => kk.split(';')[0] + ';').join('');
+}
+
+async function init(cfg) {
+ siteKey = cfg.skey;
+ siteType = cfg.stype;
+ let extend = cfg.ext;
+ if (cfg.ext.hasOwnProperty('categories')) extend = cfg.ext.categories;
+ if (cfg.ext.hasOwnProperty('cookie')) cookie = cfg.ext.cookie;
+ if (_.isEmpty(cookie)) await getCookie();
+ let result = JSON.parse(await request('https://api.bilibili.com/x/web-interface/nav', getHeaders()));
+ login = result.data.isLogin;
+ vip = result.data.hasOwnProperty('vipStatus');
+ const ext = extend.split('#');
+ const jsonData = [
+ {
+ key: 'order',
+ name: '排序',
+ value: [
+ { n: '综合排序', v: '0' },
+ { 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' },
+ ],
+ },
+ ];
+ const newarr = [];
+ const d = {};
+ for (const kk of ext) {
+ const c = {
+ type_name: kk,
+ type_id: kk,
+ land: 1,
+ ratio: 1.78,
+ };
+ newarr.push(c);
+ d[kk] = jsonData;
+ }
+ extendObj = {
+ classes: newarr,
+ filter: d,
+ };
+}
+
+function home(filter) {
+ try {
+ const jSONObject = {
+ class: extendObj.classes,
+ };
+ if (filter) {
+ jSONObject.filters = extendObj.filter;
+ }
+ return JSON.stringify(jSONObject);
+ } catch (e) {
+ return '';
+ }
+}
+
+async function homeVod() {
+ try {
+ const list = [];
+ const type_id = extendObj.classes[0].type_id;
+ const url = `https://api.bilibili.com/x/web-interface/search/type?search_type=video&keyword=${type_id}&duration=4`;
+
+ const response = await request(url, getHeaders());
+ const responseData = JSON.parse(response);
+ const vods = responseData.data.result;
+
+ for (const item of vods) {
+ const vod = {};
+ let imageUrl = item.pic;
+ if (imageUrl.startsWith('//')) {
+ imageUrl = 'https:' + imageUrl;
+ }
+
+ vod.vod_id = item.bvid;
+ vod.vod_name = removeTags(item.title);
+ vod.vod_pic = imageUrl;
+ vod.vod_remarks = item.duration.split(':')[0] + '分钟';
+ list.push(vod);
+ }
+
+ const result = { list: list };
+ return JSON.stringify(result);
+ } catch (e) {}
+}
+
+async function category(tid, page, filter, ext) {
+ if (page < 1) page = 1;
+ try {
+ if (Object.keys(ext).length > 0 && ext.hasOwnProperty('tid') && ext['tid'].length > 0) {
+ tid = ext['tid'];
+ }
+ let url = `https://api.bilibili.com/x/web-interface/search/type?search_type=video&keyword=${encodeURIComponent(tid)}`;
+
+ if (Object.keys(ext).length > 0) {
+ for (const k in ext) {
+ if (k == 'tid') {
+ continue;
+ }
+ url += `&${encodeURIComponent(k)}=${encodeURIComponent(ext[k])}`;
+ }
+ }
+
+ url += `&page=${encodeURIComponent(page)}`;
+
+ const response = await request(url, getHeaders());
+
+ const resp = JSON.parse(response);
+ const data = resp.data;
+
+ const videos = [];
+ const items = data.result;
+
+ for (const item of items) {
+ const video = {};
+ let pic = item.pic;
+ if (pic.startsWith('//')) {
+ pic = 'https:' + pic;
+ }
+
+ video.vod_id = item.bvid;
+ video.vod_name = removeTags(item.title);
+ video.vod_pic = pic;
+ video.vod_remarks = item.duration.split(':')[0] + '分钟';
+ videos.push(video);
+ }
+
+ const result = {
+ page: page,
+ pagecount: data.numPages,
+ limit: data.pagesize,
+ total: data.numResults,
+ list: videos,
+ };
+
+ return JSON.stringify(result);
+ } catch (e) {}
+ return null;
+}
+
+async function detail(ids) {
+ try {
+ const bvid = ids;
+ const bvid2aidUrl = `https://api.bilibili.com/x/web-interface/archive/stat?bvid=${bvid}`;
+
+ const bvid2aidResp = JSON.parse(await request(bvid2aidUrl, getHeaders()));
+
+ const aid = bvid2aidResp.data.aid + '';
+ const detailUrl = `https://api.bilibili.com/x/web-interface/view?aid=${aid}`;
+ const detailData = JSON.parse(await request(detailUrl, getHeaders())).data;
+
+ const video = {
+ vod_id: bvid,
+ vod_name: detailData.title,
+ vod_pic: detailData.pic,
+ type_name: detailData.tname,
+ vod_year: '',
+ vod_area: '',
+ vod_remarks: `${Math.floor(detailData.duration / 60)}分钟`,
+ vod_actor: '',
+ vod_director: '',
+ vod_content: detailData.desc,
+ };
+
+ const playurldata = 'https://api.bilibili.com/x/player/playurl?avid=' + aid + '&cid=' + detailData.cid + '&qn=127&fnval=4048&fourk=1';
+ const playurldatas = JSON.parse(await request(playurldata, getHeaders()));
+
+ const playurldatalist = playurldatas.data;
+ const a = playurldatalist.accept_description;
+ const accept_quality = playurldatalist.accept_quality;
+ const Aq = [];
+ const pFrom = [];
+
+ for (let i = 0; i < accept_quality.length; i++) {
+ if (!login) {
+ if (accept_quality[i] > 32) continue;
+ } else if (!vip && login) {
+ if (accept_quality[i] > 80) continue;
+ } else {
+ if (accept_quality[i] > 32) continue;
+ }
+ pFrom.push(a[i]);
+ Aq.push(accept_quality[i]);
+ }
+
+ const jSONArray = detailData.pages;
+ const playList = [];
+ for (let j = 0; j < jSONArray.length; j++) {
+ const jSONObject6 = jSONArray[j];
+ const j2 = jSONObject6.cid;
+ const playUrl = j + '$' + aid + '+' + j2 + '+' + Aq.join(':') + '+' + pFrom.join(':');
+ playList.push(playUrl);
+ }
+
+ video.vod_play_from = 'external$$$dash$$$mp4';
+ video.vod_play_url = playList.join('#');
+ video.vod_play_url = [video.vod_play_url, video.vod_play_url, video.vod_play_url].join('$$$');
+
+ const list = [video];
+ const result = { list };
+ return JSON.stringify(result);
+ } catch (e) {}
+ return null;
+}
+
+async function play(flag, id, flags) {
+ try {
+ const playHeaders = { Referer: 'https://www.bilibili.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' };
+ const ids = id.split('+');
+ const aid = ids[0];
+ const cid = ids[1];
+ const qualityIds = ids[2].split(':');
+ const qualityName = ids[3].split(':');
+ if (flag == 'dash') {
+ // dash mpd 代理
+ const js2Base = await js2Proxy(true, siteType, siteKey, 'dash/', {});
+ let urls = [];
+ for (let i = 0; i < qualityIds.length; i++) {
+ urls.push(qualityName[i], js2Base + base64Encode(aid + '+' + cid + '+' + qualityIds[i]));
+ }
+ return JSON.stringify({
+ parse: 0,
+ url: urls,
+ header: playHeaders,
+ });
+ } else if (flag == 'mp4') {
+ // 直链
+ let urls = [];
+ for (let i = 0; i < qualityIds.length; i++) {
+ const url = `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=${cid}&qn=${qualityIds[i]}&fourk=1`;
+ const resp = JSON.parse(await request(url, getHeaders()));
+ const data = resp.data;
+ if (data.quality != qualityIds[i]) continue;
+ let durl = data.durl[0].url;
+ urls.push(qualityName[i], durl);
+ }
+
+ return JSON.stringify({
+ parse: 0,
+ url: urls,
+ header: playHeaders,
+ });
+ } else {
+ // 音频外挂
+ let urls = [];
+ let audios = [];
+ for (let i = 0; i < qualityIds.length; i++) {
+ const url = `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=${cid}&qn=${qualityIds[i]}&fnval=4048&fourk=1`;
+ let resp = JSON.parse(await request(url, getHeaders()));
+ const dash = resp.data.dash;
+ const video = dash.video;
+ const audio = dash.audio;
+ for (let j = 0; j < video.length; j++) {
+ const dashjson = video[j];
+ if (dashjson.id == qualityIds[i]) {
+ for (const key in vod_codec) {
+ if (dashjson.codecid == key) {
+ urls.push(qualityName[i] + ' ' + vod_codec[key], dashjson.baseUrl);
+ }
+ }
+ }
+ }
+ if (audios.length == 0) {
+ for (let j = 0; j < audio.length; j++) {
+ const dashjson = audio[j];
+ for (const key in vod_audio_id) {
+ if (dashjson.id == key) {
+ audios.push({
+ title: _.floor(parseInt(vod_audio_id[key]) / 1024) + 'Kbps',
+ bit: vod_audio_id[key],
+ url: dashjson.baseUrl,
+ });
+ }
+ }
+ }
+ audios = _.sortBy(audios, 'bit');
+ }
+ }
+
+ return JSON.stringify({
+ parse: 0,
+ url: urls,
+ extra: {
+ audio: audios,
+ },
+ header: playHeaders,
+ });
+ }
+ } catch (e) {}
+ return null;
+}
+
+async function search(key, quick, pg) {
+ let page = pg || 1;
+ if (page == 0) page = 1;
+ try {
+ const ext = {
+ duration: '4',
+ };
+ let resp = JSON.parse(await category(key, page, true, ext));
+ const catVideos = resp.list;
+ const pageCount = resp.pagecount;
+ const videos = [];
+ for (let i = 0; i < catVideos.length; ++i) {
+ videos.push(catVideos[i]);
+ }
+ const result = {
+ page: page,
+ pagecount: pageCount,
+ land: 1,
+ ratio: 1.78,
+ list: videos,
+ };
+ return JSON.stringify(result);
+ } catch (e) {}
+ return null;
+}
+
+async function proxy(segments, headers) {
+ let what = segments[0];
+ let url = base64Decode(segments[1]);
+ if (what == 'dash') {
+ const ids = url.split('+');
+ const aid = ids[0];
+ const cid = ids[1];
+ const str5 = ids[2];
+ const urls = `https://api.bilibili.com/x/player/playurl?avid=${aid}&cid=${cid}&qn=${str5}&fnval=4048&fourk=1`;
+ let videoList = '';
+ let audioList = '';
+
+ let resp = JSON.parse(await request(urls, getHeaders()));
+ const dash = resp.data.dash;
+ const video = dash.video;
+ const audio = dash.audio;
+
+ for (let i = 0; i < video.length; i++) {
+ // if (i > 0) continue; // 只取一个
+ const dashjson = video[i];
+ if (dashjson.id == str5) {
+ videoList += getDashMedia(dashjson);
+ }
+ }
+
+ for (let i = 0; i < audio.length; i++) {
+ // if (i > 0) continue;
+ const ajson = audio[i];
+ for (const key in vod_audio_id) {
+ if (ajson.id == key) {
+ audioList += getDashMedia(ajson);
+ }
+ }
+ }
+
+ let mpd = getDash(resp, videoList, audioList);
+ return JSON.stringify({
+ code: 200,
+ content: mpd,
+ headers: {
+ 'Content-Type': 'application/dash+xml',
+ },
+ });
+ }
+ return JSON.stringify({
+ code: 500,
+ content: '',
+ });
+}
+
+function getDashMedia(dash) {
+ try {
+ let qnid = dash.id;
+ const codecid = dash.codecid;
+ const media_codecs = dash.codecs;
+ const media_bandwidth = dash.bandwidth;
+ const media_startWithSAP = dash.startWithSap;
+ const media_mimeType = dash.mimeType;
+ const media_BaseURL = dash.baseUrl.replace(/&/g, '&');
+ const media_SegmentBase_indexRange = dash.SegmentBase.indexRange;
+ const media_SegmentBase_Initialization = dash.SegmentBase.Initialization;
+ const mediaType = media_mimeType.split('/')[0];
+ let media_type_params = '';
+
+ if (mediaType == 'video') {
+ const media_frameRate = dash.frameRate;
+ const media_sar = dash.sar;
+ const media_width = dash.width;
+ const media_height = dash.height;
+ media_type_params = `height='${media_height}' width='${media_width}' frameRate='${media_frameRate}' sar='${media_sar}'`;
+ } else if (mediaType == 'audio') {
+ for (const key in vod_audio_id) {
+ if (qnid == key) {
+ const audioSamplingRate = vod_audio_id[key];
+ media_type_params = `numChannels='2' sampleRate='${audioSamplingRate}'`;
+ }
+ }
+ }
+ qnid += '_' + codecid;
+
+ return `
+
+
+ ${media_BaseURL}
+
+
+
+
+ `;
+ } catch (e) {
+ // Handle exceptions here
+ }
+}
+
+function getDash(ja, videoList, audioList) {
+ const duration = ja.data.dash.duration;
+ const minBufferTime = ja.data.dash.minBufferTime;
+ return `
+
+ ${videoList}
+ ${audioList}
+
+ `;
+}
+
+function base64Encode(text) {
+ return Crypto.enc.Base64.stringify(Crypto.enc.Utf8.parse(text));
+}
+
+function base64Decode(text) {
+ return Crypto.enc.Utf8.stringify(Crypto.enc.Base64.parse(text));
+}
+
+function removeTags(input) {
+ return input.replace(/<[^>]*>/g, '');
+}
+
+export function __jsEvalReturn() {
+ return {
+ init: init,
+ home: home,
+ homeVod: homeVod,
+ category: category,
+ detail: detail,
+ play: play,
+ proxy: proxy,
+ search: search,
+ };
+}