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, + }; +}