384 lines
11 KiB
JavaScript
384 lines
11 KiB
JavaScript
import { Crypto, Uri, _ } from 'assets://js/lib/cat.js';
|
|
|
|
let key = 'littleapple';
|
|
let HOST = ''
|
|
let siteKey = '';
|
|
let siteType = 0;
|
|
|
|
const APP_VER = '1.3.3';
|
|
const FULL_VER = 'XPGBOX com.phoenix.tv' + APP_VER;
|
|
const UA = 'okhttp/3.12.11';
|
|
let SIGN;
|
|
let TOKEN;
|
|
let TOKEN2;
|
|
|
|
async function request(reqUrl, method, data) {
|
|
const res = await req(reqUrl, {
|
|
method: method || 'get',
|
|
headers: getHeaders(),
|
|
data: data,
|
|
postType: method === 'post' ? 'form' : '',
|
|
});
|
|
return res.content;
|
|
}
|
|
|
|
function getHeaders() {
|
|
if (!SIGN || !TOKEN || !TOKEN2) {
|
|
SIGN = generateSignContent();
|
|
TOKEN = generateToken(SIGN);
|
|
TOKEN2 = generateToken('eT/G/wE7YQpBbk02LN1uGw8s2cOPPAj9jVVkjWjY6BQ=');
|
|
}
|
|
const timestamp = Math.floor(new Date().getTime() / 1000).toString();
|
|
const hash = getHash(SIGN, timestamp);
|
|
const headers = {
|
|
'token': TOKEN,
|
|
'token2': TOKEN2,
|
|
'user_id': 'XPGBOX',
|
|
'version': FULL_VER,
|
|
'timestamp': timestamp,
|
|
'hash': hash,
|
|
'screenx': 2331,
|
|
'screeny': 1121,
|
|
'User-Agent': UA,
|
|
};
|
|
return headers;
|
|
}
|
|
|
|
function getHash(signContent, timestamp) {
|
|
const signStr = signContent + FULL_VER + timestamp;
|
|
const md5 = Crypto.MD5(signStr).toString();
|
|
return md5.substring(8, 12);
|
|
}
|
|
|
|
function generateSignContent() {
|
|
const separator = '||';
|
|
const serial = 'unknown';
|
|
const fingerprint = 'HUAWEI/INE-LX2/HWINE:9/HUAWEIINE-LX2/9.1.0.318C636:user/release-keys';
|
|
|
|
const strArr = [];
|
|
strArr.push(randStr(16));
|
|
strArr.push(randStr(16));
|
|
|
|
const intCount = 6;
|
|
const int8Arr = new Int8Array(intCount);
|
|
for (let i = 0; i < intCount; i++) {
|
|
const random = _.random(0, 127);
|
|
int8Arr[i] = random;
|
|
}
|
|
const byte = int8Arr[0];
|
|
int8Arr[0] = (byte & (byte ^ 1));
|
|
const byteStr = _.map(int8Arr, (value) => value.toString(16).padStart(2, '0')).join('').toUpperCase();
|
|
strArr.push(byteStr);
|
|
|
|
strArr.push(randStr(16));
|
|
strArr.push(serial);
|
|
strArr.push(fingerprint);
|
|
return strArr.join(separator);
|
|
}
|
|
|
|
const charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789';
|
|
function randStr(len, withNum) {
|
|
var _str = '';
|
|
let containsNum = withNum === undefined ? true : withNum;
|
|
for (var i = 0; i < len; i++) {
|
|
let idx = _.random(0, containsNum ? charStr.length - 1 : charStr.length - 11);
|
|
_str += charStr[idx];
|
|
}
|
|
return _str;
|
|
}
|
|
|
|
function generateToken(text) {
|
|
const data = Crypto.enc.Utf8.parse(text);
|
|
const uint8Array = wordArrayToUint8Array(data);
|
|
const encoded = encodeBytes(uint8Array);
|
|
const words = uint8ArrayToWordArray(encoded);
|
|
return Crypto.enc.Base64.stringify(words);
|
|
}
|
|
|
|
function wordArrayToUint8Array(wordArray) {
|
|
let words = wordArray.words;
|
|
let sigBytes = wordArray.sigBytes;
|
|
let uint8Array = new Uint8Array(sigBytes);
|
|
for (let i = 0; i < sigBytes; i++) {
|
|
let byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
|
|
uint8Array[i] = byte;
|
|
}
|
|
return uint8Array;
|
|
}
|
|
|
|
function uint8ArrayToWordArray(uint8Array) {
|
|
let len = uint8Array.length;
|
|
let words = [];
|
|
for (let i = 0; i < len; i++) {
|
|
words[i >>> 2] |= (uint8Array[i] & 0xff) << (24 - (i % 4) * 8);
|
|
}
|
|
return Crypto.lib.WordArray.create(words, len);
|
|
}
|
|
|
|
function encodeBytes(byteArray) {
|
|
let result = null;
|
|
const key = Crypto.enc.Utf8.parse('XPINGGUO');
|
|
const keyBytes = wordArrayToUint8Array(key);
|
|
const table = new Int8Array(333);
|
|
for (let i = 0; i < 333; i++) {
|
|
table[i] = i;
|
|
}
|
|
let index1 = 0;
|
|
let index2 = 0;
|
|
const keyLength = keyBytes.length;
|
|
for (let i = 0; i < 333; i++) {
|
|
let keyByte = keyBytes[index2];
|
|
let tableByte = table[i];
|
|
index1 = ((index1 + (((((keyByte & (keyByte ^ (-256))) - 4) + (tableByte & (tableByte ^ (-256)))) + 4) + 17)) - 17) % 333;
|
|
let temp = table[i];
|
|
table[i] = table[index1];
|
|
table[index1] = temp;
|
|
index2 = (index2 + 1) % keyLength;
|
|
}
|
|
result = table;
|
|
const encryptedData = new Int8Array(byteArray.length);
|
|
index1 = 0;
|
|
index2 = 0;
|
|
for (let i = 0; i < byteArray.length; i++) {
|
|
let pos = (index1 + 1) % 333;
|
|
const tableByte = result[pos];
|
|
index2 = ((tableByte & (tableByte ^ -256)) + index2) % 333;
|
|
const temp = result[pos];
|
|
result[pos] = result[index2];
|
|
result[index2] = temp;
|
|
const b6 = result[pos];
|
|
const b7 = result[index2];
|
|
const b8 = result[(-((-(b6 & (b6 ^ (-256)))) - (b7 & (b7 ^ (-256))))) % 333];
|
|
const inputByte = byteArray[i];
|
|
encryptedData[i] = (b8 & ~inputByte) | (~b8 & inputByte);
|
|
index1 = pos;
|
|
}
|
|
return encryptedData;
|
|
}
|
|
|
|
// cfg = {skey: siteKey, ext: extend}
|
|
async function init(cfg) {
|
|
siteKey = cfg.skey;
|
|
siteType = cfg.stype;
|
|
await requestAppHost();
|
|
}
|
|
|
|
async function requestAppHost() {
|
|
const resp = await request('http://194.147.100.39/api.php/v3.user/tokenlogin?version=' + APP_VER + '&os=4', 'post', {});
|
|
try {
|
|
const encrypted = JSON.parse(resp).data;
|
|
const key = Crypto.enc.Utf8.parse('XPINGGUOXPINGGUO');
|
|
const decrypted = Crypto.AES.decrypt(encrypted, key, {
|
|
mode: Crypto.mode.ECB,
|
|
padding: Crypto.pad.Pkcs7
|
|
});
|
|
const json = Crypto.enc.Utf8.stringify(decrypted).substring(8);
|
|
const data = JSON.parse(json);
|
|
const uri = new Uri(data.apiurl);
|
|
uri.setPath('/api.php');
|
|
HOST = uri.toString();
|
|
} catch(e) {
|
|
HOST = 'http://tipu.xjqxz.top/api.php';
|
|
}
|
|
}
|
|
|
|
async function home(filter) {
|
|
const url = HOST + '/v2.vod/androidtypes';
|
|
const homeResult = await request(url);
|
|
const json = JSON.parse(homeResult);
|
|
const filters = {};
|
|
const classes = _.map(json.data, (item) => {
|
|
const filter = getFilters(item);
|
|
filters[item.type_id] = filter;
|
|
return {
|
|
type_id: item.type_id,
|
|
type_name: item.type_name,
|
|
};
|
|
});
|
|
return {
|
|
class: classes,
|
|
filters: filters,
|
|
};
|
|
}
|
|
|
|
function getFilters(data) {
|
|
const filterArray = [];
|
|
const clazz = convertTypeData(data, 'classes', '类型');
|
|
if (clazz) filterArray.push(clazz);
|
|
const area = convertTypeData(data, 'areas', '地区');
|
|
if (area) filterArray.push(area);
|
|
const year = convertTypeData(data, 'years', '年份');
|
|
if (year) filterArray.push(year);
|
|
const by = {
|
|
key: 'by',
|
|
name: '排序',
|
|
init: '',
|
|
value: [
|
|
{'n':'时间','v':'updatetime'},
|
|
{'n':'人气','v':'hits'},
|
|
{'n':'评分','v':'score'},
|
|
]
|
|
};
|
|
filterArray.push(by);
|
|
return filterArray;
|
|
}
|
|
|
|
function convertTypeData(typeData, typeKey, typeName) {
|
|
if (!typeData || !typeData[typeKey] || _.isEmpty(typeData[typeKey])) {
|
|
return null;
|
|
}
|
|
let valueList = typeData[typeKey];
|
|
const values = _.map(valueList, (item) => {
|
|
return {
|
|
n: item,
|
|
v: item,
|
|
};
|
|
});
|
|
values.unshift({
|
|
n: '全部',
|
|
v: '',
|
|
});
|
|
const typeClass = {
|
|
key: typeKey,
|
|
name: typeName,
|
|
init: '',
|
|
value: values,
|
|
};
|
|
return typeClass;
|
|
}
|
|
|
|
async function homeVod() {
|
|
const url = HOST + '/v2.main/androidhome';
|
|
const homeResult = await request(url);
|
|
const json = JSON.parse(homeResult);
|
|
const filters = {};
|
|
const videos = [];
|
|
videos.push({
|
|
vod_id: json.data.top.id,
|
|
vod_name: json.data.top.name,
|
|
vod_pic: json.data.top.pic,
|
|
vod_remarks: json.data.top.state,
|
|
});
|
|
for (const list of json.data.list) {
|
|
for (const item of list.list) {
|
|
videos.push({
|
|
vod_id: item.id,
|
|
vod_name: item.name,
|
|
vod_pic: item.pic,
|
|
vod_remarks: item.state,
|
|
});
|
|
}
|
|
}
|
|
return {
|
|
list: videos,
|
|
};
|
|
}
|
|
|
|
async function category(tid, pg, filter, extend) {
|
|
const limit = 20;
|
|
const areaPath = extend.areas ? ('&area=' + extend.areas) : '';
|
|
const classPath = extend.classes ? ('&class=' + extend.classes) : '';
|
|
const yearPath = extend.years ? ('&year=' + extend.years) : '';
|
|
const byPath = extend.by ? ('&sortby=' + extend.by) : '';
|
|
const url = HOST + '/v2.vod/androidfilter?page=' + pg + '&type=' + tid + areaPath + yearPath + byPath + classPath;
|
|
const cateResult = await request(url);
|
|
const json = JSON.parse(cateResult);
|
|
const videos = _.map(json.data, (vObj) => {
|
|
return {
|
|
vod_id: vObj.id,
|
|
vod_name: vObj.name,
|
|
vod_pic: vObj.pic,
|
|
vod_remarks: vObj.state,
|
|
};
|
|
});
|
|
const hasMore = !_.isEmpty(videos);
|
|
const page = parseInt(pg);
|
|
const pageCount = hasMore ? page + 1 :page;
|
|
return {
|
|
page: page,
|
|
pagecount: pageCount,
|
|
limit: limit,
|
|
total: pageCount * limit,
|
|
list: videos,
|
|
};
|
|
}
|
|
|
|
|
|
async function detail(id) {
|
|
const url = HOST + '/v3.vod/androiddetail2?vod_id=' + id;
|
|
const detailResult = await request(url);
|
|
const content = JSON.parse(detailResult);
|
|
const vObj = content.data;
|
|
const vodAtom = {
|
|
vod_id: id,
|
|
vod_name: vObj.name,
|
|
vod_pic: vObj.pic,
|
|
type_name: vObj.className,
|
|
vod_year: vObj.year,
|
|
vod_area: vObj.area,
|
|
vod_remarks: vObj.state,
|
|
vod_actor: vObj.actor,
|
|
vod_director: vObj.director,
|
|
vod_content: vObj.content,
|
|
}
|
|
const playInfo = vObj.urls;
|
|
const vodItems = _.map(playInfo, (epObj) => {
|
|
const epName = epObj.key;
|
|
const playUrl = epObj.url;
|
|
return epName + '$' + playUrl;
|
|
});
|
|
vodAtom.vod_play_from = '主力源';
|
|
vodAtom.vod_play_url = vodItems.join('#');
|
|
return {
|
|
list: [vodAtom],
|
|
};
|
|
}
|
|
|
|
async function play(flag, id, flags) {
|
|
let playUrl = id;
|
|
if (!playUrl.startsWith('http')) {
|
|
playUrl = 'http://c.xpgtv.net/m3u8/' + id + '.m3u8';
|
|
}
|
|
return {
|
|
parse: 0,
|
|
url: playUrl,
|
|
header: getHeaders(),
|
|
};
|
|
}
|
|
|
|
async function search(wd, quick, pg) {
|
|
const limit = 20;
|
|
const url = HOST + '/v2.vod/androidsearch10086?page=' + pg + '&wd=' + encodeURIComponent(wd);
|
|
const searchResult = await request(url);
|
|
const json = JSON.parse(searchResult);
|
|
const videos = _.map(json.data, (vObj) => {
|
|
return {
|
|
vod_id: vObj.id,
|
|
vod_name: vObj.name,
|
|
vod_pic: vObj.pic,
|
|
vod_remarks: vObj.state,
|
|
};
|
|
});
|
|
const page = parseInt(pg);
|
|
const hasMore = limit * page < json.totalcount;
|
|
const pageCount = hasMore ? page + 1 :page;
|
|
return {
|
|
page: page,
|
|
pagecount: pageCount,
|
|
limit: limit,
|
|
total: pageCount * limit,
|
|
list: videos,
|
|
};
|
|
}
|
|
|
|
export function __jsEvalReturn() {
|
|
return {
|
|
init: init,
|
|
home: home,
|
|
homeVod: homeVod,
|
|
category: category,
|
|
detail: detail,
|
|
play: play,
|
|
search: search,
|
|
};
|
|
} |