Add files via upload
This commit is contained in:
parent
544bdfae66
commit
306d400eaa
cat/tjs/lib
TextDecoder.jsTextEncoder.jsali.jsali_api.jsali_object.jsbig5.jsbilibili_ASS_Danmaku_Downloader.jsbook.jscat.jscloud.jsdanmuSpider.jsencoding-indexes.jsencodings.jseuc-jp.jseuc-kr.jsffm3u8_open.jsgb18030.jsgbk_us.jshls.jsiso-2022-jp.jslog.jsmisc.jsnivid_object.jspipiXiaObject.jsquark.jsquark_api.jsquark_object.jsshift-jis.jssimilarity.jssingle-byte.jstable.jstencentDanmu.jstext_decoder_index.jstext_decoder_indexes.jstext_decoder_utils.jsutf16.jsutf8.jsutils.jsvod.jsx-user-defined.js
190
cat/tjs/lib/TextDecoder.js
Normal file
190
cat/tjs/lib/TextDecoder.js
Normal file
@ -0,0 +1,190 @@
|
||||
import Stream, { DEFAULT_ENCODING, getEncoding } from './text_decoder_index.js'
|
||||
import { end_of_stream, finished, codePointsToString } from './text_decoder_utils.js'
|
||||
import { decoders } from './table.js'
|
||||
|
||||
// 8.1 Interface TextDecoder
|
||||
|
||||
class TextDecoder {
|
||||
/**
|
||||
* @param {string=} label The label of the encoding; defaults to 'utf-8'.
|
||||
* @param {Object=} options
|
||||
*/
|
||||
constructor(label = DEFAULT_ENCODING, options = {}) {
|
||||
// A TextDecoder object has an associated encoding, decoder,
|
||||
// stream, ignore BOM flag (initially unset), BOM seen flag
|
||||
// (initially unset), error mode (initially replacement), and do
|
||||
// not flush flag (initially unset).
|
||||
|
||||
/** @private */
|
||||
this._encoding = null
|
||||
/** @private @type {?Decoder} */
|
||||
this._decoder = null
|
||||
/** @private @type {boolean} */
|
||||
this._ignoreBOM = false
|
||||
/** @private @type {boolean} */
|
||||
this._BOMseen = false
|
||||
/** @private @type {string} */
|
||||
this._error_mode = 'replacement'
|
||||
/** @private @type {boolean} */
|
||||
this._do_not_flush = false
|
||||
|
||||
|
||||
// 1. Let encoding be the result of getting an encoding from
|
||||
// label.
|
||||
const encoding = getEncoding(label)
|
||||
|
||||
// 2. If encoding is failure or replacement, throw a RangeError.
|
||||
if (encoding === null || encoding.name == 'replacement')
|
||||
throw RangeError('Unknown encoding: ' + label)
|
||||
if (!decoders[encoding.name]) {
|
||||
throw Error('Decoder not present.' +
|
||||
' Did you forget to include encoding-indexes.js first?')
|
||||
}
|
||||
|
||||
// 4. Set dec's encoding to encoding.
|
||||
this._encoding = encoding
|
||||
|
||||
// 5. If options's fatal member is true, set dec's error mode to
|
||||
// fatal.
|
||||
if (options['fatal'])
|
||||
this._error_mode = 'fatal'
|
||||
|
||||
// 6. If options's ignoreBOM member is true, set dec's ignore BOM
|
||||
// flag.
|
||||
if (options['ignoreBOM'])
|
||||
this._ignoreBOM = true
|
||||
}
|
||||
|
||||
get encoding() {
|
||||
return this._encoding.name.toLowerCase()
|
||||
}
|
||||
get fatal() {
|
||||
return this._error_mode === 'fatal'
|
||||
}
|
||||
get ignoreBOM() {
|
||||
return this._ignoreBOM
|
||||
}
|
||||
/**
|
||||
* @param {BufferSource=} input The buffer of bytes to decode.
|
||||
* @param {Object=} options
|
||||
* @return The decoded string.
|
||||
*/
|
||||
decode(input, options = {}) {
|
||||
let bytes
|
||||
if (typeof input === 'object' && input instanceof ArrayBuffer) {
|
||||
bytes = new Uint8Array(input)
|
||||
} else if (typeof input === 'object' && 'buffer' in input &&
|
||||
input.buffer instanceof ArrayBuffer) {
|
||||
bytes = new Uint8Array(input.buffer,
|
||||
input.byteOffset,
|
||||
input.byteLength)
|
||||
} else {
|
||||
bytes = new Uint8Array(0)
|
||||
}
|
||||
|
||||
// 1. If the do not flush flag is unset, set decoder to a new
|
||||
// encoding's decoder, set stream to a new stream, and unset the
|
||||
// BOM seen flag.
|
||||
if (!this._do_not_flush) {
|
||||
this._decoder = decoders[this._encoding.name]({
|
||||
fatal: this._error_mode === 'fatal' })
|
||||
this._BOMseen = false
|
||||
}
|
||||
|
||||
// 2. If options's stream is true, set the do not flush flag, and
|
||||
// unset the do not flush flag otherwise.
|
||||
this._do_not_flush = Boolean(options['stream'])
|
||||
|
||||
// 3. If input is given, push a copy of input to stream.
|
||||
// TODO: Align with spec algorithm - maintain stream on instance.
|
||||
const input_stream = new Stream(bytes)
|
||||
|
||||
// 4. Let output be a new stream.
|
||||
const output = []
|
||||
|
||||
/** @type {?(number|!Array.<number>)} */
|
||||
let result
|
||||
|
||||
// 5. While true:
|
||||
while (true) {
|
||||
// 1. Let token be the result of reading from stream.
|
||||
const token = input_stream.read()
|
||||
|
||||
// 2. If token is end-of-stream and the do not flush flag is
|
||||
// set, return output, serialized.
|
||||
// TODO: Align with spec algorithm.
|
||||
if (token === end_of_stream)
|
||||
break
|
||||
|
||||
// 3. Otherwise, run these subsubsteps:
|
||||
|
||||
// 1. Let result be the result of processing token for decoder,
|
||||
// stream, output, and error mode.
|
||||
result = this._decoder.handler(input_stream, token)
|
||||
|
||||
// 2. If result is finished, return output, serialized.
|
||||
if (result === finished)
|
||||
break
|
||||
|
||||
if (result !== null) {
|
||||
if (Array.isArray(result))
|
||||
output.push.apply(output, /**@type {!Array.<number>}*/(result))
|
||||
else
|
||||
output.push(result)
|
||||
}
|
||||
|
||||
// 3. Otherwise, if result is error, throw a TypeError.
|
||||
// (Thrown in handler)
|
||||
|
||||
// 4. Otherwise, do nothing.
|
||||
}
|
||||
// TODO: Align with spec algorithm.
|
||||
if (!this._do_not_flush) {
|
||||
do {
|
||||
result = this._decoder.handler(input_stream, input_stream.read())
|
||||
if (result === finished)
|
||||
break
|
||||
if (result === null)
|
||||
continue
|
||||
if (Array.isArray(result))
|
||||
output.push.apply(output, /**@type {!Array.<number>}*/(result))
|
||||
else
|
||||
output.push(result)
|
||||
} while (!input_stream.endOfStream())
|
||||
this._decoder = null
|
||||
}
|
||||
|
||||
return this.serializeStream(output)
|
||||
}
|
||||
// A TextDecoder object also has an associated serialize stream
|
||||
// algorithm...
|
||||
/**
|
||||
* @param {!Array.<number>} stream
|
||||
*/
|
||||
serializeStream(stream) {
|
||||
// 1. Let token be the result of reading from stream.
|
||||
// (Done in-place on array, rather than as a stream)
|
||||
|
||||
// 2. If encoding is UTF-8, UTF-16BE, or UTF-16LE, and ignore
|
||||
// BOM flag and BOM seen flag are unset, run these subsubsteps:
|
||||
if (['UTF-8', 'UTF-16LE', 'UTF-16BE'].includes(this._encoding.name) &&
|
||||
!this._ignoreBOM && !this._BOMseen) {
|
||||
if (stream.length > 0 && stream[0] === 0xFEFF) {
|
||||
// 1. If token is U+FEFF, set BOM seen flag.
|
||||
this._BOMseen = true
|
||||
stream.shift()
|
||||
} else if (stream.length > 0) {
|
||||
// 2. Otherwise, if token is not end-of-stream, set BOM seen
|
||||
// flag and append token to stream.
|
||||
this._BOMseen = true
|
||||
} else {
|
||||
// 3. Otherwise, if token is not end-of-stream, append token
|
||||
// to output.
|
||||
// (no-op)
|
||||
}
|
||||
}
|
||||
// 4. Otherwise, return output.
|
||||
return codePointsToString(stream)
|
||||
}
|
||||
}
|
||||
export {TextDecoder}
|
107
cat/tjs/lib/TextEncoder.js
Normal file
107
cat/tjs/lib/TextEncoder.js
Normal file
@ -0,0 +1,107 @@
|
||||
import Stream, { DEFAULT_ENCODING, getEncoding } from './text_decoder_index.js'
|
||||
import { end_of_stream, finished, stringToCodePoints } from './text_decoder_utils.js'
|
||||
import { encoders } from './table.js'
|
||||
|
||||
// 8.2 Interface TextEncoder
|
||||
|
||||
class TextEncoder {
|
||||
/**
|
||||
* @param {string=} label The label of the encoding. NONSTANDARD.
|
||||
* @param {Object=} [options] NONSTANDARD.
|
||||
*/
|
||||
constructor(label, options = {}) {
|
||||
// A TextEncoder object has an associated encoding and encoder.
|
||||
|
||||
/** @private */
|
||||
this._encoding = null
|
||||
/** @private @type {?Encoder} */
|
||||
this._encoder = null
|
||||
|
||||
// Non-standard
|
||||
/** @private @type {boolean} */
|
||||
this._do_not_flush = false
|
||||
/** @private @type {string} */
|
||||
this._fatal = options['fatal'] ? 'fatal' : 'replacement'
|
||||
|
||||
// 2. Set enc's encoding to UTF-8's encoder.
|
||||
if (options['NONSTANDARD_allowLegacyEncoding']) {
|
||||
// NONSTANDARD behavior.
|
||||
label = label !== undefined ? String(label) : DEFAULT_ENCODING
|
||||
var encoding = getEncoding(label)
|
||||
if (encoding === null || encoding.name === 'replacement')
|
||||
throw RangeError('Unknown encoding: ' + label)
|
||||
if (!encoders[encoding.name]) {
|
||||
throw Error('Encoder not present.' +
|
||||
' Did you forget to include encoding-indexes.js first?')
|
||||
}
|
||||
this._encoding = encoding
|
||||
} else {
|
||||
// Standard behavior.
|
||||
this._encoding = getEncoding('utf-8')
|
||||
|
||||
if (label !== undefined && 'console' in global) {
|
||||
console.warn('TextEncoder constructor called with encoding label, '
|
||||
+ 'which is ignored.')
|
||||
}
|
||||
}
|
||||
}
|
||||
get encoding() {
|
||||
return this._encoding.name.toLowerCase()
|
||||
}
|
||||
/**
|
||||
* @param {string=} opt_string The string to encode.
|
||||
* @param {Object=} options
|
||||
*/
|
||||
encode(opt_string = '', options = {}) {
|
||||
// NOTE: This option is nonstandard. None of the encodings
|
||||
// permitted for encoding (i.e. UTF-8, UTF-16) are stateful when
|
||||
// the input is a USVString so streaming is not necessary.
|
||||
if (!this._do_not_flush)
|
||||
this._encoder = encoders[this._encoding.name]({
|
||||
fatal: this._fatal === 'fatal' })
|
||||
this._do_not_flush = Boolean(options['stream'])
|
||||
|
||||
// 1. Convert input to a stream.
|
||||
const input = new Stream(stringToCodePoints(opt_string))
|
||||
|
||||
// 2. Let output be a new stream
|
||||
const output = []
|
||||
|
||||
/** @type {?(number|!Array.<number>)} */
|
||||
var result
|
||||
// 3. While true, run these substeps:
|
||||
while (true) {
|
||||
// 1. Let token be the result of reading from input.
|
||||
var token = input.read()
|
||||
if (token === end_of_stream)
|
||||
break
|
||||
// 2. Let result be the result of processing token for encoder,
|
||||
// input, output.
|
||||
result = this._encoder.handler(input, token)
|
||||
if (result === finished)
|
||||
break
|
||||
if (Array.isArray(result))
|
||||
output.push.apply(output, /**@type {!Array.<number>}*/(result))
|
||||
else
|
||||
output.push(result)
|
||||
}
|
||||
// TODO: Align with spec algorithm.
|
||||
if (!this._do_not_flush) {
|
||||
while (true) {
|
||||
result = this._encoder.handler(input, input.read())
|
||||
if (result === finished)
|
||||
break
|
||||
if (Array.isArray(result))
|
||||
output.push.apply(output, /**@type {!Array.<number>}*/(result))
|
||||
else
|
||||
output.push(result)
|
||||
}
|
||||
this._encoder = null
|
||||
}
|
||||
// 3. If result is finished, convert output into a byte sequence,
|
||||
// and then return a Uint8Array object wrapping an ArrayBuffer
|
||||
// containing output.
|
||||
return new Uint8Array(output)
|
||||
}
|
||||
}
|
||||
export {TextEncoder}
|
86
cat/tjs/lib/ali.js
Normal file
86
cat/tjs/lib/ali.js
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* @Author: samples jadehh@live.com
|
||||
* @Date: 2023-12-14 11:03:04
|
||||
* @LastEditors: samples jadehh@live.com
|
||||
* @LastEditTime: 2023-12-14 11:03:04
|
||||
* @FilePath: lib/ali.js
|
||||
* @Description: 阿里云盘Spider公共
|
||||
*/
|
||||
import {
|
||||
initSome,
|
||||
clearFile,
|
||||
playerContent,
|
||||
playerContentByFlag,
|
||||
setShareId,
|
||||
setToken,
|
||||
getFileByShare, getTempFileId
|
||||
} from './ali_api.js';
|
||||
import {JadeLogging} from "./log.js";
|
||||
const aliName = "阿里云盘"
|
||||
const JadeLog = new JadeLogging(aliName)
|
||||
const aliPlayFormatList = ["原画", "超清", "高清", "标清"]
|
||||
|
||||
async function initAli(token) {
|
||||
await initSome();
|
||||
await setToken(token);
|
||||
await getTempFileId();
|
||||
// await clearFile();
|
||||
await JadeLog.info("阿里云盘初始化完成", true)
|
||||
}
|
||||
|
||||
|
||||
function getShareId(share_url) {
|
||||
let patternAli = /https:\/\/www\.alipan\.com\/s\/([^\\/]+)(\/folder\/([^\\/]+))?|https:\/\/www\.aliyundrive\.com\/s\/([^\\/]+)(\/folder\/([^\\/]+))?/
|
||||
let matches = patternAli.exec(share_url)
|
||||
const filteredArr = matches.filter(item => item !== undefined);
|
||||
if (filteredArr.length > 1) {
|
||||
return filteredArr[1]
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
async function detailContentAli(share_url_list, type_name = "电影") {
|
||||
try {
|
||||
let video_items = [], sub_items = []
|
||||
|
||||
|
||||
for (let i=0;i<share_url_list.length;i++){
|
||||
let share_url = share_url_list[i]
|
||||
let share_id = getShareId(share_url)
|
||||
let share_token = await setShareId(share_id)
|
||||
if (share_token !== undefined) {
|
||||
await getFileByShare(i+1,share_token, share_url, video_items, sub_items)
|
||||
}
|
||||
}
|
||||
if (video_items.length > 0) {
|
||||
await JadeLog.info(`获取播放链接成功,分享链接为:${share_url_list.join("\t")}`)
|
||||
} else {
|
||||
await JadeLog.error(`获取播放链接失败,检查分享链接为:${share_url_list.join("\t")}`)
|
||||
}
|
||||
return {"video_items":video_items,"sub_items":sub_items}
|
||||
} catch (e) {
|
||||
await JadeLog.error('获取阿里视频失败,失败原因为:' + e.message + ' 行数为:' + e.lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
async function playContentAli(flag, id, flags) {
|
||||
if (flags.length > 0) {
|
||||
await JadeLog.info(`准备播放,播放类型为:${flag},播放文件Id为:${id},播放所有类型为:${flags.join("")}`)
|
||||
} else {
|
||||
await JadeLog.info(`准备播放,播放类型为:${flag},播放文件Id为:${id},播放所有类型为:${flags.join("")}`)
|
||||
}
|
||||
let file_id_list = id.split("+")
|
||||
let share_id = file_id_list[1]
|
||||
let file_id = file_id_list[0]
|
||||
let share_token = file_id_list[2]
|
||||
return flag === "原画" ? await playerContent(file_id, share_id, share_token) : await playerContentByFlag(file_id, flag, share_id, share_token);
|
||||
}
|
||||
|
||||
export {
|
||||
initAli,
|
||||
detailContentAli,
|
||||
playContentAli,
|
||||
aliPlayFormatList,
|
||||
aliName,
|
||||
};
|
677
cat/tjs/lib/ali_api.js
Normal file
677
cat/tjs/lib/ali_api.js
Normal file
@ -0,0 +1,677 @@
|
||||
/*
|
||||
* @Author: samples jadehh@live.com
|
||||
* @Date: 2023-12-14 11:03:04
|
||||
* @LastEditors: samples jadehh@live.com
|
||||
* @LastEditTime: 2023-12-14 11:03:04
|
||||
* @FilePath: /lib/ali_api.js
|
||||
* @Description: 阿里云盘Api
|
||||
*/
|
||||
import {_, jinja2} from "./cat.js";
|
||||
import * as Utils from "./utils.js";
|
||||
import {JadeLogging} from "./log.js";
|
||||
import {
|
||||
Code, Drive, getHeader, getOAuthCache, getUserCache, Item, OAuth, post, postJson, Sub, User, CLIENT_ID
|
||||
} from "./ali_object.js";
|
||||
|
||||
let quality = {}, tempIds = [], shareToken = "", shareId = "", oauth = new OAuth(), user = new User(),
|
||||
driveInfo = new Drive(), tmpFolderName = "TV", curTmpFolderFileId = "",
|
||||
JadeLog = new JadeLogging("阿里云盘", "INFO");
|
||||
|
||||
async function initSome() {
|
||||
let user_cache_str = await getUserCache();
|
||||
user = User.objectFrom(user_cache_str);
|
||||
if (!_.isEmpty(user.getRefreshToken())) {
|
||||
await JadeLog.info("读取用户缓存成功", true);
|
||||
} else {
|
||||
await JadeLog.error("读取用户缓存失败", true);
|
||||
}
|
||||
|
||||
let oauth_cache_str = await getOAuthCache();
|
||||
oauth = OAuth.objectFrom(oauth_cache_str);
|
||||
if (!_.isEmpty(oauth.getAccessToken())) {
|
||||
await JadeLog.info("读取授权成功", true)
|
||||
} else {
|
||||
await JadeLog.error("读取授权失败", true)
|
||||
|
||||
}
|
||||
// quality = {
|
||||
// "4K": "UHD", "2k": "QHD", "超清": "FHD", "高清": "HD", "标清": "SD", "流畅": "LD"
|
||||
// };
|
||||
quality = {
|
||||
"4K": "UHD", "2k": "QHD", "超清": "QHD", "高清": "HD", "标清": "SD", "流畅": "LD"
|
||||
};
|
||||
await JadeLog.info("阿里Api初始化完成")
|
||||
}
|
||||
|
||||
async function getTempFileId() {
|
||||
curTmpFolderFileId = await createTmpFolder();
|
||||
}
|
||||
|
||||
async function clearFile() {
|
||||
try {
|
||||
await deleteTmpFolderAndRecreate();
|
||||
} catch (e) {
|
||||
await JadeLog.error("清空缓存文件失败,失败原因为:{}" + e)
|
||||
}
|
||||
await cleanRecord()
|
||||
}
|
||||
|
||||
async function cleanRecord() {
|
||||
await local.set("file", "file_id", JSON.stringify({}))
|
||||
}
|
||||
|
||||
async function setShareId(share_id) {
|
||||
getOAuthCache().length === 0 && (await oauth.clean().save());
|
||||
getUserCache().length === 0 && (await user.clean().save());
|
||||
shareId = share_id;
|
||||
return await refreshShareToken();
|
||||
}
|
||||
|
||||
function getHeaderAuth(shareToken) {
|
||||
const params = {};
|
||||
params["x-share-token"] = shareToken;
|
||||
params["X-Canary"] = "client=Android,app=adrive,version=v4.3.1";
|
||||
|
||||
if (user.isAuthed()) {
|
||||
params.authorization = user.getAuthorization();
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function getHeaderShare() {
|
||||
const params = getHeader();
|
||||
params["x-share-token"] = shareToken;
|
||||
params["X-Canary"] = "client=Android,app=adrive,version=v4.3.1";
|
||||
return params;
|
||||
}
|
||||
|
||||
function getHeaderOpen() {
|
||||
const params = {};
|
||||
params.authorization = oauth.getAuthorization();
|
||||
return params;
|
||||
}
|
||||
|
||||
function aliExpection(data_str) {
|
||||
if (data_str.indexOf("TooManyRequests") > -1) {
|
||||
Utils.sleep(1)
|
||||
return {code: 429, content: data_str}
|
||||
} else if (data_str.indexOf("AccessTokenInvalid") > -1) {
|
||||
return {code: 400, content: data_str}
|
||||
} else if (data_str.indexOf("AccessTokenExpired") > -1) {
|
||||
return {code: 401, content: data_str}
|
||||
} else if (data_str.indexOf("BadRequest") > -1) {
|
||||
return {code: 402, content: data_str}
|
||||
} else if (data_str.indexOf("NotFound.File") > -1 || data_str.indexOf("ForbiddenFileInTheRecycleBin") > -1) {
|
||||
return {code: 403, content: data_str}
|
||||
} else if (data_str.indexOf("user not allowed access drive") > -1){
|
||||
return {code: 404, content: data_str}
|
||||
} else if (data_str.indexOf("ForbiddenNoPermission.File") > -1) {
|
||||
return {code: 500, content: data_str}
|
||||
} else if (data_str.indexOf("InvalidParameter.ToParentFileId") > -1) {
|
||||
return {code: 501, content: data_str}
|
||||
} else if (data_str.indexOf("NotFound.ParentFileId") > -1) {
|
||||
return {code: 502, content: data_str}
|
||||
} else if (data_str.indexOf("The resource drive has exceeded the limit. File size exceeded drive capacity") > -1) {
|
||||
return {code: 503, content: data_str}
|
||||
}else if (data_str.indexOf("The resource drive has exceeded the limit. File size exceeded drive capacity") > -1) {
|
||||
return {code: 503, content: data_str}
|
||||
}
|
||||
return {code: 200, content: data_str}
|
||||
}
|
||||
|
||||
async function alistManyRequest(data_str) {
|
||||
if (!(data_str.indexOf("Too Many Requests") > -1)) {
|
||||
return false;
|
||||
}
|
||||
await oauth.clean().save();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Alist Token获取
|
||||
async function alist(url_param, params) {
|
||||
let url = "https://api-cf.nn.ci/alist/ali_open/" + url_param;
|
||||
let response = await postJson(url, params, getHeader()), response_content = response.content;
|
||||
if (await alistManyRequest(response_content)) {
|
||||
await JadeLog.error(`Alist授权Token失败,失败原因为:太多请求,失败详情为:${response_content}`)
|
||||
return false;
|
||||
}
|
||||
oauth = await OAuth.objectFrom(response_content).save();
|
||||
return true;
|
||||
}
|
||||
|
||||
// 阿里云盘用户Api
|
||||
async function auth(url, params, shareToken, retry) {
|
||||
url = url.startsWith("https") ? url : "https://api.aliyundrive.com/" + url;
|
||||
let response = await postJson(url, params, getHeaderAuth(shareToken));
|
||||
await JadeLog.debug(`正在请求需要阿里登录的url:${url},参数为:${JSON.stringify(params)}`)
|
||||
response = aliExpection(response.content)
|
||||
if (retry && (response.code === 400)) {
|
||||
await JadeLog.error("登录阿里云盘失败,失败原因为:登录Token无效,准备重新授权,失败详情:" + response.content)
|
||||
await refreshAccessToken("")
|
||||
return await auth(url, params, shareToken, false);
|
||||
}
|
||||
await JadeLog.debug(`完成请求需要阿里登录的url:${url},参数为:${JSON.stringify(params)},请求结果为${response.content}`)
|
||||
return response.content;
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 需要授权的Api
|
||||
async function oauthFunc(url, params, retry) {
|
||||
url = url.startsWith("https") ? url : "https://open.aliyundrive.com/adrive/v1.0/" + url;
|
||||
await JadeLog.debug(`正在请求需要阿里授权的url:${url},参数为:${JSON.stringify(params)}`)
|
||||
let open_header = getHeaderOpen();
|
||||
let response = await postJson(url, params, open_header);
|
||||
response = aliExpection(response.content)
|
||||
if (retry && (response.code === 400 || response.code === 401 || response.code === 429 || response.code === 402 || response.code === 403 || response.code === 404)) {
|
||||
if (response.code === 400) {
|
||||
await JadeLog.error("阿里授权失败,失败原因为:授权Token无效,准备重新授权,失败详情:" + response.content)
|
||||
await activateRefreshOpenToken()
|
||||
} else if (response.code === 401) {
|
||||
await JadeLog.error("阿里授权失败,失败原因为:授权Token失效,准备重新授权,失败详情:" + response.content)
|
||||
await activateRefreshOpenToken()
|
||||
} else if (response.code === 402) {
|
||||
await JadeLog.error("阿里授权失败,失败原因为:授权Token失效,准备重新授权,失败详情:" + response.content)
|
||||
return await oauthFunc(url, params, true)
|
||||
} else if (response.code === 403) {
|
||||
await JadeLog.error("阿里授权失败,失败原因为:没有找到缓存文件,失败详情:" + response.content)
|
||||
await cleanRecord()
|
||||
return "retry"
|
||||
}else if (response.code === 404) {
|
||||
await JadeLog.error("阿里授权失败,失败原因为:用户没有权限" + response.content)
|
||||
return await oauthFunc(url, params, true)
|
||||
}else if (response.code === 429) {
|
||||
await JadeLog.error(`正在请求需要阿里授权的url:${url},请求过于频繁,稍后重试,10分钟后再重试`)
|
||||
Utils.sleep(10 * 60)
|
||||
return await oauthFunc(url, params, true)
|
||||
}
|
||||
return await oauthFunc(url, params, false)
|
||||
}
|
||||
await JadeLog.debug(`完成请求需要阿里授权的url:${url},参数为:${JSON.stringify(params)},请求结果为:${JSON.stringify(response)}`)
|
||||
return response.content;
|
||||
|
||||
}
|
||||
|
||||
async function shareFunc(url, params) {
|
||||
url = url.startsWith("https") ? url : "https://api.aliyundrive.com/" + url;
|
||||
let headers = getHeaderShare(), response = await postJson(url, params, headers);
|
||||
return response.content;
|
||||
}
|
||||
|
||||
//主动刷新授权Token
|
||||
|
||||
async function activateRefreshOpenToken() {
|
||||
await oauth.clean().save()
|
||||
await refreshOpenToken()
|
||||
}
|
||||
|
||||
|
||||
async function refreshShareToken() {
|
||||
try {
|
||||
let params = {};
|
||||
params.share_id = shareId;
|
||||
params.share_pwd = "";
|
||||
let response_content = await post("v2/share_link/get_share_token", params),
|
||||
response_json = JSON.parse(response_content);
|
||||
if (response_json["code"] === "ShareLink.Cancelled") {
|
||||
await JadeLog.error("分享链接被取消了")
|
||||
}
|
||||
shareToken = response_json.share_token;
|
||||
return shareToken
|
||||
} catch (e) {
|
||||
await JadeLog.error("刷新Share Token失败" + e)
|
||||
}
|
||||
}
|
||||
|
||||
//支持切换Token
|
||||
async function refreshAccessToken(token) {
|
||||
try {
|
||||
if (_.isEmpty(user.getAccessToken()) || user.getRefreshToken() !== token) {
|
||||
let refresh_token_params = {};
|
||||
refresh_token_params.refresh_token = user.getRefreshToken();
|
||||
refresh_token_params.grant_type = "refresh_token";
|
||||
await JadeLog.info(`准备登录阿里云盘,登录Token为:${user.getRefreshToken()}`)
|
||||
let response_conetent = await post("https://auth.aliyundrive.com/v2/account/token", refresh_token_params);
|
||||
if (response_conetent.indexOf("InvalidParameter.RefreshToken") > 1 || _.isEmpty(response_conetent)) {
|
||||
if (_.isEmpty(response_conetent)) {
|
||||
await JadeLog.error(`登录阿里云盘失败,登录Token为:${user.getRefreshToken()},失败原因为:检查Token是否正确`)
|
||||
} else {
|
||||
await JadeLog.error(`登录阿里云盘失败,登录Token为:${user.getRefreshToken()},失败原因为:检查Token是否正确,返回结果为:${response_conetent}`)
|
||||
}
|
||||
} else {
|
||||
await JadeLog.info(`登录阿里云盘成功,登录Token为:${user.getRefreshToken()}`)
|
||||
user = await User.objectFrom(response_conetent).save();
|
||||
}
|
||||
} else {
|
||||
await JadeLog.info(`阿里云盘已登录,无需重复登录,登录Token为:${user.getRefreshToken()}`)
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
await JadeLog.error(`登录阿里云盘失败,登录Token为:${user.getRefreshToken()},失败原因为:${e}`)
|
||||
await user.clean().save();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async function oauthRequest() {
|
||||
try {
|
||||
let params = {};
|
||||
params.authorize = 1;
|
||||
params.scope = "user:base,file:all:read,file:all:write";
|
||||
let url = "https://open.aliyundrive.com/oauth/users/authorize?client_id=" + CLIENT_ID + "&redirect_uri=https://alist.nn.ci/tool/aliyundrive/callback&scope=user:base,file:all:read,file:all:write&state="
|
||||
await JadeLog.debug(`正在请求获取阿里授权码的url:${url},参数为:${params}`)
|
||||
let response_str = await auth(url, params, shareToken, true);
|
||||
await JadeLog.debug(`完成请求获取阿里授权码的url:${url},参数为:${params},返回值为:${response_str}`)
|
||||
if (_.isEmpty(response_str) || response_str.indexOf("AccessTokenInvalid") > -1) {
|
||||
if (_.isEmpty(response_str)) {
|
||||
await JadeLog.error(`请求获取阿里授权码失败,失败原因为:还未登录`)
|
||||
} else {
|
||||
await JadeLog.error(`请求获取阿里授权码失败,失败原因为:还未登录,失败详情为:${response_str}`)
|
||||
}
|
||||
} else {
|
||||
await JadeLog.info(`请求获取阿里授权码成功,返回值为:${response_str}`)
|
||||
return await oauthRedirect(Code.objectFrom(response_str).getCode());
|
||||
}
|
||||
} catch (e) {
|
||||
await JadeLog.error(`请求获取阿里授权失败,失败原因为:${e}`)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function oauthRedirect(code) {
|
||||
try {
|
||||
let params = {};
|
||||
params.code = code;
|
||||
params.grant_type = "authorization_code";
|
||||
return await alist("code", params);
|
||||
} catch (e) {
|
||||
// // console.debug(_0x114c46);
|
||||
await oauth.clean().save();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshOpenToken() {
|
||||
try {
|
||||
// 刷新 Refresh Token
|
||||
if (_.isEmpty(oauth.getRefreshToken())) {
|
||||
return await oauthRequest();
|
||||
}
|
||||
// 刷新Access Token
|
||||
if (_.isEmpty(oauth.getAccessToken())) {
|
||||
let params = {};
|
||||
params.grant_type = "refresh_token";
|
||||
params.refresh_token = oauth.getRefreshToken();
|
||||
return await alist("token", params);
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
await JadeLog.error("刷新授权Token失败,失败原因为:" + e);
|
||||
await oauth.clean().save();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getFileByShare(index,share_token, share_url, video_item_list, sub_item_list) {
|
||||
let params = {};
|
||||
params.share_id = shareId;
|
||||
let file_id = share_url.split("folder/").slice(-1)[0]
|
||||
if (file_id.length !== 40) {
|
||||
file_id = ""
|
||||
}
|
||||
let response_str = await post("adrive/v3/share_link/get_share_by_anonymous", params),
|
||||
response_json = JSON.parse(response_str), item_file_id = getParentFileId(file_id, response_json),
|
||||
item = new Item(item_file_id);
|
||||
await listFiles(index,item, video_item_list, sub_item_list, share_token);
|
||||
}
|
||||
|
||||
|
||||
async function listFiles(index,item, video_item_list, sub_item_list, share_token) {
|
||||
return await listFilesMarker(index,item, video_item_list, sub_item_list, "", share_token);
|
||||
}
|
||||
|
||||
async function listFilesMarker(index,item, video_item_list, sub_item_list, netxt_markers, share_token) {
|
||||
let new_item = {}, file_list = [];
|
||||
new_item.limit = 200;
|
||||
new_item.share_id = shareId;
|
||||
new_item.share_token = share_token
|
||||
new_item.parent_file_id = item.getFileId();
|
||||
new_item.order_by = "name";
|
||||
new_item.order_direction = "ASC";
|
||||
new_item.share_index = index
|
||||
if (netxt_markers.length > 0) {
|
||||
new_item.marker = netxt_markers;
|
||||
}
|
||||
let items = Item.objectFrom(await shareFunc("adrive/v2/file/list_by_share", new_item), shareToken,index);
|
||||
for (const r_item of items.getItems()) {
|
||||
if (r_item.getType() === "folder") {
|
||||
file_list.push(r_item);
|
||||
} else {
|
||||
if ((r_item.getCategory() === "video" || r_item.getCategory() === "audio")) {
|
||||
//判断数组中是否有file_id
|
||||
//
|
||||
let is_video_file_exists = false
|
||||
for (const video_item of video_item_list) {
|
||||
if (r_item.getFileId() === video_item.getFileId()) {
|
||||
is_video_file_exists = true
|
||||
await JadeLog.debug('视频分享文件重复,无需添加')
|
||||
}
|
||||
}
|
||||
if (!is_video_file_exists) {
|
||||
if (r_item.getCategory() === "video" && r_item.size / 1000000 > 10) {
|
||||
video_item_list.push(r_item.parentFunc(item.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (Utils.isSub(r_item.getExt())) {
|
||||
let is_sub_file_exists = false
|
||||
for (const sub_item of sub_item_list) {
|
||||
if (r_item.getFileId() === sub_item.getFileId()) {
|
||||
is_sub_file_exists = true
|
||||
await JadeLog.debug('字幕分享文件重复,无需添加')
|
||||
}
|
||||
}
|
||||
if (!is_sub_file_exists) {
|
||||
sub_item_list.push(r_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
items.getNextMarker().length > 0 && (await listFilesMarker(index,item, video_item_list, sub_item_list, items.getNextMarker()));
|
||||
for (const file of file_list) {
|
||||
await listFiles(index,file, video_item_list, sub_item_list);
|
||||
}
|
||||
}
|
||||
|
||||
function getParentFileId(file_id, items) {
|
||||
let file_infos = items.file_infos;
|
||||
|
||||
if (!_.isEmpty(file_id)) {
|
||||
return file_id;
|
||||
}
|
||||
|
||||
if (file_infos.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let item = file_infos[0];
|
||||
|
||||
if (item.type === "folder") {
|
||||
return item.file_id;
|
||||
}
|
||||
if (item.type === "file" && item.category === "video") {
|
||||
return "root";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
async function getSubs(sub_list, share_id) {
|
||||
let sub_url_list = [];
|
||||
for (const sub_str of sub_list) {
|
||||
if (!(sub_str.indexOf("@@@") > -1)) {
|
||||
continue;
|
||||
}
|
||||
let sub_split_list = sub_str.split("@@@"), sub_name = sub_split_list[0], sub_ext = sub_split_list[1],
|
||||
sub_file_id = sub_split_list[2], sub_url = await getDownloadUrl(sub_file_id, share_id);
|
||||
|
||||
sub_url_list.push(Sub.create().setName(sub_name).setExt(sub_ext).setUrl(sub_url));
|
||||
}
|
||||
return sub_url_list;
|
||||
}
|
||||
|
||||
async function getDriveInfo() {
|
||||
if (!_.isEmpty(driveInfo) && !_.isEmpty(driveInfo.default_drive_id)) {
|
||||
return driveInfo;
|
||||
}
|
||||
|
||||
let _0x3740f3 = await oauthFunc("user/getDriveInfo", {}, true), _0x56fde5 = JSON.parse(_0x3740f3);
|
||||
|
||||
driveInfo = {
|
||||
default_drive_id: _0x56fde5.default_drive_id,
|
||||
resource_drive_id: _0x56fde5.resource_drive_id,
|
||||
backup_drive_id: _0x56fde5.backup_drive_id
|
||||
};
|
||||
return driveInfo;
|
||||
}
|
||||
|
||||
async function getDriveId() {
|
||||
if (_.isEmpty(user.getDriveId())) {
|
||||
let drive = await getDriveInfo();
|
||||
return drive.resource_drive_id;
|
||||
}
|
||||
return user.getDriveId();
|
||||
}
|
||||
|
||||
async function getDownloadUrl(file_id, share_id, share_token) {
|
||||
let drive_id = await getDriveId();
|
||||
tempIds.unshift(await copy(file_id, share_id, share_token));
|
||||
let params = {};
|
||||
params.file_id = tempIds[0];
|
||||
params.drive_id = drive_id;
|
||||
if (tempIds[0] !== null) {
|
||||
let response_str = await oauthFunc("openFile/getDownloadUrl", params, true);
|
||||
if (response_str === "retry") {
|
||||
await JadeLog.info("尝试重新获取下载链接");
|
||||
return await getDownloadUrl(file_id, share_id)
|
||||
} else {
|
||||
await JadeLog.info("获取下载链接成功:返回结果为:" + response_str + "请求参数为:" + JSON.stringify(params));
|
||||
return JSON.parse(response_str).url;
|
||||
}
|
||||
} else {
|
||||
await JadeLog.error("获取下载链接失败:失败原因:请检查转存文件失败原因")
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getVideoPreviewPlayInfo(file_id, share_id, shareToken) {
|
||||
let drive_id = await getDriveId();
|
||||
tempIds.unshift(await copy(file_id, share_id, shareToken));
|
||||
let params = {};
|
||||
params.file_id = tempIds[0];
|
||||
params.drive_id = drive_id;
|
||||
params.category = "live_transcoding";
|
||||
params.url_expire_sec = "14400";
|
||||
let response_str = await oauthFunc("openFile/getVideoPreviewPlayInfo", params, true);
|
||||
return JSON.parse(response_str).video_preview_play_info;
|
||||
}
|
||||
|
||||
async function playerContent(file_id, share_id, share_token) {
|
||||
try {
|
||||
await JadeLog.info("正在获取原画的播放地址和字幕下载链接", true)
|
||||
let download_url = await getDownloadUrl(file_id, share_id, share_token);
|
||||
// let sub_list = await getSubs(file_id_list,share_id);
|
||||
await JadeLog.info("获取原画的播放地址和字幕下载链接成功", true)
|
||||
await JadeLog.info(`下载地址为:${download_url}`)
|
||||
return download_url;
|
||||
} catch (e) {
|
||||
await JadeLog.error("获取原画的播放地址和字幕下载链接失败:失败原因为:" + e);
|
||||
}
|
||||
}
|
||||
|
||||
// 转码头的Url和字幕
|
||||
async function playerContentByFlag(file_id, flag, share_id, shareToken) {
|
||||
try {
|
||||
await JadeLog.info("正在获取转码后的播放地址和字幕下载链接", true)
|
||||
let video_preview_play_info = await getVideoPreviewPlayInfo(file_id, share_id, shareToken),
|
||||
video_preview_url = getPreviewUrl(video_preview_play_info, flag);
|
||||
// let sub_list = await getSubs(file_id_list,share_id),
|
||||
// sub_p_list = getSubsByPlayInfo(video_preview_play_info);
|
||||
|
||||
// for (const sub_p of sub_p_list) {
|
||||
// sub_list.push(sub_p);
|
||||
// }
|
||||
|
||||
await JadeLog.info("获取转码后的播放地址和字幕下载链接成功", true)
|
||||
await JadeLog.info(`下载地址为:${video_preview_url}`)
|
||||
return video_preview_url;
|
||||
} catch (e) {
|
||||
await JadeLog.error(`获取转码后的播放地址和字幕下载链接失败,失败原因为:${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
function getPreviewUrl(video_preview_play_info, flag) {
|
||||
if (!video_preview_play_info.hasOwnProperty("live_transcoding_task_list")) {
|
||||
return "";
|
||||
}
|
||||
let live_transcoding_task_list = video_preview_play_info.live_transcoding_task_list;
|
||||
for (let index = 0; index < live_transcoding_task_list.length; ++index) {
|
||||
let live_transcoding_task = live_transcoding_task_list[index];
|
||||
|
||||
if (live_transcoding_task.template_id === quality[flag]) {
|
||||
return live_transcoding_task.url;
|
||||
}
|
||||
}
|
||||
return live_transcoding_task_list.slice(-1)[0].url;
|
||||
}
|
||||
|
||||
function getSubsByPlayInfo(video_preview_play_info) {
|
||||
if (!video_preview_play_info.hasOwnProperty("live_transcoding_subtitle_task_list")) {
|
||||
return [];
|
||||
}
|
||||
let live_transcoding_subtitle_task_list = video_preview_play_info.live_transcoding_subtitle_task_list,
|
||||
sub_p_list = [];
|
||||
for (let index = 0; index < live_transcoding_subtitle_task_list.length; ++index) {
|
||||
let live_transcoding_subtitle_task = live_transcoding_subtitle_task_list[index],
|
||||
language = live_transcoding_subtitle_task.language, url = live_transcoding_subtitle_task.url;
|
||||
sub_p_list.push(Sub.create().setUrl(url).setName(language).setLang(language).setExt("vtt"));
|
||||
}
|
||||
return sub_p_list;
|
||||
}
|
||||
|
||||
async function copy(file_id, shareId, shareToken) {
|
||||
let copy_file_id
|
||||
let cache_dic = {}
|
||||
try {
|
||||
cache_dic = JSON.parse(await local.get("file", "file_id"))
|
||||
} catch (e) {
|
||||
}
|
||||
copy_file_id = cache_dic[file_id]
|
||||
if (typeof (copy_file_id) == "string") {
|
||||
await JadeLog.info(`file id为:${file_id},已经缓存过,copy file id为:${copy_file_id}`)
|
||||
} else {
|
||||
let params_str = "{\"requests\":[{\"body\":{\"file_id\":\"{{data.fileId}}\",\"share_id\":\"{{data.shareId}}\",\"auto_rename\":true,\"to_parent_file_id\":\"{{data.tmpFolderFileId}}\",\"to_drive_id\":\"{{data.driveId}}\"},\"headers\":{\"Content-Type\":\"application/json\"},\"id\":\"0\",\"method\":\"POST\",\"url\":\"/file/copy\"}],\"resource\":\"file\"}",
|
||||
drive_id = await getDriveId(), params = {
|
||||
fileId: file_id, shareId: shareId, driveId: drive_id, tmpFolderFileId: curTmpFolderFileId
|
||||
};
|
||||
params_str = jinja2(params_str, {
|
||||
data: params
|
||||
});
|
||||
await JadeLog.debug(`正在转存文件,文件id为:${file_id}`, true)
|
||||
let response_str = await auth("adrive/v2/batch", JSON.parse(params_str), shareToken, true);
|
||||
let response = aliExpection(response_str)
|
||||
if (response.code === 500 || response.code === 501 || response.code === 502 || response.code === 503 || response.code === 403) {
|
||||
if (response.code === 500) {
|
||||
await JadeLog.error("转存文件失败,失败详情:" + response.content)
|
||||
return copy(file_id);
|
||||
} else if (response.code === 501) {
|
||||
await JadeLog.error("转存文件失败,失败详情:" + response.content)
|
||||
return copy(file_id)
|
||||
} else if (response.code === 502) {
|
||||
await JadeLog.error("转存文件失败,失败原因为:转存文件夹不存在,失败详情:" + response.content)
|
||||
return null;
|
||||
} else if (response.code === 503) {
|
||||
await JadeLog.error("转存文件失败,失败原因为:转存文件夹大小被限制" + response.content)
|
||||
await clearFile()
|
||||
return copy(file_id)
|
||||
} else if (response.code === 403) {
|
||||
await JadeLog.error("转存文件失败,失败原因为:没有找到File Id,失败详情:" + response.content)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
await JadeLog.debug(`转存文件成功,文件id为:${file_id},请求结果为:${response_str}`)
|
||||
copy_file_id = JSON.parse(response_str).responses[0].body.file_id;
|
||||
let file_dic = {}
|
||||
try {
|
||||
JSON.parse(await local.get("file", "file_id"))
|
||||
} catch (e) {
|
||||
}
|
||||
file_dic[file_id] = copy_file_id
|
||||
await local.set("file", "file_id", JSON.stringify(file_dic))
|
||||
}
|
||||
return copy_file_id;
|
||||
}
|
||||
|
||||
async function deleteTmpFolderAndRecreate() {
|
||||
// 删除缓存文件夹
|
||||
let file_id = await tmpFolderExistsFunc();
|
||||
file_id && (await trashFile(file_id), await recyclebinClear());
|
||||
await getTempFileId();
|
||||
}
|
||||
|
||||
//放入回车站
|
||||
async function trashFile(file_id) {
|
||||
let params_str = "{\"requests\":[{\"body\":{\"file_id\":\"{{data.fileId}}\",\"drive_id\":\"{{data.driveId}}\"},\"headers\":{\"Content-Type\":\"application/json\"},\"id\":\"0\",\"method\":\"POST\",\"url\":\"/recyclebin/trash\"}],\"resource\":\"file\"}",
|
||||
drive_id = await getDriveId(), params = {
|
||||
fileId: file_id, driveId: drive_id
|
||||
};
|
||||
params_str = jinja2(params_str, {
|
||||
data: params
|
||||
});
|
||||
await JadeLog.debug(`正在准备删除文件,文件id为:${file_id}`, true)
|
||||
let response = await auth("v2/batch", JSON.parse(params_str), shareToken, true);
|
||||
await JadeLog.debug(`删除文件成功,文件id为:${file_id},请求结果为:${response}`)
|
||||
return true;
|
||||
}
|
||||
|
||||
//清空回车站
|
||||
async function recyclebinClear() {
|
||||
let drive_id = await getDriveId(), params = {
|
||||
drive_id: drive_id
|
||||
};
|
||||
await auth("v2/recyclebin/clear", params, shareToken, true);
|
||||
await JadeLog.info("清空回车站成功", true)
|
||||
return true;
|
||||
}
|
||||
|
||||
async function createTmpFolder() {
|
||||
//创建文件夹
|
||||
let file_id = await tmpFolderExistsFunc();
|
||||
if (file_id) {
|
||||
await JadeLog.info("文件夹存在,无需重新创建")
|
||||
return file_id;
|
||||
}
|
||||
await JadeLog.debug("文件夹不存在,重新创建文件夹")
|
||||
let drive_id = await getDriveId(), params = {
|
||||
check_name_mode: "refuse", drive_id: drive_id, name: tmpFolderName, parent_file_id: "root", type: "folder"
|
||||
}, response_str = await oauthFunc("openFile/create", params, true);
|
||||
let response_json = JSON.parse(response_str);
|
||||
if (_.isEmpty(response_json.drive_id)) {
|
||||
await JadeLog.error(`创建文件夹失败,失败原因为:${response_str}`)
|
||||
return null;
|
||||
}
|
||||
await JadeLog.info(`创建文件夹成功`, true)
|
||||
return response_json.file_id;
|
||||
}
|
||||
|
||||
async function tmpFolderExistsFunc() {
|
||||
let drive_id = await getDriveId(), params = {
|
||||
drive_id: drive_id, parent_file_id: "root", limit: 100, order_by: "updated_at", order_direction: "DESC"
|
||||
}, response_str = await oauthFunc("openFile/list", params, true);
|
||||
let response_json = JSON.parse(response_str);
|
||||
if (_.isEmpty(response_json.items)) {
|
||||
return false;
|
||||
}
|
||||
for (const item of response_json.items) {
|
||||
if (item.name === tmpFolderName) {
|
||||
return item.file_id;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
async function setToken(token) {
|
||||
// Token设置
|
||||
user.setRefreshToken(token);
|
||||
await refreshAccessToken(token);
|
||||
await refreshOpenToken();
|
||||
}
|
||||
|
||||
export {
|
||||
initSome, setToken, clearFile, setShareId, getFileByShare, playerContent, playerContentByFlag, getTempFileId
|
||||
};
|
516
cat/tjs/lib/ali_object.js
Normal file
516
cat/tjs/lib/ali_object.js
Normal file
@ -0,0 +1,516 @@
|
||||
/*
|
||||
* @Author: samples jadehh@live.com
|
||||
* @Date: 2023-12-14 11:03:04
|
||||
* @LastEditors: samples jadehh@live.com
|
||||
* @LastEditTime: 2023-12-14 11:03:04
|
||||
* @FilePath: /lib/ali_object.js
|
||||
* @Description: 阿里云盘基础类
|
||||
*/
|
||||
import {_} from "./cat.js";
|
||||
|
||||
const UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"
|
||||
const CLIENT_ID = "76917ccccd4441c39457a04f6084fb2f";
|
||||
import * as Utils from "./utils.js";
|
||||
// 引用会导致出错
|
||||
// import qs from "qs";
|
||||
// import axios from "axios";
|
||||
// import https from "https";
|
||||
|
||||
function getHeader() {
|
||||
const params = {};
|
||||
params["User-Agent"] = UA;
|
||||
params.Referer = "https://www.aliyundrive.com/";
|
||||
return params;
|
||||
}
|
||||
|
||||
class User {
|
||||
constructor() {
|
||||
this.driveId = "";
|
||||
this.userId = "";
|
||||
this.tokenType = "";
|
||||
this.accessToken = "";
|
||||
this.refreshToken = "";
|
||||
}
|
||||
|
||||
static objectFrom(json_str) {
|
||||
if (_.isEmpty(json_str)) {
|
||||
return new User();
|
||||
}
|
||||
|
||||
let resonse = JSON.parse(json_str), user = new User();
|
||||
user.driveId = resonse.default_drive_id;
|
||||
user.userId = resonse.user_id;
|
||||
user.tokenType = resonse.token_type;
|
||||
user.accessToken = resonse.access_token;
|
||||
user.refreshToken = resonse.refresh_token; // 刷新Token记录原有的Token
|
||||
return user;
|
||||
}
|
||||
|
||||
getDriveId() {
|
||||
return _.isEmpty(this.driveId) ? "" : this.driveId;
|
||||
}
|
||||
|
||||
getUserId() {
|
||||
return _.isEmpty(this.userId) ? "" : this.userId;
|
||||
}
|
||||
|
||||
getTokenType() {
|
||||
return _.isEmpty(this.tokenType) ? "" : this.tokenType;
|
||||
}
|
||||
|
||||
getAccessToken() {
|
||||
return _.isEmpty(this.accessToken) ? "" : this.accessToken;
|
||||
}
|
||||
|
||||
getRefreshToken() {
|
||||
return _.isEmpty(this.refreshToken) ? "" : this.refreshToken;
|
||||
}
|
||||
|
||||
setRefreshToken(refresh_token) {
|
||||
this.refreshToken = refresh_token
|
||||
}
|
||||
|
||||
getAuthorization() {
|
||||
return this.getTokenType() + " " + this.getAccessToken();
|
||||
}
|
||||
|
||||
isAuthed() {
|
||||
return this.getTokenType().length > 0 && this.getAccessToken().length > 0;
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.refreshToken = "";
|
||||
this.accessToken = "";
|
||||
return this;
|
||||
}
|
||||
|
||||
async save() {
|
||||
await local.set("ali", "aliyundrive_user", this.toString());
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify(this.toDict());
|
||||
}
|
||||
|
||||
toDict() {
|
||||
return {
|
||||
default_drive_id: this.getDriveId(),
|
||||
user_id: this.getUserId(),
|
||||
token_type: this.getTokenType(),
|
||||
access_token: this.getAccessToken(),
|
||||
refresh_token: this.getRefreshToken()
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class OAuth {
|
||||
constructor() {
|
||||
this.tokenType = "";
|
||||
this.accessToken = "";
|
||||
this.refreshToken = "";
|
||||
}
|
||||
|
||||
static objectFrom(json_str) {
|
||||
if (_.isEmpty(json_str)) {
|
||||
return new OAuth();
|
||||
}
|
||||
let oauth_json = JSON.parse(json_str), oAuth = new OAuth();
|
||||
oAuth.tokenType = oauth_json.token_type;
|
||||
oAuth.accessToken = oauth_json.access_token;
|
||||
oAuth.refreshToken = oauth_json.refresh_token;
|
||||
return oAuth;
|
||||
}
|
||||
|
||||
getTokenType() {
|
||||
return _.isEmpty(this.tokenType) ? "" : this.tokenType;
|
||||
}
|
||||
|
||||
getAccessToken() {
|
||||
return _.isEmpty(this.accessToken) ? "" : this.accessToken;
|
||||
}
|
||||
|
||||
getRefreshToken() {
|
||||
return _.isEmpty(this.refreshToken) ? "" : this.refreshToken;
|
||||
}
|
||||
|
||||
getAuthorization() {
|
||||
return this.getTokenType() + " " + this.getAccessToken();
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.refreshToken = "";
|
||||
this.accessToken = "";
|
||||
return this;
|
||||
}
|
||||
|
||||
async save() {
|
||||
await local.set("ali", "aliyundrive_oauth", this.toString());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return JSON.stringify(this.toDict());
|
||||
}
|
||||
|
||||
toDict() {
|
||||
return {
|
||||
token_type: this.getTokenType(), access_token: this.getAccessToken(), refresh_token: this.getRefreshToken()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Drive {
|
||||
constructor() {
|
||||
this.defaultDriveId = "";
|
||||
this.resourceDriveId = "";
|
||||
this.backupDriveId = "";
|
||||
}
|
||||
|
||||
static objectFrom(json_str) {
|
||||
if (_.isEmpty(json_str)) {
|
||||
return new Drive();
|
||||
}
|
||||
let obj = JSON.parse(json_str), drive = new Drive();
|
||||
drive.defaultDriveId = obj.default_drive_id;
|
||||
drive.resourceDriveId = obj.resource_drive_id;
|
||||
drive.backupDriveId = obj.backup_drive_id;
|
||||
return drive;
|
||||
}
|
||||
|
||||
getDefaultDriveId() {
|
||||
return _.isEmpty(this.defaultDriveId) ? "" : this.defaultDriveId;
|
||||
}
|
||||
|
||||
getResourceDriveId() {
|
||||
return _.isEmpty(this.resourceDriveId) ? "" : this.resourceDriveId;
|
||||
}
|
||||
|
||||
getBackupDriveId() {
|
||||
return _.isEmpty(this.backupDriveId) ? "" : this.backupDriveId;
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.defaultDriveId = "";
|
||||
this.backupDriveId = "";
|
||||
this.resourceDriveId = "";
|
||||
return this;
|
||||
}
|
||||
|
||||
async save() {
|
||||
await local.set("ali", "aliyundrive_drive", this.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
toString() {
|
||||
const params = {
|
||||
default_drive_id: this.getDefaultDriveId(),
|
||||
resource_drive_id: this.getResourceDriveId(),
|
||||
backup_drive_id: this.getBackupDriveId()
|
||||
};
|
||||
return JSON.stringify(params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Code {
|
||||
constructor() {
|
||||
this.redirectUri = "";
|
||||
}
|
||||
|
||||
static objectFrom(json_str) {
|
||||
if (_.isEmpty(json_str)) {
|
||||
return new Code();
|
||||
}
|
||||
let code_json = JSON.parse(json_str), code = new Code();
|
||||
code.redirectUri = code_json.redirectUri;
|
||||
return code;
|
||||
}
|
||||
|
||||
getRedirectUri() {
|
||||
return _.isEmpty(this.redirectUri) ? "" : this.redirectUri;
|
||||
}
|
||||
|
||||
getCode() {
|
||||
return this.getRedirectUri().split("code=")[1];
|
||||
}
|
||||
}
|
||||
|
||||
class Item {
|
||||
constructor(file_id) {
|
||||
this.items = [];
|
||||
this.nextMarker = "";
|
||||
this.fileId = file_id;
|
||||
this.shareId = "";
|
||||
this.name = "";
|
||||
this.type = "";
|
||||
this.fileExtension = "";
|
||||
this.category = "";
|
||||
this.size = "";
|
||||
this.parent = "";
|
||||
this.shareToken = "";
|
||||
this.shareIndex = 0;
|
||||
}
|
||||
|
||||
static objectFrom(json_str, shareToken,shareIndex) {
|
||||
if (_.isEmpty(json_str)) {
|
||||
return new Item();
|
||||
}
|
||||
|
||||
let item_json = JSON.parse(json_str), item = new Item();
|
||||
|
||||
item.nextMarker = typeof item_json.next_marker == "undefined" ? "" : item_json.next_marker;
|
||||
item.fileId = typeof item_json.file_id == "undefined" ? "" : item_json.file_id;
|
||||
item.shareId = typeof item_json.share_id == "undefined" ? "" : item_json.share_id;
|
||||
item.shareToken = shareToken
|
||||
item.name = typeof item_json.name == "undefined" ? "" : item_json.name;
|
||||
item.type = typeof item_json.type == "undefined" ? "" : item_json.type;
|
||||
item.fileExtension = typeof item_json.file_extension == "undefined" ? "" : item_json.file_extension;
|
||||
item.category = typeof item_json.category == "undefined" ? "" : item_json.category;
|
||||
item.size = typeof item_json.size == "undefined" ? "" : item_json.size;
|
||||
item.parent = typeof item_json.parent_file_id == "undefined" ? "" : item_json.parent_file_id;
|
||||
item.shareIndex = shareIndex
|
||||
typeof item.items != "undefined" && Array.isArray(item_json.items) && !_.isEmpty(item_json.items) && item_json.items.forEach(function (x) {
|
||||
let new_item = Item.objectFrom(JSON.stringify((x)), shareToken,shareIndex)
|
||||
item.items.push(new_item);
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return _.isEmpty(this.items) ? [] : this.items;
|
||||
}
|
||||
|
||||
getNextMarker() {
|
||||
return _.isEmpty(this.nextMarker) ? "" : this.nextMarker;
|
||||
}
|
||||
|
||||
getFileId() {
|
||||
return _.isEmpty(this.fileId) ? "" : this.fileId;
|
||||
}
|
||||
|
||||
getShareId() {
|
||||
return _.isEmpty(this.shareId) ? "" : this.shareId;
|
||||
}
|
||||
|
||||
getFileExtension() {
|
||||
return _.isEmpty(this.fileExtension) ? "" : this.fileExtension;
|
||||
}
|
||||
|
||||
getName() {
|
||||
return _.isEmpty(this.name) ? "" : this.name;
|
||||
}
|
||||
|
||||
getType() {
|
||||
return _.isEmpty(this.type) ? "" : this.type;
|
||||
}
|
||||
|
||||
getExt() {
|
||||
return _.isEmpty(this.fileExtension) ? "" : this.fileExtension;
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
return _.isEmpty(this.category) ? "" : this.category;
|
||||
}
|
||||
|
||||
getSize() {
|
||||
return this.size === 0 ? "" : "[" + Utils.getSize(this.size) + "]";
|
||||
}
|
||||
|
||||
getParent() {
|
||||
return _.isEmpty(this.parent) ? "" : "[" + this.parent + "]";
|
||||
}
|
||||
|
||||
getShareIndex(){
|
||||
return this.shareIndex
|
||||
}
|
||||
|
||||
parentFunc(item) {
|
||||
this.parent = item;
|
||||
return this;
|
||||
}
|
||||
|
||||
getDisplayName(type_name) {
|
||||
let name = this.getName();
|
||||
name = name.replaceAll("玩偶哥 q 频道:【神秘的哥哥们】", "")
|
||||
if (type_name === "电视剧") {
|
||||
let replaceNameList = ["4k", "4K"]
|
||||
name = name.replaceAll("." + this.getFileExtension(), "")
|
||||
name = name.replaceAll(" ", "").replaceAll(" ", "")
|
||||
for (const replaceName of replaceNameList) {
|
||||
name = name.replaceAll(replaceName, "")
|
||||
}
|
||||
name = Utils.getStrByRegexDefault(/\.S01E(.*?)\./, name)
|
||||
const numbers = name.match(/\d+/g);
|
||||
if (!_.isEmpty(numbers) && numbers.length > 0) {
|
||||
name = numbers[0]
|
||||
}
|
||||
}
|
||||
|
||||
return name + " " + this.getParent() + " " + this.getSize();
|
||||
}
|
||||
getEpisodeUrl(type_name){
|
||||
return this.getDisplayName(type_name) + "$" + this.getFileId() + "+" + this.shareId + "+" + this.shareToken
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Sub {
|
||||
constructor() {
|
||||
this.url = "";
|
||||
this.name = "";
|
||||
this.lang = "";
|
||||
this.format = "";
|
||||
}
|
||||
|
||||
static create() {
|
||||
return new Sub();
|
||||
}
|
||||
|
||||
setName(name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
setUrl(url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
setLang(lang) {
|
||||
this.lang = lang;
|
||||
return this;
|
||||
}
|
||||
|
||||
setFormat(format) {
|
||||
this.format = format;
|
||||
return this;
|
||||
}
|
||||
|
||||
setExt(ext) {
|
||||
switch (ext) {
|
||||
case "vtt":
|
||||
return this.setFormat("text/vtt");
|
||||
case "ass":
|
||||
case "ssa":
|
||||
return this.setFormat("text/x-ssa");
|
||||
default:
|
||||
return this.setFormat("application/x-subrip");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function getUserCache() {
|
||||
return await local.get("ali", "aliyundrive_user");
|
||||
}
|
||||
|
||||
async function getOAuthCache() {
|
||||
return await local.get("ali", "aliyundrive_oauth");
|
||||
}
|
||||
|
||||
function getShareId(share_url) {
|
||||
let patternAli = /https:\/\/www\.alipan\.com\/s\/([^\\/]+)(\/folder\/([^\\/]+))?|https:\/\/www\.aliyundrive\.com\/s\/([^\\/]+)(\/folder\/([^\\/]+))?/
|
||||
let matches = patternAli.exec(share_url)
|
||||
const filteredArr = matches.filter(item => item !== undefined);
|
||||
if (filteredArr.length > 1) {
|
||||
return matches[1]
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
async function ali_request(url, opt) {
|
||||
|
||||
let resp;
|
||||
let data;
|
||||
try {
|
||||
data = opt ? opt.data || null : null;
|
||||
const postType = opt ? opt.postType || null : null;
|
||||
const returnBuffer = opt ? opt.buffer || 0 : 0;
|
||||
const timeout = opt ? opt.timeout || 5000 : 5000;
|
||||
|
||||
const headers = opt ? opt.headers || {} : {};
|
||||
if (postType === 'form') {
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
|
||||
if (data != null) {
|
||||
data = qs.stringify(data, {encode: false});
|
||||
}
|
||||
}
|
||||
let respType = returnBuffer === 1 || returnBuffer === 2 ? 'arraybuffer' : undefined;
|
||||
resp = await axios(url, {
|
||||
responseType: respType,
|
||||
method: opt ? opt.method || 'get' : 'get',
|
||||
headers: headers,
|
||||
data: data,
|
||||
timeout: timeout,
|
||||
httpsAgent: https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
}),
|
||||
});
|
||||
data = resp.data;
|
||||
|
||||
const resHeader = {};
|
||||
for (const hks of resp.headers) {
|
||||
const v = hks[1];
|
||||
resHeader[hks[0]] = Array.isArray(v) ? (v.length === 1 ? v[0] : v) : v;
|
||||
}
|
||||
|
||||
if (!returnBuffer) {
|
||||
if (typeof data === 'object') {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
} else if (returnBuffer === 1) {
|
||||
return {code: resp.status, headers: resHeader, content: data};
|
||||
} else if (returnBuffer === 2) {
|
||||
return {code: resp.status, headers: resHeader, content: data.toString('base64')};
|
||||
}
|
||||
return {code: resp.status, headers: resHeader, content: data};
|
||||
} catch (error) {
|
||||
// await Utils.log(`请求失败,URL为:${url},失败原因为:${error}`)
|
||||
resp = error.response
|
||||
try {
|
||||
return {code: resp.status, headers: resp.headers, content: JSON.stringify(resp.data)};
|
||||
} catch (err) {
|
||||
return {headers: {}, content: ''};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function post(url, params) {
|
||||
url = url.startsWith("https") ? url : "https://api.aliyundrive.com/" + url;
|
||||
let response = await postJson(url, params, getHeader());
|
||||
return response.content;
|
||||
}
|
||||
|
||||
async function postJson(url, params, headers) {
|
||||
params["Content-Type"] = "application/json";
|
||||
return await req(url, {
|
||||
headers: headers, method: "post", data: params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
UA,
|
||||
CLIENT_ID,
|
||||
OAuth,
|
||||
Code,
|
||||
Sub,
|
||||
User,
|
||||
Item,
|
||||
Drive,
|
||||
getHeader,
|
||||
getShareId,
|
||||
getOAuthCache,
|
||||
getUserCache,
|
||||
post,
|
||||
postJson
|
||||
};
|
154
cat/tjs/lib/big5.js
Normal file
154
cat/tjs/lib/big5.js
Normal file
@ -0,0 +1,154 @@
|
||||
import { inRange, decoderError, encoderError, isASCIICodePoint,
|
||||
end_of_stream, finished, isASCIIByte, floor } from './text_decoder_utils.js'
|
||||
import index, { indexBig5PointerFor, indexCodePointFor } from './text_decoder_indexes.js'
|
||||
|
||||
//
|
||||
// 12. Legacy multi-byte Chinese (traditional) encodings
|
||||
//
|
||||
|
||||
// 12.1 Big5
|
||||
|
||||
// 12.1.1 Big5 decoder
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class Big5Decoder {
|
||||
constructor(options) {
|
||||
const { fatal } = options
|
||||
this.fatal = fatal
|
||||
// Big5's decoder has an associated Big5 lead (initially 0x00).
|
||||
this.Big5_lead = 0x00
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream and Big5 lead is not 0x00, set
|
||||
// Big5 lead to 0x00 and return error.
|
||||
if (bite === end_of_stream && this.Big5_lead !== 0x00) {
|
||||
this.Big5_lead = 0x00
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 2. If byte is end-of-stream and Big5 lead is 0x00, return
|
||||
// finished.
|
||||
if (bite === end_of_stream && this.Big5_lead === 0x00)
|
||||
return finished
|
||||
|
||||
// 3. If Big5 lead is not 0x00, let lead be Big5 lead, let
|
||||
// pointer be null, set Big5 lead to 0x00, and then run these
|
||||
// substeps:
|
||||
if (this.Big5_lead !== 0x00) {
|
||||
const lead = this.Big5_lead
|
||||
let pointer = null
|
||||
this.Big5_lead = 0x00
|
||||
|
||||
// 1. Let offset be 0x40 if byte is less than 0x7F and 0x62
|
||||
// otherwise.
|
||||
const offset = bite < 0x7F ? 0x40 : 0x62
|
||||
|
||||
// 2. If byte is in the range 0x40 to 0x7E, inclusive, or 0xA1
|
||||
// to 0xFE, inclusive, set pointer to (lead − 0x81) × 157 +
|
||||
// (byte − offset).
|
||||
if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE))
|
||||
pointer = (lead - 0x81) * 157 + (bite - offset)
|
||||
|
||||
// 3. If there is a row in the table below whose first column
|
||||
// is pointer, return the two code points listed in its second
|
||||
// column
|
||||
// Pointer | Code points
|
||||
// --------+--------------
|
||||
// 1133 | U+00CA U+0304
|
||||
// 1135 | U+00CA U+030C
|
||||
// 1164 | U+00EA U+0304
|
||||
// 1166 | U+00EA U+030C
|
||||
switch (pointer) {
|
||||
case 1133: return [0x00CA, 0x0304]
|
||||
case 1135: return [0x00CA, 0x030C]
|
||||
case 1164: return [0x00EA, 0x0304]
|
||||
case 1166: return [0x00EA, 0x030C]
|
||||
}
|
||||
|
||||
// 4. Let code point be null if pointer is null and the index
|
||||
// code point for pointer in index Big5 otherwise.
|
||||
const code_point = (pointer === null) ? null :
|
||||
indexCodePointFor(pointer, index('big5'))
|
||||
|
||||
// 5. If code point is null and byte is an ASCII byte, prepend
|
||||
// byte to stream.
|
||||
if (code_point === null && isASCIIByte(bite))
|
||||
stream.prepend(bite)
|
||||
|
||||
// 6. If code point is null, return error.
|
||||
if (code_point === null)
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 7. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
|
||||
// 4. If byte is an ASCII byte, return a code point whose value
|
||||
// is byte.
|
||||
if (isASCIIByte(bite))
|
||||
return bite
|
||||
|
||||
// 5. If byte is in the range 0x81 to 0xFE, inclusive, set Big5
|
||||
// lead to byte and return continue.
|
||||
if (inRange(bite, 0x81, 0xFE)) {
|
||||
this.Big5_lead = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 6. Return error.
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 12.1.2 Big5 encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class Big5Encoder {
|
||||
constructor() {
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
*/
|
||||
this.handler = function(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point, return a byte whose
|
||||
// value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 3. Let pointer be the index Big5 pointer for code point.
|
||||
const pointer = indexBig5PointerFor(code_point)
|
||||
|
||||
// 4. If pointer is null, return error with code point.
|
||||
if (pointer === null)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 5. Let lead be floor(pointer / 157) + 0x81.
|
||||
const lead = floor(pointer / 157) + 0x81
|
||||
|
||||
// 6. If lead is less than 0xA1, return error with code point.
|
||||
if (lead < 0xA1)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 7. Let trail be pointer % 157.
|
||||
const trail = pointer % 157
|
||||
|
||||
// 8. Let offset be 0x40 if trail is less than 0x3F and 0x62
|
||||
// otherwise.
|
||||
const offset = trail < 0x3F ? 0x40 : 0x62
|
||||
|
||||
// Return two bytes whose values are lead and trail + offset.
|
||||
return [lead, trail + offset]
|
||||
}
|
||||
}
|
||||
}
|
31
cat/tjs/lib/bilibili_ASS_Danmaku_Downloader.js
Normal file
31
cat/tjs/lib/bilibili_ASS_Danmaku_Downloader.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* @File : bilibili_ASS_Danmaku_Downloader.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/3/14 13:19
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
function parseXML(json) {
|
||||
let list = [];
|
||||
/**
|
||||
* <d p="{time},{type},{size},{color},{timestamp},{pool},{uid_crc32},{row_id}">
|
||||
*
|
||||
* {Text}
|
||||
* time为弹幕在视频里的时间 -->
|
||||
* type为弹幕类型 -->
|
||||
* size为字体大小 -->
|
||||
* color为十进制的RGB颜色(16进制转10进制) -->
|
||||
* timestamp为弹幕发送时间戳(unix时间戳) -->
|
||||
* pool为弹幕池 -->
|
||||
* uid_crc32为发送者uid的crc32 -->
|
||||
*/
|
||||
Array.from(json.danmuku).forEach(x => {
|
||||
let start = Number(x[0]);
|
||||
let content = x[4];
|
||||
list.push(`<d p="${start},1,25,16777215,1659282294,0,8b53b65c,1108899274487246080"><![CDATA[${content}]]></d>`)
|
||||
});
|
||||
return String.raw`<?xml version="1.0" encoding="UTF-8"?><i><chatserver>chat.bilibili.com</chatserver><chatid>52175602</chatid><mission>0</mission><maxlimit>1000</maxlimit><state>0</state><real_name>0</real_name><source>k-v</source>` + list.join('') + "</i>"
|
||||
}
|
||||
export {parseXML}
|
||||
|
76
cat/tjs/lib/book.js
Normal file
76
cat/tjs/lib/book.js
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* @File : book.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/1/30 17:01
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
export class BookShort {
|
||||
|
||||
constructor() {
|
||||
this.book_id = "" //id
|
||||
this.book_name = "" //名称
|
||||
this.book_pic = "" //图片
|
||||
this.book_remarks = "" //备注
|
||||
}
|
||||
|
||||
to_dict() {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
load_dic(json_str) {
|
||||
let obj = JSON.parse(json_str)
|
||||
for (let propName in obj) {
|
||||
this[propName] = obj[propName];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class BookDetail extends BookShort {
|
||||
/**
|
||||
* let book = {
|
||||
* book_name: $('[property$=book_name]')[0].attribs.content,
|
||||
* book_year: $('[property$=update_time]')[0].attribs.content,
|
||||
* book_director: $('[property$=author]')[0].attribs.content,
|
||||
* book_content: $('[property$=description]')[0].attribs.content,
|
||||
* };
|
||||
* $ = await this.getHtml(this.siteUrl + id + `list.html`);
|
||||
* let urls = [];
|
||||
* const links = $('dl>dd>a[href*="/html/"]');
|
||||
* for (const l of links) {
|
||||
* const name = $(l).text().trim();
|
||||
* const link = l.attribs.href;
|
||||
* urls.push(name + '$' + link);
|
||||
* }
|
||||
* book.volumes = '全卷';
|
||||
* book.urls = urls.join('#');
|
||||
* return book
|
||||
* */
|
||||
constructor() {
|
||||
super();
|
||||
this.book_year = ""
|
||||
this.book_director = ""
|
||||
this.book_content = ""
|
||||
this.volumes = ""
|
||||
this.urls = ""
|
||||
}
|
||||
|
||||
to_short() {
|
||||
let bookShort = new BookShort()
|
||||
bookShort.load_dic(this.to_dict())
|
||||
return bookShort.to_dict()
|
||||
}
|
||||
|
||||
load_dic(json_str) {
|
||||
let obj = JSON.parse(json_str)
|
||||
for (let propName in obj) {
|
||||
this[propName] = obj[propName];
|
||||
console.log(propName);//打印👉属性名-->name age gender address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
15158
cat/tjs/lib/cat.js
Normal file
15158
cat/tjs/lib/cat.js
Normal file
File diff suppressed because one or more lines are too long
107
cat/tjs/lib/cloud.js
Normal file
107
cat/tjs/lib/cloud.js
Normal file
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* File: h:\PycharmProjects\Github\TVSpider\lib\cloud.js
|
||||
* Project: h:\PycharmProjects\Github\TVSpider
|
||||
* Created Date: Tuesday, May 21st 2024, 2:01:09 pm
|
||||
* Author: jade
|
||||
* -----
|
||||
* Last Modified: Tue May 21 2024
|
||||
* Modified By: jade
|
||||
* -----
|
||||
* Copyright (c) 2024 samples
|
||||
* ------------------------------------
|
||||
* Javascript will save your soul!
|
||||
*/
|
||||
|
||||
import {initAli,detailContentAli,playContentAli, aliPlayFormatList,aliName} from "./ali.js"
|
||||
import { initQuark,detailContentQuark,playContentQuark,getQuarkPlayFormatList,quarkName,getQuarkHeaders} from "./quark.js"
|
||||
import * as Utils from "./utils.js";
|
||||
import {_} from "../lib/cat.js";
|
||||
|
||||
|
||||
async function initCloud(cfg){
|
||||
initAli(cfg["aliToken"])
|
||||
initQuark(cfg["quarkCookie"])
|
||||
}
|
||||
|
||||
async function detailContent(share_url_list,type_name="电影"){
|
||||
let ali_share_url_list = [],quark_share_url_list = []
|
||||
const playVod = {}
|
||||
for (const shareUrl of share_url_list){
|
||||
let aliMatches = shareUrl.match(Utils.patternAli);
|
||||
if (!_.isEmpty(aliMatches)) {
|
||||
ali_share_url_list.push(aliMatches[1])
|
||||
}
|
||||
let quarkMatches = shareUrl.match(Utils.patternQuark);
|
||||
if (!_.isEmpty(quarkMatches)){
|
||||
quark_share_url_list.push(quarkMatches[1])
|
||||
}
|
||||
}
|
||||
let aliItems = await detailContentAli(ali_share_url_list)
|
||||
let quarkItems = await detailContentQuark(quark_share_url_list)
|
||||
let quarkPlayFormatList = getQuarkPlayFormatList();
|
||||
await getVod(aliPlayFormatList,aliName,playVod,aliItems.video_items,aliItems.sub_items,type_name)
|
||||
await getVod(quarkPlayFormatList,quarkName,playVod,quarkItems.video_items,quarkItems.sub_items,type_name)
|
||||
|
||||
return playVod
|
||||
}
|
||||
|
||||
|
||||
async function getVod(play_foramt_list,cloud_name,playVod,video_item_list, sub_item_list, type_name) {
|
||||
if (video_item_list.length ==0){
|
||||
return
|
||||
}
|
||||
for (let i=0; i<video_item_list.slice(-1)[0].shareIndex;i++){
|
||||
for (let index = 0; index < play_foramt_list.length; index++) {
|
||||
let vodItems = []
|
||||
for (const video_item of video_item_list) {
|
||||
if (video_item.shareIndex === i + 1){
|
||||
vodItems.push( video_item.getEpisodeUrl(type_name)+ findSubs(video_item.getName(), sub_item_list)); }
|
||||
}
|
||||
playVod[`${cloud_name}-${i+1}-${play_foramt_list[index]}`] = vodItems.join("#")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//字幕匹配
|
||||
function pair(name, item_list, sub_item_list) {
|
||||
for (const item of item_list) {
|
||||
const sub_name = Utils.removeExt(item.getName()).toLowerCase();
|
||||
if (name.indexOf(sub_name) > -1 || sub_name.indexOf(name) > -1) {
|
||||
sub_item_list.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//找出所有字幕
|
||||
function findSubs(name, item_list) {
|
||||
let sub_item_list = [];
|
||||
pair(Utils.removeExt(name).toLowerCase(), item_list, sub_item_list);
|
||||
if (sub_item_list.length === 0) {
|
||||
for (const item of item_list) {
|
||||
sub_item_list.push(item);
|
||||
}
|
||||
}
|
||||
let sub_str = "";
|
||||
for (const item of sub_item_list) {
|
||||
sub_str += "+" + Utils.removeExt(item.getName()) + "@@@" + item.getExt() + "@@@" + item.getFileId();
|
||||
}
|
||||
return sub_str;
|
||||
}
|
||||
async function playContent(flag,id,flags){
|
||||
if (flag.indexOf(aliName) > -1) {
|
||||
return await playContentAli(flag, id, flags)
|
||||
}else if (flag.indexOf(quarkName) > -1){
|
||||
return await playContentQuark(flag,id,flags)
|
||||
}
|
||||
}
|
||||
|
||||
function getHeaders(flag){
|
||||
if (flag.indexOf(aliName) > -1) {
|
||||
return {}
|
||||
}else if (flag.indexOf(quarkName) > -1){
|
||||
return getQuarkHeaders()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export {initCloud,detailContent,playContent,getHeaders,aliName,quarkName}
|
188
cat/tjs/lib/danmuSpider.js
Normal file
188
cat/tjs/lib/danmuSpider.js
Normal file
@ -0,0 +1,188 @@
|
||||
/*
|
||||
* @File : danmuSpider.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/3/13 13:39
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
|
||||
import {_, load, Uri} from "./cat.js";
|
||||
import * as Utils from "./utils.js";
|
||||
import {JadeLogging} from "./log.js";
|
||||
import {VodDetail, VodShort} from "./vod.js";
|
||||
import {parseXML} from "./bilibili_ASS_Danmaku_Downloader.js";
|
||||
|
||||
class DanmuSpider {
|
||||
constructor() {
|
||||
this.siteUrl = "https://search.youku.com"
|
||||
this.reconnectTimes = 0
|
||||
this.maxReconnectTimes = 5
|
||||
this.jadeLog = new JadeLogging(this.getAppName(), "DEBUG")
|
||||
}
|
||||
|
||||
getAppName() {
|
||||
return "弹幕"
|
||||
}
|
||||
|
||||
getHeader() {
|
||||
return {"User-Agent": Utils.CHROME, "Referer": this.siteUrl + "/"};
|
||||
}
|
||||
|
||||
async reconnnect(reqUrl, params, headers, redirect_url, return_cookie, buffer) {
|
||||
await this.jadeLog.error("请求失败,请检查url:" + reqUrl + ",两秒后重试")
|
||||
Utils.sleep(2)
|
||||
if (this.reconnectTimes < this.maxReconnectTimes) {
|
||||
this.reconnectTimes = this.reconnectTimes + 1
|
||||
return await this.fetch(reqUrl, params, headers, redirect_url, return_cookie, buffer)
|
||||
} else {
|
||||
await this.jadeLog.error("请求失败,重连失败")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async getResponse(reqUrl, params, headers, redirect_url, return_cookie, buffer, response) {
|
||||
{
|
||||
if (response.headers["location"] !== undefined) {
|
||||
if (redirect_url) {
|
||||
await this.jadeLog.debug(`返回重定向连接:${response.headers["location"]}`)
|
||||
return response.headers["location"]
|
||||
} else {
|
||||
return this.fetch(response.headers["location"], params, headers, redirect_url, return_cookie, buffer)
|
||||
}
|
||||
} else if (response.content.length > 0) {
|
||||
this.reconnectTimes = 0
|
||||
if (return_cookie) {
|
||||
return {"cookie": response.headers["set-cookie"], "content": response.content}
|
||||
} else {
|
||||
return response.content
|
||||
}
|
||||
} else if (buffer === 1) {
|
||||
this.reconnectTimes = 0
|
||||
return response.content
|
||||
} else {
|
||||
await this.jadeLog.error(`请求失败,请求url为:${reqUrl},回复内容为:${JSON.stringify(response)}`)
|
||||
return await this.reconnnect(reqUrl, params, headers, redirect_url, return_cookie, buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async fetch(reqUrl, params, headers, redirect_url = false, return_cookie = false, buffer = 0) {
|
||||
let data = Utils.objectToStr(params)
|
||||
let url = reqUrl
|
||||
if (!_.isEmpty(data)) {
|
||||
url = reqUrl + "?" + data
|
||||
}
|
||||
let uri = new Uri(url);
|
||||
let response;
|
||||
response = await req(uri.toString(), {method: "get", headers: headers, buffer: buffer, data: null})
|
||||
if (response.code === 201 || response.code === 200 || response.code === 302 || response.code === 301 || return_cookie) {
|
||||
return await this.getResponse(reqUrl, params, headers, redirect_url, return_cookie, buffer, response)
|
||||
} else {
|
||||
await this.jadeLog.error(`请求失败,失败原因为:状态码出错,请求url为:${uri},回复内容为:${JSON.stringify(response)}`)
|
||||
return await this.reconnnect(reqUrl, params, headers, redirect_url, return_cookie, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
async getHtml(url = this.siteUrl, headers = this.getHeader()) {
|
||||
let html = await this.fetch(url, null, headers)
|
||||
if (!_.isEmpty(html)) {
|
||||
return load(html)
|
||||
} else {
|
||||
await this.jadeLog.error(`html获取失败`, true)
|
||||
}
|
||||
}
|
||||
|
||||
async parseVodShortListFromJson(obj, vodDetail) {
|
||||
for (const componentObj of obj["pageComponentList"]) {
|
||||
if (componentObj["commonData"] !== undefined) {
|
||||
let searchVodDetail = new VodDetail()
|
||||
let commonData = componentObj["commonData"]
|
||||
searchVodDetail.type_name = commonData["feature"]
|
||||
if (commonData["notice"] !== undefined) {
|
||||
searchVodDetail.vod_actor = commonData["notice"].replaceAll("演员:", "").replaceAll(" ", "")
|
||||
}
|
||||
if (commonData["director"] !== undefined) {
|
||||
searchVodDetail.vod_director = commonData["director"].replaceAll("导演:", "").replaceAll(" ", "")
|
||||
}
|
||||
if (vodDetail.type_name === "电影") {
|
||||
searchVodDetail.vod_id = commonData["leftButtonDTO"]["action"]["value"]
|
||||
} else {
|
||||
searchVodDetail.vod_id = commonData["showId"]
|
||||
}
|
||||
searchVodDetail.vod_name = commonData["titleDTO"]["displayName"]
|
||||
if ( searchVodDetail.vod_name === vodDetail.vod_name || searchVodDetail.type_name.indexOf(vodDetail.vod_year) > -1 || searchVodDetail.type_name.indexOf(vodDetail.type_name) > -1 || searchVodDetail.vod_director === vodDetail.vod_director) {
|
||||
await this.jadeLog.debug(`匹配视频网站成功,名称为:${searchVodDetail.vod_name},类型为:${searchVodDetail.type_name},导演为:${searchVodDetail.vod_director}`, true)
|
||||
return searchVodDetail
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.jadeLog.warning("没有匹配到弹幕网站")
|
||||
return null
|
||||
}
|
||||
|
||||
async parseVodUrlFromJsonByEpisodeId(obj, episodeId) {
|
||||
for (const serises of obj["serisesList"]) {
|
||||
if (Utils.isNumeric(episodeId["episodeId"])) {
|
||||
if (parseInt(episodeId["episodeId"]).toString() === serises["displayName"]) {
|
||||
return serises["action"]["value"]
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.jadeLog.error("没有找到匹配的集数")
|
||||
return ""
|
||||
}
|
||||
|
||||
async downloadDanmu(url) {
|
||||
// 如果没有找到弹幕的话,容易导致卡在这一步,从而导致结果加载不出来
|
||||
let response = await req(url,{headers:this.getHeader()})
|
||||
if (response.code === 200){
|
||||
let xml = parseXML(JSON.parse(response.content))
|
||||
let params = {"do": "set", "key": "danmu", "value": xml}
|
||||
await req("http://127.0.0.1:9978/cache", {method: "post", data: params, postType: "form-data"});
|
||||
return "http://127.0.0.1:9978/cache?do=get&key=danmu"
|
||||
}
|
||||
else{
|
||||
this.jadeLog.error(`弹幕请求失败,返回结果为:${JSON.stringify(response)}`)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
async search(vodDetail, episodeId) {
|
||||
let params = {"pg": "1", "keyword": vodDetail.vod_name}
|
||||
let searchObj = JSON.parse(await this.fetch(this.siteUrl + "/api/search", params, this.getHeader()))
|
||||
let searchDetail = await this.parseVodShortListFromJson(searchObj, vodDetail)
|
||||
if (!_.isEmpty(searchDetail)){
|
||||
return await this.getVideoUrl(searchDetail.vod_id, episodeId)
|
||||
}else{
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
async getVideoUrl(showId, episodeId) {
|
||||
let url = "";
|
||||
if (!_.isEmpty(showId)) {
|
||||
if (showId.startsWith("http")) {
|
||||
url = showId
|
||||
} else {
|
||||
let params = {"appScene": "show_episode", "showIds": showId}
|
||||
let matchObj = JSON.parse(await this.fetch(this.siteUrl + "/api/search", params, this.getHeader()))
|
||||
url = await this.parseVodUrlFromJsonByEpisodeId(matchObj, episodeId)
|
||||
}
|
||||
if (!_.isEmpty(url)) {
|
||||
await this.jadeLog.debug(`弹幕视频播放连接为:${url}`)
|
||||
return await this.downloadDanmu("https://dmku.thefilehosting.com/?ac=dm&url=" + url)
|
||||
}
|
||||
}
|
||||
return url
|
||||
|
||||
}
|
||||
|
||||
|
||||
async getDammu(voddetail, episodeId) {
|
||||
return await this.search(voddetail, episodeId)
|
||||
}
|
||||
}
|
||||
|
||||
export {DanmuSpider}
|
37
cat/tjs/lib/encoding-indexes.js
Normal file
37
cat/tjs/lib/encoding-indexes.js
Normal file
File diff suppressed because one or more lines are too long
460
cat/tjs/lib/encodings.js
Normal file
460
cat/tjs/lib/encodings.js
Normal file
@ -0,0 +1,460 @@
|
||||
/**
|
||||
* Encodings table: https://encoding.spec.whatwg.org/encodings.json
|
||||
*/
|
||||
const encodings = [
|
||||
{
|
||||
encodings: [
|
||||
{
|
||||
labels: [
|
||||
"unicode-1-1-utf-8",
|
||||
"utf-8",
|
||||
"utf8",
|
||||
],
|
||||
name: "UTF-8",
|
||||
},
|
||||
],
|
||||
heading: "The Encoding",
|
||||
},
|
||||
{
|
||||
encodings: [
|
||||
{
|
||||
labels: [
|
||||
"866",
|
||||
"cp866",
|
||||
"csibm866",
|
||||
"ibm866",
|
||||
],
|
||||
name: "IBM866",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csisolatin2",
|
||||
"iso-8859-2",
|
||||
"iso-ir-101",
|
||||
"iso8859-2",
|
||||
"iso88592",
|
||||
"iso_8859-2",
|
||||
"iso_8859-2:1987",
|
||||
"l2",
|
||||
"latin2",
|
||||
],
|
||||
name: "ISO-8859-2",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csisolatin3",
|
||||
"iso-8859-3",
|
||||
"iso-ir-109",
|
||||
"iso8859-3",
|
||||
"iso88593",
|
||||
"iso_8859-3",
|
||||
"iso_8859-3:1988",
|
||||
"l3",
|
||||
"latin3",
|
||||
],
|
||||
name: "ISO-8859-3",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csisolatin4",
|
||||
"iso-8859-4",
|
||||
"iso-ir-110",
|
||||
"iso8859-4",
|
||||
"iso88594",
|
||||
"iso_8859-4",
|
||||
"iso_8859-4:1988",
|
||||
"l4",
|
||||
"latin4",
|
||||
],
|
||||
name: "ISO-8859-4",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csisolatincyrillic",
|
||||
"cyrillic",
|
||||
"iso-8859-5",
|
||||
"iso-ir-144",
|
||||
"iso8859-5",
|
||||
"iso88595",
|
||||
"iso_8859-5",
|
||||
"iso_8859-5:1988",
|
||||
],
|
||||
name: "ISO-8859-5",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"arabic",
|
||||
"asmo-708",
|
||||
"csiso88596e",
|
||||
"csiso88596i",
|
||||
"csisolatinarabic",
|
||||
"ecma-114",
|
||||
"iso-8859-6",
|
||||
"iso-8859-6-e",
|
||||
"iso-8859-6-i",
|
||||
"iso-ir-127",
|
||||
"iso8859-6",
|
||||
"iso88596",
|
||||
"iso_8859-6",
|
||||
"iso_8859-6:1987",
|
||||
],
|
||||
name: "ISO-8859-6",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csisolatingreek",
|
||||
"ecma-118",
|
||||
"elot_928",
|
||||
"greek",
|
||||
"greek8",
|
||||
"iso-8859-7",
|
||||
"iso-ir-126",
|
||||
"iso8859-7",
|
||||
"iso88597",
|
||||
"iso_8859-7",
|
||||
"iso_8859-7:1987",
|
||||
"sun_eu_greek",
|
||||
],
|
||||
name: "ISO-8859-7",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csiso88598e",
|
||||
"csisolatinhebrew",
|
||||
"hebrew",
|
||||
"iso-8859-8",
|
||||
"iso-8859-8-e",
|
||||
"iso-ir-138",
|
||||
"iso8859-8",
|
||||
"iso88598",
|
||||
"iso_8859-8",
|
||||
"iso_8859-8:1988",
|
||||
"visual",
|
||||
],
|
||||
name: "ISO-8859-8",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csiso88598i",
|
||||
"iso-8859-8-i",
|
||||
"logical",
|
||||
],
|
||||
name: "ISO-8859-8-I",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csisolatin6",
|
||||
"iso-8859-10",
|
||||
"iso-ir-157",
|
||||
"iso8859-10",
|
||||
"iso885910",
|
||||
"l6",
|
||||
"latin6",
|
||||
],
|
||||
name: "ISO-8859-10",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"iso-8859-13",
|
||||
"iso8859-13",
|
||||
"iso885913",
|
||||
],
|
||||
name: "ISO-8859-13",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"iso-8859-14",
|
||||
"iso8859-14",
|
||||
"iso885914",
|
||||
],
|
||||
name: "ISO-8859-14",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csisolatin9",
|
||||
"iso-8859-15",
|
||||
"iso8859-15",
|
||||
"iso885915",
|
||||
"iso_8859-15",
|
||||
"l9",
|
||||
],
|
||||
name: "ISO-8859-15",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"iso-8859-16",
|
||||
],
|
||||
name: "ISO-8859-16",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cskoi8r",
|
||||
"koi",
|
||||
"koi8",
|
||||
"koi8-r",
|
||||
"koi8_r",
|
||||
],
|
||||
name: "KOI8-R",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"koi8-ru",
|
||||
"koi8-u",
|
||||
],
|
||||
name: "KOI8-U",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csmacintosh",
|
||||
"mac",
|
||||
"macintosh",
|
||||
"x-mac-roman",
|
||||
],
|
||||
name: "macintosh",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"dos-874",
|
||||
"iso-8859-11",
|
||||
"iso8859-11",
|
||||
"iso885911",
|
||||
"tis-620",
|
||||
"windows-874",
|
||||
],
|
||||
name: "windows-874",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1250",
|
||||
"windows-1250",
|
||||
"x-cp1250",
|
||||
],
|
||||
name: "windows-1250",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1251",
|
||||
"windows-1251",
|
||||
"x-cp1251",
|
||||
],
|
||||
name: "windows-1251",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"ansi_x3.4-1968",
|
||||
"ascii",
|
||||
"cp1252",
|
||||
"cp819",
|
||||
"csisolatin1",
|
||||
"ibm819",
|
||||
"iso-8859-1",
|
||||
"iso-ir-100",
|
||||
"iso8859-1",
|
||||
"iso88591",
|
||||
"iso_8859-1",
|
||||
"iso_8859-1:1987",
|
||||
"l1",
|
||||
"latin1",
|
||||
"us-ascii",
|
||||
"windows-1252",
|
||||
"x-cp1252",
|
||||
],
|
||||
name: "windows-1252",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1253",
|
||||
"windows-1253",
|
||||
"x-cp1253",
|
||||
],
|
||||
name: "windows-1253",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1254",
|
||||
"csisolatin5",
|
||||
"iso-8859-9",
|
||||
"iso-ir-148",
|
||||
"iso8859-9",
|
||||
"iso88599",
|
||||
"iso_8859-9",
|
||||
"iso_8859-9:1989",
|
||||
"l5",
|
||||
"latin5",
|
||||
"windows-1254",
|
||||
"x-cp1254",
|
||||
],
|
||||
name: "windows-1254",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1255",
|
||||
"windows-1255",
|
||||
"x-cp1255",
|
||||
],
|
||||
name: "windows-1255",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1256",
|
||||
"windows-1256",
|
||||
"x-cp1256",
|
||||
],
|
||||
name: "windows-1256",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1257",
|
||||
"windows-1257",
|
||||
"x-cp1257",
|
||||
],
|
||||
name: "windows-1257",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"cp1258",
|
||||
"windows-1258",
|
||||
"x-cp1258",
|
||||
],
|
||||
name: "windows-1258",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"x-mac-cyrillic",
|
||||
"x-mac-ukrainian",
|
||||
],
|
||||
name: "x-mac-cyrillic",
|
||||
},
|
||||
],
|
||||
heading: "Legacy single-byte encodings",
|
||||
},
|
||||
{
|
||||
encodings: [
|
||||
{
|
||||
labels: [
|
||||
"chinese",
|
||||
"csgb2312",
|
||||
"csiso58gb231280",
|
||||
"gb2312",
|
||||
"gb_2312",
|
||||
"gb_2312-80",
|
||||
"gbk",
|
||||
"iso-ir-58",
|
||||
"x-gbk",
|
||||
],
|
||||
name: "GBK",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"gb18030",
|
||||
],
|
||||
name: "gb18030",
|
||||
},
|
||||
],
|
||||
heading: "Legacy multi-byte Chinese (simplified) encodings",
|
||||
},
|
||||
{
|
||||
encodings: [
|
||||
{
|
||||
labels: [
|
||||
"big5",
|
||||
"big5-hkscs",
|
||||
"cn-big5",
|
||||
"csbig5",
|
||||
"x-x-big5",
|
||||
],
|
||||
name: "Big5",
|
||||
},
|
||||
],
|
||||
heading: "Legacy multi-byte Chinese (traditional) encodings",
|
||||
},
|
||||
{
|
||||
encodings: [
|
||||
{
|
||||
labels: [
|
||||
"cseucpkdfmtjapanese",
|
||||
"euc-jp",
|
||||
"x-euc-jp",
|
||||
],
|
||||
name: "EUC-JP",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csiso2022jp",
|
||||
"iso-2022-jp",
|
||||
],
|
||||
name: "ISO-2022-JP",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"csshiftjis",
|
||||
"ms932",
|
||||
"ms_kanji",
|
||||
"shift-jis",
|
||||
"shift_jis",
|
||||
"sjis",
|
||||
"windows-31j",
|
||||
"x-sjis",
|
||||
],
|
||||
name: "Shift_JIS",
|
||||
},
|
||||
],
|
||||
heading: "Legacy multi-byte Japanese encodings",
|
||||
},
|
||||
{
|
||||
encodings: [
|
||||
{
|
||||
labels: [
|
||||
"cseuckr",
|
||||
"csksc56011987",
|
||||
"euc-kr",
|
||||
"iso-ir-149",
|
||||
"korean",
|
||||
"ks_c_5601-1987",
|
||||
"ks_c_5601-1989",
|
||||
"ksc5601",
|
||||
"ksc_5601",
|
||||
"windows-949",
|
||||
],
|
||||
name: "EUC-KR",
|
||||
},
|
||||
],
|
||||
heading: "Legacy multi-byte Korean encodings",
|
||||
},
|
||||
{
|
||||
encodings: [
|
||||
{
|
||||
labels: [
|
||||
"csiso2022kr",
|
||||
"hz-gb-2312",
|
||||
"iso-2022-cn",
|
||||
"iso-2022-cn-ext",
|
||||
"iso-2022-kr",
|
||||
],
|
||||
name: "replacement",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"utf-16be",
|
||||
],
|
||||
name: "UTF-16BE",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"utf-16",
|
||||
"utf-16le",
|
||||
],
|
||||
name: "UTF-16LE",
|
||||
},
|
||||
{
|
||||
labels: [
|
||||
"x-user-defined",
|
||||
],
|
||||
name: "x-user-defined",
|
||||
},
|
||||
],
|
||||
heading: "Legacy miscellaneous encodings",
|
||||
},
|
||||
]
|
||||
|
||||
export default encodings
|
166
cat/tjs/lib/euc-jp.js
Normal file
166
cat/tjs/lib/euc-jp.js
Normal file
@ -0,0 +1,166 @@
|
||||
import { inRange, decoderError, encoderError, isASCIICodePoint,
|
||||
end_of_stream, finished, isASCIIByte, floor } from './text_decoder_utils.js'
|
||||
import index, { indexCodePointFor, indexPointerFor } from './text_decoder_indexes.js'
|
||||
|
||||
//
|
||||
// 13. Legacy multi-byte Japanese encodings
|
||||
//
|
||||
|
||||
// 13.1 euc-jp
|
||||
|
||||
// 13.1.1 euc-jp decoder
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class EUCJPDecoder {
|
||||
constructor(options) {
|
||||
const { fatal } = options
|
||||
this.fatal = fatal
|
||||
|
||||
// euc-jp's decoder has an associated euc-jp jis0212 flag
|
||||
// (initially unset) and euc-jp lead (initially 0x00).
|
||||
this.eucjp_jis0212_flag = false
|
||||
this.eucjp_lead = 0x00
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream and euc-jp lead is not 0x00, set
|
||||
// euc-jp lead to 0x00, and return error.
|
||||
if (bite === end_of_stream && this.eucjp_lead !== 0x00) {
|
||||
this.eucjp_lead = 0x00
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 2. If byte is end-of-stream and euc-jp lead is 0x00, return
|
||||
// finished.
|
||||
if (bite === end_of_stream && this.eucjp_lead === 0x00)
|
||||
return finished
|
||||
|
||||
// 3. If euc-jp lead is 0x8E and byte is in the range 0xA1 to
|
||||
// 0xDF, inclusive, set euc-jp lead to 0x00 and return a code
|
||||
// point whose value is 0xFF61 − 0xA1 + byte.
|
||||
if (this.eucjp_lead === 0x8E && inRange(bite, 0xA1, 0xDF)) {
|
||||
this.eucjp_lead = 0x00
|
||||
return 0xFF61 - 0xA1 + bite
|
||||
}
|
||||
|
||||
// 4. If euc-jp lead is 0x8F and byte is in the range 0xA1 to
|
||||
// 0xFE, inclusive, set the euc-jp jis0212 flag, set euc-jp lead
|
||||
// to byte, and return continue.
|
||||
if (this.eucjp_lead === 0x8F && inRange(bite, 0xA1, 0xFE)) {
|
||||
this.eucjp_jis0212_flag = true
|
||||
this.eucjp_lead = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 5. If euc-jp lead is not 0x00, let lead be euc-jp lead, set
|
||||
// euc-jp lead to 0x00, and run these substeps:
|
||||
if (this.eucjp_lead !== 0x00) {
|
||||
const lead = this.eucjp_lead
|
||||
this.eucjp_lead = 0x00
|
||||
|
||||
// 1. Let code point be null.
|
||||
let code_point = null
|
||||
|
||||
// 2. If lead and byte are both in the range 0xA1 to 0xFE,
|
||||
// inclusive, set code point to the index code point for (lead
|
||||
// − 0xA1) × 94 + byte − 0xA1 in index jis0208 if the euc-jp
|
||||
// jis0212 flag is unset and in index jis0212 otherwise.
|
||||
if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) {
|
||||
code_point = indexCodePointFor(
|
||||
(lead - 0xA1) * 94 + (bite - 0xA1),
|
||||
index(!this.eucjp_jis0212_flag ? 'jis0208' : 'jis0212'))
|
||||
}
|
||||
|
||||
// 3. Unset the euc-jp jis0212 flag.
|
||||
this.eucjp_jis0212_flag = false
|
||||
|
||||
// 4. If byte is not in the range 0xA1 to 0xFE, inclusive,
|
||||
// prepend byte to stream.
|
||||
if (!inRange(bite, 0xA1, 0xFE))
|
||||
stream.prepend(bite)
|
||||
|
||||
// 5. If code point is null, return error.
|
||||
if (code_point === null)
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 6. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
|
||||
// 6. If byte is an ASCII byte, return a code point whose value
|
||||
// is byte.
|
||||
if (isASCIIByte(bite))
|
||||
return bite
|
||||
|
||||
// 7. If byte is 0x8E, 0x8F, or in the range 0xA1 to 0xFE,
|
||||
// inclusive, set euc-jp lead to byte and return continue.
|
||||
if (bite === 0x8E || bite === 0x8F || inRange(bite, 0xA1, 0xFE)) {
|
||||
this.eucjp_lead = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 8. Return error.
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
}
|
||||
|
||||
// 13.1.2 euc-jp encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class EUCJPEncoder {
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point, return a byte whose
|
||||
// value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 3. If code point is U+00A5, return byte 0x5C.
|
||||
if (code_point === 0x00A5)
|
||||
return 0x5C
|
||||
|
||||
// 4. If code point is U+203E, return byte 0x7E.
|
||||
if (code_point === 0x203E)
|
||||
return 0x7E
|
||||
|
||||
// 5. If code point is in the range U+FF61 to U+FF9F, inclusive,
|
||||
// return two bytes whose values are 0x8E and code point −
|
||||
// 0xFF61 + 0xA1.
|
||||
if (inRange(code_point, 0xFF61, 0xFF9F))
|
||||
return [0x8E, code_point - 0xFF61 + 0xA1]
|
||||
|
||||
// 6. If code point is U+2212, set it to U+FF0D.
|
||||
if (code_point === 0x2212)
|
||||
code_point = 0xFF0D
|
||||
|
||||
// 7. Let pointer be the index pointer for code point in index
|
||||
// jis0208.
|
||||
const pointer = indexPointerFor(code_point, index('jis0208'))
|
||||
|
||||
// 8. If pointer is null, return error with code point.
|
||||
if (pointer === null)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 9. Let lead be floor(pointer / 94) + 0xA1.
|
||||
const lead = floor(pointer / 94) + 0xA1
|
||||
|
||||
// 10. Let trail be pointer % 94 + 0xA1.
|
||||
const trail = pointer % 94 + 0xA1
|
||||
|
||||
// 11. Return two bytes whose values are lead and trail.
|
||||
return [lead, trail]
|
||||
}
|
||||
}
|
124
cat/tjs/lib/euc-kr.js
Normal file
124
cat/tjs/lib/euc-kr.js
Normal file
@ -0,0 +1,124 @@
|
||||
import { inRange, decoderError, encoderError, isASCIICodePoint,
|
||||
end_of_stream, finished, isASCIIByte, floor } from './text_decoder_utils.js'
|
||||
import index, { indexCodePointFor, indexPointerFor } from './text_decoder_indexes.js'
|
||||
|
||||
//
|
||||
// 14. Legacy multi-byte Korean encodings
|
||||
//
|
||||
|
||||
// 14.1 euc-kr
|
||||
|
||||
// 14.1.1 euc-kr decoder
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class EUCKRDecoder {
|
||||
constructor(options) {
|
||||
const { fatal } = options
|
||||
this.fatal = fatal
|
||||
// euc-kr's decoder has an associated euc-kr lead (initially 0x00).
|
||||
this.euckr_lead = 0x00
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream and euc-kr lead is not 0x00, set
|
||||
// euc-kr lead to 0x00 and return error.
|
||||
if (bite === end_of_stream && this.euckr_lead !== 0) {
|
||||
this.euckr_lead = 0x00
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 2. If byte is end-of-stream and euc-kr lead is 0x00, return
|
||||
// finished.
|
||||
if (bite === end_of_stream && this.euckr_lead === 0)
|
||||
return finished
|
||||
|
||||
// 3. If euc-kr lead is not 0x00, let lead be euc-kr lead, let
|
||||
// pointer be null, set euc-kr lead to 0x00, and then run these
|
||||
// substeps:
|
||||
if (this.euckr_lead !== 0x00) {
|
||||
const lead = this.euckr_lead
|
||||
let pointer = null
|
||||
this.euckr_lead = 0x00
|
||||
|
||||
// 1. If byte is in the range 0x41 to 0xFE, inclusive, set
|
||||
// pointer to (lead − 0x81) × 190 + (byte − 0x41).
|
||||
if (inRange(bite, 0x41, 0xFE))
|
||||
pointer = (lead - 0x81) * 190 + (bite - 0x41)
|
||||
|
||||
// 2. Let code point be null, if pointer is null, and the
|
||||
// index code point for pointer in index euc-kr otherwise.
|
||||
const code_point = (pointer === null)
|
||||
? null : indexCodePointFor(pointer, index('euc-kr'))
|
||||
|
||||
// 3. If code point is null and byte is an ASCII byte, prepend
|
||||
// byte to stream.
|
||||
if (pointer === null && isASCIIByte(bite))
|
||||
stream.prepend(bite)
|
||||
|
||||
// 4. If code point is null, return error.
|
||||
if (code_point === null)
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 5. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
|
||||
// 4. If byte is an ASCII byte, return a code point whose value
|
||||
// is byte.
|
||||
if (isASCIIByte(bite))
|
||||
return bite
|
||||
|
||||
// 5. If byte is in the range 0x81 to 0xFE, inclusive, set
|
||||
// euc-kr lead to byte and return continue.
|
||||
if (inRange(bite, 0x81, 0xFE)) {
|
||||
this.euckr_lead = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 6. Return error.
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
}
|
||||
|
||||
// 14.1.2 euc-kr encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class EUCKREncoder {
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
* @return {(number|!Array.<number>)} Byte(s) to emit.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point, return a byte whose
|
||||
// value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 3. Let pointer be the index pointer for code point in index
|
||||
// euc-kr.
|
||||
const pointer = indexPointerFor(code_point, index('euc-kr'))
|
||||
|
||||
// 4. If pointer is null, return error with code point.
|
||||
if (pointer === null)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 5. Let lead be floor(pointer / 190) + 0x81.
|
||||
const lead = floor(pointer / 190) + 0x81
|
||||
|
||||
// 6. Let trail be pointer % 190 + 0x41.
|
||||
const trail = (pointer % 190) + 0x41
|
||||
|
||||
// 7. Return two bytes whose values are lead and trail.
|
||||
return [lead, trail]
|
||||
}
|
||||
}
|
480
cat/tjs/lib/ffm3u8_open.js
Normal file
480
cat/tjs/lib/ffm3u8_open.js
Normal file
@ -0,0 +1,480 @@
|
||||
/*
|
||||
* @File : ffm3u8_open.js.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/2/5 16:06
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
import { _ } from './cat.js';
|
||||
import * as HLS from './hls.js';
|
||||
|
||||
let key = 'ffm3u8';
|
||||
let url = '';
|
||||
let categories = [];
|
||||
let siteKey = '';
|
||||
let siteType = 0;
|
||||
|
||||
async function request(reqUrl, agentSp) {
|
||||
let res = await req(reqUrl, {
|
||||
method: 'get',
|
||||
});
|
||||
return JSON.parse(res.content);
|
||||
}
|
||||
|
||||
async function init(cfg) {
|
||||
siteKey = cfg.skey;
|
||||
siteType = cfg.stype;
|
||||
url = cfg.ext.url;
|
||||
categories = cfg.ext.categories;
|
||||
}
|
||||
|
||||
async function home(filter) {
|
||||
const data = await request(url);
|
||||
let classes = [];
|
||||
for (const cls of data.class) {
|
||||
const n = cls.type_name.toString().trim();
|
||||
if (categories && categories.length > 0) {
|
||||
if (categories.indexOf(n) < 0) continue;
|
||||
}
|
||||
classes.push({
|
||||
type_id: cls.type_id.toString(),
|
||||
type_name: n,
|
||||
});
|
||||
}
|
||||
if (categories && categories.length > 0) {
|
||||
classes = _.sortBy(classes, (p) => {
|
||||
return categories.indexOf(p.type_name);
|
||||
});
|
||||
}
|
||||
return {
|
||||
class: classes,
|
||||
};
|
||||
}
|
||||
|
||||
async function homeVod() {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
async function category(tid, pg, filter, extend) {
|
||||
let page = pg || 1;
|
||||
if (page == 0) page = 1;
|
||||
const data = await request(url + `?ac=detail&t=${tid}&pg=${page}`);
|
||||
let videos = [];
|
||||
for (const vod of data.list) {
|
||||
videos.push({
|
||||
vod_id: vod.vod_id.toString(),
|
||||
vod_name: vod.vod_name.toString(),
|
||||
vod_pic: vod.vod_pic,
|
||||
vod_remarks: vod.vod_remarks,
|
||||
});
|
||||
}
|
||||
return {
|
||||
page: parseInt(data.page),
|
||||
pagecount: data.pagecount,
|
||||
total: data.total,
|
||||
list: videos,
|
||||
};
|
||||
}
|
||||
|
||||
async function detail(id) {
|
||||
const data = (await request(url + `?ac=detail&ids=${id}`)).list[0];
|
||||
let vod = {
|
||||
vod_id: data.vod_id,
|
||||
vod_name: data.vod_name,
|
||||
vod_pic: data.vod_pic,
|
||||
type_name: data.type_name,
|
||||
vod_year: data.vod_year,
|
||||
vod_area: data.vod_area,
|
||||
vod_remarks: data.vod_remarks,
|
||||
vod_actor: data.vod_actor,
|
||||
vod_director: data.vod_director,
|
||||
vod_content: data.vod_content.trim(),
|
||||
vod_play_from: data.vod_play_from,
|
||||
vod_play_url: data.vod_play_url,
|
||||
};
|
||||
return {
|
||||
list: [vod],
|
||||
};
|
||||
}
|
||||
|
||||
async function proxy(segments, headers, reqHeaders) {
|
||||
let what = segments[0];
|
||||
let segs = decodeURIComponent(segments[1]);
|
||||
if (what == 'hls') {
|
||||
function hlsHeader(data, hls) {
|
||||
let hlsHeaders = {};
|
||||
if (data.headers['content-length']) {
|
||||
Object.assign(hlsHeaders, data.headers, { 'content-length': hls.length.toString() });
|
||||
} else {
|
||||
Object.assign(hlsHeaders, data.headers);
|
||||
}
|
||||
delete hlsHeaders['transfer-encoding'];
|
||||
if (hlsHeaders['content-encoding'] == 'gzip') {
|
||||
delete hlsHeaders['content-encoding'];
|
||||
}
|
||||
return hlsHeaders;
|
||||
}
|
||||
const hlsData = await hlsCache(segs, headers);
|
||||
if (hlsData.variants) {
|
||||
// variants -> variants -> .... ignore
|
||||
const hls = HLS.stringify(hlsData.plist);
|
||||
return {
|
||||
code: hlsData.code,
|
||||
content: hls,
|
||||
headers: hlsHeader(hlsData, hls),
|
||||
};
|
||||
} else {
|
||||
const hls = HLS.stringify(hlsData.plist, (segment) => {
|
||||
return js2Proxy(false, siteType, siteKey, 'ts/' + encodeURIComponent(hlsData.key + '/' + segment.mediaSequenceNumber.toString()), headers);
|
||||
});
|
||||
return {
|
||||
code: hlsData.code,
|
||||
content: hls,
|
||||
headers: hlsHeader(hlsData, hls),
|
||||
};
|
||||
}
|
||||
} else if (what == 'ts') {
|
||||
const info = segs.split('/');
|
||||
const hlsKey = info[0];
|
||||
const segIdx = parseInt(info[1]);
|
||||
return await tsCache(hlsKey, segIdx, headers);
|
||||
}
|
||||
return '{}';
|
||||
}
|
||||
|
||||
async function play(flag, id, flags) {
|
||||
try {
|
||||
const pUrls = await hls2Urls(id, {});
|
||||
for (let index = 1; index < pUrls.length; index += 2) {
|
||||
pUrls[index] = js2Proxy(false, siteType, siteKey, 'hls/' + encodeURIComponent(pUrls[index]), {});
|
||||
}
|
||||
pUrls.push('original');
|
||||
pUrls.push(id);
|
||||
return {
|
||||
parse: 0,
|
||||
url: pUrls,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
parse: 0,
|
||||
url: id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function search(wd, quick, pg) {
|
||||
let page = pg || 1;
|
||||
if (page == 0) page = 1;
|
||||
const data = await request(url + `?ac=detail&wd=${wd}`);
|
||||
let videos = [];
|
||||
for (const vod of data.list) {
|
||||
videos.push({
|
||||
vod_id: vod.vod_id.toString(),
|
||||
vod_name: vod.vod_name.toString(),
|
||||
vod_pic: vod.vod_pic,
|
||||
vod_remarks: vod.vod_remarks,
|
||||
});
|
||||
}
|
||||
return {
|
||||
page: parseInt(data.page),
|
||||
pagecount: data.pagecount,
|
||||
total: data.total,
|
||||
list: videos,
|
||||
};
|
||||
}
|
||||
|
||||
const cacheRoot = 'hls_cache';
|
||||
const hlsKeys = [];
|
||||
const hlsPlistCaches = {};
|
||||
const interrupts = {};
|
||||
const downloadTask = {};
|
||||
let currentDownloadHlsKey = '';
|
||||
|
||||
function hlsCacheInsert(key, data) {
|
||||
hlsKeys.push(key);
|
||||
hlsPlistCaches[key] = data;
|
||||
if (hlsKeys.length > 5) {
|
||||
const rmKey = hlsKeys.shift();
|
||||
hlsCacheRemove(rmKey);
|
||||
}
|
||||
}
|
||||
|
||||
function hlsCacheRemove(key) {
|
||||
delete hlsPlistCaches[key];
|
||||
delete hlsKeys[key];
|
||||
new JSFile(cacheRoot + '/' + key).delete();
|
||||
}
|
||||
|
||||
function plistUriResolve(baseUrl, plist) {
|
||||
if (plist.variants) {
|
||||
for (const v of plist.variants) {
|
||||
if (!v.uri.startsWith('http')) {
|
||||
v.uri = relative2Absolute(baseUrl, v.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (plist.segments) {
|
||||
for (const s of plist.segments) {
|
||||
if (!s.uri.startsWith('http')) {
|
||||
s.uri = relative2Absolute(baseUrl, s.uri);
|
||||
}
|
||||
if (s.key && s.key.uri && !s.key.uri.startsWith('http')) {
|
||||
s.key.uri = relative2Absolute(baseUrl, s.key.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
return plist;
|
||||
}
|
||||
|
||||
async function hls2Urls(url, headers) {
|
||||
let urls = [];
|
||||
let resp = {};
|
||||
let tmpUrl = url;
|
||||
while (true) {
|
||||
resp = await req(tmpUrl, {
|
||||
headers: headers,
|
||||
redirect: 0,
|
||||
});
|
||||
if (resp.headers['location']) {
|
||||
tmpUrl = resp.headers['location'];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resp.code == 200) {
|
||||
var hls = resp.content;
|
||||
const plist = plistUriResolve(tmpUrl, HLS.parse(hls));
|
||||
if (plist.variants) {
|
||||
for (const vari of _.sortBy(plist.variants, (v) => -1 * v.bandwidth)) {
|
||||
urls.push(`proxy_${vari.resolution.width}x${vari.resolution.height}`);
|
||||
urls.push(vari.uri);
|
||||
}
|
||||
} else {
|
||||
urls.push('proxy');
|
||||
urls.push(url);
|
||||
const hlsKey = md5X(url);
|
||||
hlsCacheInsert(hlsKey, {
|
||||
code: resp.code,
|
||||
plist: plist,
|
||||
key: hlsKey,
|
||||
headers: resp.headers,
|
||||
});
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
async function hlsCache(url, headers) {
|
||||
const hlsKey = md5X(url);
|
||||
if (hlsPlistCaches[hlsKey]) {
|
||||
return hlsPlistCaches[hlsKey];
|
||||
}
|
||||
let resp = {};
|
||||
let tmpUrl = url;
|
||||
while (true) {
|
||||
resp = await req(tmpUrl, {
|
||||
headers: headers,
|
||||
redirect: 0,
|
||||
});
|
||||
if (resp.headers['location']) {
|
||||
tmpUrl = resp.headers['location'];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resp.code == 200) {
|
||||
var hls = resp.content;
|
||||
const plist = plistUriResolve(tmpUrl, HLS.parse(hls));
|
||||
hlsCacheInsert(hlsKey, {
|
||||
code: resp.code,
|
||||
plist: plist,
|
||||
key: hlsKey,
|
||||
headers: resp.headers,
|
||||
});
|
||||
return hlsPlistCaches[hlsKey];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async function tsCache(hlsKey, segmentIndex, headers) {
|
||||
if (!hlsPlistCaches[hlsKey]) {
|
||||
return {};
|
||||
}
|
||||
const plist = hlsPlistCaches[hlsKey].plist;
|
||||
const segments = plist.segments;
|
||||
|
||||
let startFirst = !downloadTask[hlsKey];
|
||||
if (startFirst) {
|
||||
downloadTask[hlsKey] = {};
|
||||
for (const seg of segments) {
|
||||
const tk = md5X(seg.uri + seg.mediaSequenceNumber.toString());
|
||||
downloadTask[hlsKey][tk] = {
|
||||
file: cacheRoot + '/' + hlsKey + '/' + tk,
|
||||
uri: seg.uri,
|
||||
key: tk,
|
||||
index: seg.mediaSequenceNumber,
|
||||
order: seg.mediaSequenceNumber,
|
||||
state: -1,
|
||||
read: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// sort task
|
||||
for (const tk in downloadTask[hlsKey]) {
|
||||
const task = downloadTask[hlsKey][tk];
|
||||
if (task.index >= segmentIndex) {
|
||||
task.order = task.index - segmentIndex;
|
||||
} else {
|
||||
task.order = segments.length - segmentIndex + task.index;
|
||||
}
|
||||
}
|
||||
|
||||
if (startFirst) {
|
||||
fixedCachePool(hlsKey, 5, headers);
|
||||
}
|
||||
|
||||
const segment = segments[segmentIndex];
|
||||
const tsKey = md5X(segment.uri + segment.mediaSequenceNumber.toString());
|
||||
const task = downloadTask[hlsKey][tsKey];
|
||||
if (task.state == 1 || task.state == -1) {
|
||||
const file = new JSFile(task.file);
|
||||
if (await file.exist()) {
|
||||
task.state = 1;
|
||||
// download finish
|
||||
return {
|
||||
buffer: 3,
|
||||
code: 200,
|
||||
headers: {
|
||||
connection: 'close',
|
||||
'content-type': 'video/mp2t',
|
||||
},
|
||||
content: file,
|
||||
};
|
||||
} else {
|
||||
// file miss?? retry
|
||||
task.state = -1;
|
||||
}
|
||||
}
|
||||
if (task.state == -1) {
|
||||
// start download
|
||||
startTsTask(hlsKey, task, headers);
|
||||
}
|
||||
// wait read dwonload
|
||||
if (task.state == 0) {
|
||||
var stream = new JSProxyStream();
|
||||
stream.head(200, {
|
||||
connection: 'close',
|
||||
'content-type': 'video/mp2t',
|
||||
});
|
||||
let downloaded = 0;
|
||||
task.read = true;
|
||||
new Promise(async function (resolve, reject) {
|
||||
const f = new JSFile(task.file + '.dl');
|
||||
await f.open('r');
|
||||
(async function waitReadFile() {
|
||||
const s = await f.size();
|
||||
if (s > downloaded) {
|
||||
var downloadBuf = await f.read(s - downloaded, downloaded);
|
||||
await stream.write(downloadBuf);
|
||||
downloaded = s;
|
||||
}
|
||||
if (task.state == 1 || task.state < 0) {
|
||||
// finish error or done
|
||||
stream.done();
|
||||
await f.close();
|
||||
await f.delete();
|
||||
task.read = false;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
setTimeout(waitReadFile, 5);
|
||||
})();
|
||||
});
|
||||
return {
|
||||
buffer: 3,
|
||||
content: stream,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function startTsTask(hlsKey, task, headers) {
|
||||
if (task.state >= 0) return;
|
||||
if (!interrupts[hlsKey]) {
|
||||
return;
|
||||
}
|
||||
task.state = 0;
|
||||
if (await new JSFile(task.file).exist()) {
|
||||
task.state = 1;
|
||||
return;
|
||||
}
|
||||
const file = new JSFile(task.file + '.dl');
|
||||
await file.open('w');
|
||||
const resp = await req(task.uri, {
|
||||
buffer: 3,
|
||||
headers: headers,
|
||||
stream: file,
|
||||
timeout: [5000, 10000],
|
||||
});
|
||||
if (resp.error || resp.code >= 300) {
|
||||
await file.close();
|
||||
if (!task.read) {
|
||||
await file.delete();
|
||||
}
|
||||
task.state = -1;
|
||||
return;
|
||||
}
|
||||
await file.close();
|
||||
if (task.read) {
|
||||
await file.copy(task.file);
|
||||
} else {
|
||||
await file.move(task.file);
|
||||
}
|
||||
task.state = 1;
|
||||
}
|
||||
|
||||
async function fixedCachePool(hlsKey, limit, headers) {
|
||||
// keep last cache task only
|
||||
if (currentDownloadHlsKey && currentDownloadHlsKey != hlsKey) {
|
||||
delete interrupts[currentDownloadHlsKey];
|
||||
}
|
||||
currentDownloadHlsKey = hlsKey;
|
||||
interrupts[hlsKey] = true;
|
||||
for (let index = 0; index < limit; index++) {
|
||||
if (!interrupts[hlsKey]) break;
|
||||
new Promise(function (resolve, reject) {
|
||||
(async function doTask() {
|
||||
if (!interrupts[hlsKey]) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const tasks = _.pickBy(downloadTask[hlsKey], function (o) {
|
||||
return o.state == -1;
|
||||
});
|
||||
const task = _.minBy(Object.values(tasks), function (o) {
|
||||
return o.order;
|
||||
});
|
||||
if (!task) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
await startTsTask(hlsKey, task, headers);
|
||||
setTimeout(doTask, 5);
|
||||
})();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function relative2Absolute(base, relative) {
|
||||
var stack = base.split('/'),
|
||||
parts = relative.split('/');
|
||||
stack.pop();
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
if (parts[i] == '.') continue;
|
||||
if (parts[i] == '..') stack.pop();
|
||||
else stack.push(parts[i]);
|
||||
}
|
||||
return stack.join('/');
|
||||
}
|
||||
export {hls2Urls,hlsCache,tsCache}
|
251
cat/tjs/lib/gb18030.js
Normal file
251
cat/tjs/lib/gb18030.js
Normal file
@ -0,0 +1,251 @@
|
||||
import { inRange, decoderError, encoderError, isASCIICodePoint,
|
||||
end_of_stream, finished, isASCIIByte, floor } from './text_decoder_utils.js'
|
||||
import index, {
|
||||
indexGB18030RangesCodePointFor, indexGB18030RangesPointerFor,
|
||||
indexCodePointFor, indexPointerFor } from './text_decoder_indexes.js'
|
||||
|
||||
// 11.2 gb18030
|
||||
|
||||
// 11.2.1 gb18030 decoder
|
||||
/**
|
||||
* @constructor
|
||||
* @implements {Decoder}
|
||||
* @param {{fatal: boolean}} options
|
||||
*/
|
||||
export class GB18030Decoder {
|
||||
constructor(options) {
|
||||
const { fatal } = options
|
||||
this.fatal = fatal
|
||||
// gb18030's decoder has an associated gb18030 first, gb18030
|
||||
// second, and gb18030 third (all initially 0x00).
|
||||
this.gb18030_first = 0x00
|
||||
this.gb18030_second = 0x00,
|
||||
this.gb18030_third = 0x00
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
* @return The next code point(s) decoded, or null if not enough data exists in the input stream to decode a complete code point.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream and gb18030 first, gb18030
|
||||
// second, and gb18030 third are 0x00, return finished.
|
||||
if (bite === end_of_stream && this.gb18030_first === 0x00 &&
|
||||
this.gb18030_second === 0x00 && this.gb18030_third === 0x00) {
|
||||
return finished
|
||||
}
|
||||
// 2. If byte is end-of-stream, and gb18030 first, gb18030
|
||||
// second, or gb18030 third is not 0x00, set gb18030 first,
|
||||
// gb18030 second, and gb18030 third to 0x00, and return error.
|
||||
if (bite === end_of_stream &&
|
||||
(this.gb18030_first !== 0x00 || this.gb18030_second !== 0x00 ||
|
||||
this.gb18030_third !== 0x00)) {
|
||||
this.gb18030_first = 0x00
|
||||
this.gb18030_second = 0x00
|
||||
this.gb18030_third = 0x00
|
||||
decoderError(this.fatal)
|
||||
}
|
||||
var code_point
|
||||
// 3. If gb18030 third is not 0x00, run these substeps:
|
||||
if (this.gb18030_third !== 0x00) {
|
||||
// 1. Let code point be null.
|
||||
code_point = null
|
||||
// 2. If byte is in the range 0x30 to 0x39, inclusive, set
|
||||
// code point to the index gb18030 ranges code point for
|
||||
// (((gb18030 first − 0x81) × 10 + gb18030 second − 0x30) ×
|
||||
// 126 + gb18030 third − 0x81) × 10 + byte − 0x30.
|
||||
if (inRange(bite, 0x30, 0x39)) {
|
||||
code_point = indexGB18030RangesCodePointFor(
|
||||
(((this.gb18030_first - 0x81) * 10 + this.gb18030_second - 0x30) * 126 +
|
||||
this.gb18030_third - 0x81) * 10 + bite - 0x30)
|
||||
}
|
||||
|
||||
// 3. Let buffer be a byte sequence consisting of gb18030
|
||||
// second, gb18030 third, and byte, in order.
|
||||
var buffer = [this.gb18030_second, this.gb18030_third, bite]
|
||||
|
||||
// 4. Set gb18030 first, gb18030 second, and gb18030 third to
|
||||
// 0x00.
|
||||
this.gb18030_first = 0x00
|
||||
this.gb18030_second = 0x00
|
||||
this.gb18030_third = 0x00
|
||||
|
||||
// 5. If code point is null, prepend buffer to stream and
|
||||
// return error.
|
||||
if (code_point === null) {
|
||||
stream.prepend(buffer)
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 6. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
|
||||
// 4. If gb18030 second is not 0x00, run these substeps:
|
||||
if (this.gb18030_second !== 0x00) {
|
||||
// 1. If byte is in the range 0x81 to 0xFE, inclusive, set
|
||||
// gb18030 third to byte and return continue.
|
||||
if (inRange(bite, 0x81, 0xFE)) {
|
||||
this.gb18030_third = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Prepend gb18030 second followed by byte to stream, set
|
||||
// gb18030 first and gb18030 second to 0x00, and return error.
|
||||
stream.prepend([this.gb18030_second, bite])
|
||||
this.gb18030_first = 0x00
|
||||
this.gb18030_second = 0x00
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 5. If gb18030 first is not 0x00, run these substeps:
|
||||
if (this.gb18030_first !== 0x00) {
|
||||
// 1. If byte is in the range 0x30 to 0x39, inclusive, set
|
||||
// gb18030 second to byte and return continue.
|
||||
if (inRange(bite, 0x30, 0x39)) {
|
||||
this.gb18030_second = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Let lead be gb18030 first, let pointer be null, and set
|
||||
// gb18030 first to 0x00.
|
||||
var lead = this.gb18030_first
|
||||
var pointer = null
|
||||
this.gb18030_first = 0x00
|
||||
|
||||
// 3. Let offset be 0x40 if byte is less than 0x7F and 0x41
|
||||
// otherwise.
|
||||
var offset = bite < 0x7F ? 0x40 : 0x41
|
||||
|
||||
// 4. If byte is in the range 0x40 to 0x7E, inclusive, or 0x80
|
||||
// to 0xFE, inclusive, set pointer to (lead − 0x81) × 190 +
|
||||
// (byte − offset).
|
||||
if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE))
|
||||
pointer = (lead - 0x81) * 190 + (bite - offset)
|
||||
|
||||
// 5. Let code point be null if pointer is null and the index
|
||||
// code point for pointer in index gb18030 otherwise.
|
||||
code_point = pointer === null ? null :
|
||||
indexCodePointFor(pointer, index('gb18030'))
|
||||
|
||||
// 6. If code point is null and byte is an ASCII byte, prepend
|
||||
// byte to stream.
|
||||
if (code_point === null && isASCIIByte(bite))
|
||||
stream.prepend(bite)
|
||||
|
||||
// 7. If code point is null, return error.
|
||||
if (code_point === null)
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 8. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
|
||||
// 6. If byte is an ASCII byte, return a code point whose value
|
||||
// is byte.
|
||||
if (isASCIIByte(bite))
|
||||
return bite
|
||||
|
||||
// 7. If byte is 0x80, return code point U+20AC.
|
||||
if (bite === 0x80)
|
||||
return 0x20AC
|
||||
|
||||
// 8. If byte is in the range 0x81 to 0xFE, inclusive, set
|
||||
// gb18030 first to byte and return continue.
|
||||
if (inRange(bite, 0x81, 0xFE)) {
|
||||
this.gb18030_first = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 9. Return error.
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
}
|
||||
|
||||
// 11.2.2 gb18030 encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class GB18030Encoder {
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
* @return Byte(s) to emit.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point, return a byte whose
|
||||
// value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 3. If code point is U+E5E5, return error with code point.
|
||||
if (code_point === 0xE5E5)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 4. If the gbk flag is set and code point is U+20AC, return
|
||||
// byte 0x80.
|
||||
if (this.gbk_flag && code_point === 0x20AC)
|
||||
return 0x80
|
||||
|
||||
// 5. Let pointer be the index pointer for code point in index
|
||||
// gb18030.
|
||||
var pointer = indexPointerFor(code_point, index('gb18030'))
|
||||
|
||||
// 6. If pointer is not null, run these substeps:
|
||||
if (pointer !== null) {
|
||||
// 1. Let lead be floor(pointer / 190) + 0x81.
|
||||
var lead = floor(pointer / 190) + 0x81
|
||||
|
||||
// 2. Let trail be pointer % 190.
|
||||
var trail = pointer % 190
|
||||
|
||||
// 3. Let offset be 0x40 if trail is less than 0x3F and 0x41 otherwise.
|
||||
var offset = trail < 0x3F ? 0x40 : 0x41
|
||||
|
||||
// 4. Return two bytes whose values are lead and trail + offset.
|
||||
return [lead, trail + offset]
|
||||
}
|
||||
|
||||
// 7. If gbk flag is set, return error with code point.
|
||||
if (this.gbk_flag)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 8. Set pointer to the index gb18030 ranges pointer for code
|
||||
// point.
|
||||
pointer = indexGB18030RangesPointerFor(code_point)
|
||||
|
||||
// 9. Let byte1 be floor(pointer / 10 / 126 / 10).
|
||||
var byte1 = floor(pointer / 10 / 126 / 10)
|
||||
|
||||
// 10. Set pointer to pointer − byte1 × 10 × 126 × 10.
|
||||
pointer = pointer - byte1 * 10 * 126 * 10
|
||||
|
||||
// 11. Let byte2 be floor(pointer / 10 / 126).
|
||||
var byte2 = floor(pointer / 10 / 126)
|
||||
|
||||
// 12. Set pointer to pointer − byte2 × 10 × 126.
|
||||
pointer = pointer - byte2 * 10 * 126
|
||||
|
||||
// 13. Let byte3 be floor(pointer / 10).
|
||||
var byte3 = floor(pointer / 10)
|
||||
|
||||
// 14. Let byte4 be pointer − byte3 × 10.
|
||||
var byte4 = pointer - byte3 * 10
|
||||
|
||||
// 15. Return four bytes whose values are byte1 + 0x81, byte2 +
|
||||
// 0x30, byte3 + 0x81, byte4 + 0x30.
|
||||
return [byte1 + 0x81,
|
||||
byte2 + 0x30,
|
||||
byte3 + 0x81,
|
||||
byte4 + 0x30]
|
||||
}
|
||||
|
||||
constructor(options = {}, gbk_flag = false) {
|
||||
// gb18030's decoder has an associated gbk flag (initially unset).
|
||||
this.gbk_flag = gbk_flag
|
||||
}
|
||||
}
|
32194
cat/tjs/lib/gbk_us.js
Normal file
32194
cat/tjs/lib/gbk_us.js
Normal file
File diff suppressed because it is too large
Load Diff
940
cat/tjs/lib/hls.js
Normal file
940
cat/tjs/lib/hls.js
Normal file
@ -0,0 +1,940 @@
|
||||
/*
|
||||
* @File : hls.js.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/2/5 16:07
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
let t = {};
|
||||
|
||||
function e(e) {
|
||||
if (t.strictMode) throw e;
|
||||
t.silent || console.error(e.message)
|
||||
}
|
||||
|
||||
function s(t, ...s) {
|
||||
for (const [i, n] of s.entries()) n || e(new Error(`${t} : Failed at [${i}]`))
|
||||
}
|
||||
|
||||
function i(...t) {
|
||||
for (const [s, [i, n]] of t.entries()) i && (n || e(new Error(`Conditional Assert : Failed at [${s}]`)))
|
||||
}
|
||||
|
||||
function n(...t) {
|
||||
for (const [s, i] of t.entries()) void 0 === i && e(new Error(`Param Check : Failed at [${s}]`))
|
||||
}
|
||||
|
||||
function a(...t) {
|
||||
for (const [s, [i, n]] of t.entries()) i && void 0 === n && e(new Error(`Conditional Param Check : Failed at [${s}]`))
|
||||
}
|
||||
|
||||
function r(t) {
|
||||
e(new Error(`Invalid Playlist : ${t}`))
|
||||
}
|
||||
|
||||
function o(t, e = 10) {
|
||||
if ("number" == typeof t) return t;
|
||||
const s = 10 === e ? Number.parseFloat(t) : Number.parseInt(t, e);
|
||||
return Number.isNaN(s) ? 0 : s
|
||||
}
|
||||
|
||||
function E(t) {
|
||||
(t.startsWith("0x") || t.startsWith("0X")) && (t = t.slice(2));
|
||||
const e = new Uint8Array(t.length / 2);
|
||||
for (let s = 0; s < t.length; s += 2) e[s / 2] = o(t.slice(s, s + 2), 16);
|
||||
return e
|
||||
}
|
||||
|
||||
function T(t, s = 0, i = t.length) {
|
||||
i <= s && e(new Error(`end must be larger than start : start=${s}, end=${i}`));
|
||||
const n = [];
|
||||
for (let e = s; e < i; e++) n.push(`0${(255 & t[e]).toString(16).toUpperCase()}`.slice(-2));
|
||||
return `0x${n.join("")}`
|
||||
}
|
||||
|
||||
function u(t, e, s = 0) {
|
||||
let i = -1;
|
||||
for (let n = 0, a = 0; n < t.length; n++) if (t[n] === e) {
|
||||
if (a++ === s) return [t.slice(0, n), t.slice(n + 1)];
|
||||
i = n
|
||||
}
|
||||
return -1 !== i ? [t.slice(0, i), t.slice(i + 1)] : [t]
|
||||
}
|
||||
|
||||
function c(t) {
|
||||
const e = [];
|
||||
let s = !1;
|
||||
for (const i of t) "-" !== i && "_" !== i ? s ? (e.push(i.toUpperCase()), s = !1) : e.push(i.toLowerCase()) : s = !0;
|
||||
return e.join("")
|
||||
}
|
||||
|
||||
function l(t) {
|
||||
return `${t.getUTCFullYear()}-${("0" + (t.getUTCMonth() + 1)).slice(-2)}-${("0" + t.getUTCDate()).slice(-2)}T${("0" + t.getUTCHours()).slice(-2)}:${("0" + t.getUTCMinutes()).slice(-2)}:${("0" + t.getUTCSeconds()).slice(-2)}.${("00" + t.getUTCMilliseconds()).slice(-3)}Z`
|
||||
}
|
||||
|
||||
function h(e = {}) {
|
||||
t = Object.assign(t, e)
|
||||
}
|
||||
|
||||
function X() {
|
||||
return Object.assign({}, t)
|
||||
}
|
||||
|
||||
function p(t, e) {
|
||||
e = Math.trunc(e) || 0;
|
||||
const s = t.length >>> 0;
|
||||
if (e < 0 && (e = s + e), !(e < 0 || e >= s)) return t[e]
|
||||
}
|
||||
|
||||
class I {
|
||||
constructor({
|
||||
type: t,
|
||||
uri: e,
|
||||
groupId: s,
|
||||
language: a,
|
||||
assocLanguage: r,
|
||||
name: o,
|
||||
isDefault: E,
|
||||
autoselect: T,
|
||||
forced: u,
|
||||
instreamId: c,
|
||||
characteristics: l,
|
||||
channels: h
|
||||
}) {
|
||||
n(t, s, o), i(["SUBTITLES" === t, e], ["CLOSED-CAPTIONS" === t, c], ["CLOSED-CAPTIONS" === t, !e], [u, "SUBTITLES" === t]), this.type = t, this.uri = e, this.groupId = s, this.language = a, this.assocLanguage = r, this.name = o, this.isDefault = E, this.autoselect = T, this.forced = u, this.instreamId = c, this.characteristics = l, this.channels = h
|
||||
}
|
||||
}
|
||||
|
||||
class N {
|
||||
constructor({
|
||||
uri: t,
|
||||
isIFrameOnly: e = !1,
|
||||
bandwidth: s,
|
||||
averageBandwidth: i,
|
||||
score: a,
|
||||
codecs: r,
|
||||
resolution: o,
|
||||
frameRate: E,
|
||||
hdcpLevel: T,
|
||||
allowedCpc: u,
|
||||
videoRange: c,
|
||||
stableVariantId: l,
|
||||
programId: h,
|
||||
audio: X = [],
|
||||
video: p = [],
|
||||
subtitles: I = [],
|
||||
closedCaptions: N = [],
|
||||
currentRenditions: d = {audio: 0, video: 0, subtitles: 0, closedCaptions: 0}
|
||||
}) {
|
||||
n(t, s), this.uri = t, this.isIFrameOnly = e, this.bandwidth = s, this.averageBandwidth = i, this.score = a, this.codecs = r, this.resolution = o, this.frameRate = E, this.hdcpLevel = T, this.allowedCpc = u, this.videoRange = c, this.stableVariantId = l, this.programId = h, this.audio = X, this.video = p, this.subtitles = I, this.closedCaptions = N, this.currentRenditions = d
|
||||
}
|
||||
}
|
||||
|
||||
class d {
|
||||
constructor({id: t, value: e, uri: i, language: a}) {
|
||||
n(t, e || i), s("SessionData cannot have both value and uri, shoud be either.", !(e && i)), this.id = t, this.value = e, this.uri = i, this.language = a
|
||||
}
|
||||
}
|
||||
|
||||
class A {
|
||||
constructor({method: t, uri: e, iv: s, format: r, formatVersion: o}) {
|
||||
n(t), a(["NONE" !== t, e]), i(["NONE" === t, !(e || s || r || o)]), this.method = t, this.uri = e, this.iv = s, this.format = r, this.formatVersion = o
|
||||
}
|
||||
}
|
||||
|
||||
class f {
|
||||
constructor({hint: t = !1, uri: e, mimeType: s, byterange: i}) {
|
||||
n(e), this.hint = t, this.uri = e, this.mimeType = s, this.byterange = i
|
||||
}
|
||||
}
|
||||
|
||||
class S {
|
||||
constructor({
|
||||
id: t,
|
||||
classId: e,
|
||||
start: s,
|
||||
end: r,
|
||||
duration: o,
|
||||
plannedDuration: E,
|
||||
endOnNext: T,
|
||||
attributes: u = {}
|
||||
}) {
|
||||
n(t), a([!0 === T, e]), i([r, s], [r, s <= r], [o, o >= 0], [E, E >= 0]), this.id = t, this.classId = e, this.start = s, this.end = r, this.duration = o, this.plannedDuration = E, this.endOnNext = T, this.attributes = u
|
||||
}
|
||||
}
|
||||
|
||||
class R {
|
||||
constructor({type: t, duration: e, tagName: s, value: i}) {
|
||||
n(t), a(["OUT" === t, e]), a(["RAW" === t, s]), this.type = t, this.duration = e, this.tagName = s, this.value = i
|
||||
}
|
||||
}
|
||||
|
||||
class m {
|
||||
constructor(t) {
|
||||
n(t), this.type = t
|
||||
}
|
||||
}
|
||||
|
||||
class g extends m {
|
||||
constructor({isMasterPlaylist: t, uri: e, version: s, independentSegments: i = !1, start: a, source: r}) {
|
||||
super("playlist"), n(t), this.isMasterPlaylist = t, this.uri = e, this.version = s, this.independentSegments = i, this.start = a, this.source = r
|
||||
}
|
||||
}
|
||||
|
||||
class O extends g {
|
||||
constructor(t = {}) {
|
||||
super(Object.assign(Object.assign({}, t), {isMasterPlaylist: !0}));
|
||||
const {variants: e = [], currentVariant: s, sessionDataList: i = [], sessionKeyList: n = []} = t;
|
||||
this.variants = e, this.currentVariant = s, this.sessionDataList = i, this.sessionKeyList = n
|
||||
}
|
||||
}
|
||||
|
||||
class D extends g {
|
||||
constructor(t = {}) {
|
||||
super(Object.assign(Object.assign({}, t), {isMasterPlaylist: !1}));
|
||||
const {
|
||||
targetDuration: e,
|
||||
mediaSequenceBase: s = 0,
|
||||
discontinuitySequenceBase: i = 0,
|
||||
endlist: n = !1,
|
||||
playlistType: a,
|
||||
isIFrame: r,
|
||||
segments: o = [],
|
||||
prefetchSegments: E = [],
|
||||
lowLatencyCompatibility: T,
|
||||
partTargetDuration: u,
|
||||
renditionReports: c = [],
|
||||
skip: l = 0,
|
||||
hash: h
|
||||
} = t;
|
||||
this.targetDuration = e, this.mediaSequenceBase = s, this.discontinuitySequenceBase = i, this.endlist = n, this.playlistType = a, this.isIFrame = r, this.segments = o, this.prefetchSegments = E, this.lowLatencyCompatibility = T, this.partTargetDuration = u, this.renditionReports = c, this.skip = l, this.hash = h
|
||||
}
|
||||
}
|
||||
|
||||
class P extends m {
|
||||
constructor({
|
||||
uri: t,
|
||||
mimeType: e,
|
||||
data: s,
|
||||
duration: i,
|
||||
title: n,
|
||||
byterange: a,
|
||||
discontinuity: r,
|
||||
mediaSequenceNumber: o = 0,
|
||||
discontinuitySequence: E = 0,
|
||||
key: T,
|
||||
map: u,
|
||||
programDateTime: c,
|
||||
dateRange: l,
|
||||
markers: h = [],
|
||||
parts: X = []
|
||||
}) {
|
||||
super("segment"), this.uri = t, this.mimeType = e, this.data = s, this.duration = i, this.title = n, this.byterange = a, this.discontinuity = r, this.mediaSequenceNumber = o, this.discontinuitySequence = E, this.key = T, this.map = u, this.programDateTime = c, this.dateRange = l, this.markers = h, this.parts = X
|
||||
}
|
||||
}
|
||||
|
||||
class y extends m {
|
||||
constructor({hint: t = !1, uri: e, duration: s, independent: i, byterange: a, gap: r}) {
|
||||
super("part"), n(e), this.hint = t, this.uri = e, this.duration = s, this.independent = i, this.duration = s, this.byterange = a, this.gap = r
|
||||
}
|
||||
}
|
||||
|
||||
class C extends m {
|
||||
constructor({uri: t, discontinuity: e, mediaSequenceNumber: s = 0, discontinuitySequence: i = 0, key: a}) {
|
||||
super("prefetch"), n(t), this.uri = t, this.discontinuity = e, this.mediaSequenceNumber = s, this.discontinuitySequence = i, this.key = a
|
||||
}
|
||||
}
|
||||
|
||||
class U {
|
||||
constructor({uri: t, lastMSN: e, lastPart: s}) {
|
||||
n(t), this.uri = t, this.lastMSN = e, this.lastPart = s
|
||||
}
|
||||
}
|
||||
|
||||
var M = Object.freeze({
|
||||
__proto__: null,
|
||||
Rendition: I,
|
||||
Variant: N,
|
||||
SessionData: d,
|
||||
Key: A,
|
||||
MediaInitializationSection: f,
|
||||
DateRange: S,
|
||||
SpliceInfo: R,
|
||||
Playlist: g,
|
||||
MasterPlaylist: O,
|
||||
MediaPlaylist: D,
|
||||
Segment: P,
|
||||
PartialSegment: y,
|
||||
PrefetchSegment: C,
|
||||
RenditionReport: U
|
||||
});
|
||||
|
||||
function b(t) {
|
||||
return function (t, e = " ") {
|
||||
return t ? (t = t.trim(), " " === e || (t.startsWith(e) && (t = t.slice(1)), t.endsWith(e) && (t = t.slice(0, -1))), t) : t
|
||||
}(t, '"')
|
||||
}
|
||||
|
||||
function L(t) {
|
||||
const e = u(t, ",");
|
||||
return {duration: o(e[0]), title: decodeURIComponent(escape(e[1]))}
|
||||
}
|
||||
|
||||
function v(t) {
|
||||
const e = u(t, "@");
|
||||
return {length: o(e[0]), offset: e[1] ? o(e[1]) : -1}
|
||||
}
|
||||
|
||||
function $(t) {
|
||||
const e = u(t, "x");
|
||||
return {width: o(e[0]), height: o(e[1])}
|
||||
}
|
||||
|
||||
function Y(t) {
|
||||
const e = "ALLOWED-CPC: Each entry must consit of KEYFORMAT and Content Protection Configuration", s = t.split(",");
|
||||
0 === s.length && r(e);
|
||||
const i = [];
|
||||
for (const t of s) {
|
||||
const [s, n] = u(t, ":");
|
||||
s && n ? i.push({format: s, cpcList: n.split("/")}) : r(e)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
function F(t) {
|
||||
const e = E(t);
|
||||
return 16 !== e.length && r("IV must be a 128-bit unsigned integer"), e
|
||||
}
|
||||
|
||||
function G(t, e) {
|
||||
e.IV && t.compatibleVersion < 2 && (t.compatibleVersion = 2), (e.KEYFORMAT || e.KEYFORMATVERSIONS) && t.compatibleVersion < 5 && (t.compatibleVersion = 5)
|
||||
}
|
||||
|
||||
function V(t) {
|
||||
const e = {};
|
||||
for (const i of function (t) {
|
||||
const e = [];
|
||||
let s = !0, i = 0;
|
||||
const n = [];
|
||||
for (let a = 0; a < t.length; a++) {
|
||||
const r = t[a];
|
||||
s && "," === r ? (e.push(t.slice(i, a).trim()), i = a + 1) : '"' !== r && "'" !== r || (s ? (n.push(r), s = !1) : r === p(n, -1) ? (n.pop(), s = !0) : n.push(r))
|
||||
}
|
||||
return e.push(t.slice(i).trim()), e
|
||||
}(t)) {
|
||||
const [t, n] = u(i, "="), a = b(n);
|
||||
switch (t) {
|
||||
case"URI":
|
||||
e[t] = a;
|
||||
break;
|
||||
case"START-DATE":
|
||||
case"END-DATE":
|
||||
e[t] = new Date(a);
|
||||
break;
|
||||
case"IV":
|
||||
e[t] = F(a);
|
||||
break;
|
||||
case"BYTERANGE":
|
||||
e[t] = v(a);
|
||||
break;
|
||||
case"RESOLUTION":
|
||||
e[t] = $(a);
|
||||
break;
|
||||
case"ALLOWED-CPC":
|
||||
e[t] = Y(a);
|
||||
break;
|
||||
case"END-ON-NEXT":
|
||||
case"DEFAULT":
|
||||
case"AUTOSELECT":
|
||||
case"FORCED":
|
||||
case"PRECISE":
|
||||
case"CAN-BLOCK-RELOAD":
|
||||
case"INDEPENDENT":
|
||||
case"GAP":
|
||||
e[t] = "YES" === a;
|
||||
break;
|
||||
case"DURATION":
|
||||
case"PLANNED-DURATION":
|
||||
case"BANDWIDTH":
|
||||
case"AVERAGE-BANDWIDTH":
|
||||
case"FRAME-RATE":
|
||||
case"TIME-OFFSET":
|
||||
case"CAN-SKIP-UNTIL":
|
||||
case"HOLD-BACK":
|
||||
case"PART-HOLD-BACK":
|
||||
case"PART-TARGET":
|
||||
case"BYTERANGE-START":
|
||||
case"BYTERANGE-LENGTH":
|
||||
case"LAST-MSN":
|
||||
case"LAST-PART":
|
||||
case"SKIPPED-SEGMENTS":
|
||||
case"SCORE":
|
||||
case"PROGRAM-ID":
|
||||
e[t] = o(a);
|
||||
break;
|
||||
default:
|
||||
t.startsWith("SCTE35-") ? e[t] = E(a) : t.startsWith("X-") ? e[t] = (s = n).startsWith('"') ? b(s) : s.startsWith("0x") || s.startsWith("0X") ? E(s) : o(s) : ("VIDEO-RANGE" === t && "SDR" !== a && "HLG" !== a && "PQ" !== a && r(`VIDEO-RANGE: unknown value "${a}"`), e[t] = a)
|
||||
}
|
||||
}
|
||||
var s;
|
||||
return e
|
||||
}
|
||||
|
||||
function w() {
|
||||
r("The file contains both media and master playlist tags.")
|
||||
}
|
||||
|
||||
function B(t, e, s) {
|
||||
const i = function ({attributes: t}) {
|
||||
return new I({
|
||||
type: t.TYPE,
|
||||
uri: t.URI,
|
||||
groupId: t["GROUP-ID"],
|
||||
language: t.LANGUAGE,
|
||||
assocLanguage: t["ASSOC-LANGUAGE"],
|
||||
name: t.NAME,
|
||||
isDefault: t.DEFAULT,
|
||||
autoselect: t.AUTOSELECT,
|
||||
forced: t.FORCED,
|
||||
instreamId: t["INSTREAM-ID"],
|
||||
characteristics: t.CHARACTERISTICS,
|
||||
channels: t.CHANNELS
|
||||
})
|
||||
}(e), n = t[c(s)], a = function (t, e) {
|
||||
let s = !1;
|
||||
for (const i of t) {
|
||||
if (i.name === e.name) return "All EXT-X-MEDIA tags in the same Group MUST have different NAME attributes.";
|
||||
i.isDefault && (s = !0)
|
||||
}
|
||||
return s && e.isDefault ? "EXT-X-MEDIA A Group MUST NOT have more than one member with a DEFAULT attribute of YES." : ""
|
||||
}(n, i);
|
||||
a && r(a), n.push(i), i.isDefault && (t.currentRenditions[c(s)] = n.length - 1)
|
||||
}
|
||||
|
||||
function H(t, e, s, i, n) {
|
||||
const a = new N({
|
||||
uri: s,
|
||||
bandwidth: e.BANDWIDTH,
|
||||
averageBandwidth: e["AVERAGE-BANDWIDTH"],
|
||||
score: e.SCORE,
|
||||
codecs: e.CODECS,
|
||||
resolution: e.RESOLUTION,
|
||||
frameRate: e["FRAME-RATE"],
|
||||
hdcpLevel: e["HDCP-LEVEL"],
|
||||
allowedCpc: e["ALLOWED-CPC"],
|
||||
videoRange: e["VIDEO-RANGE"],
|
||||
stableVariantId: e["STABLE-VARIANT-ID"],
|
||||
programId: e["PROGRAM-ID"]
|
||||
});
|
||||
for (const s of t) if ("EXT-X-MEDIA" === s.name) {
|
||||
const t = s.attributes, i = t.TYPE;
|
||||
if (i && t["GROUP-ID"] || r("EXT-X-MEDIA TYPE attribute is REQUIRED."), e[i] === t["GROUP-ID"] && (B(a, s, i), "CLOSED-CAPTIONS" === i)) for (const {instreamId: t} of a.closedCaptions) if (t && t.startsWith("SERVICE") && n.compatibleVersion < 7) {
|
||||
n.compatibleVersion = 7;
|
||||
break
|
||||
}
|
||||
}
|
||||
return function (t, e, s) {
|
||||
for (const i of ["AUDIO", "VIDEO", "SUBTITLES", "CLOSED-CAPTIONS"]) "CLOSED-CAPTIONS" === i && "NONE" === t[i] ? (s.isClosedCaptionsNone = !0, e.closedCaptions = []) : t[i] && !e[c(i)].some((e => e.groupId === t[i])) && r(`${i} attribute MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag whose TYPE attribute is ${i}.`)
|
||||
}(e, a, n), a.isIFrameOnly = i, a
|
||||
}
|
||||
|
||||
function K(t, e) {
|
||||
if (t.method !== e.method) return !1;
|
||||
if (t.uri !== e.uri) return !1;
|
||||
if (t.iv) {
|
||||
if (!e.iv) return !1;
|
||||
if (t.iv.length !== e.iv.length) return !1;
|
||||
for (let s = 0; s < t.iv.length; s++) if (t.iv[s] !== e.iv[s]) return !1
|
||||
} else if (e.iv) return !1;
|
||||
return t.format === e.format && t.formatVersion === e.formatVersion
|
||||
}
|
||||
|
||||
function k(t, e, s, i, n, a, o) {
|
||||
const E = new P({uri: e, mediaSequenceNumber: n, discontinuitySequence: a});
|
||||
let T = !1, u = !1;
|
||||
for (let e = s; e <= i; e++) {
|
||||
const {name: s, value: i, attributes: n} = t[e];
|
||||
if ("EXTINF" === s) !Number.isInteger(i.duration) && o.compatibleVersion < 3 && (o.compatibleVersion = 3), Math.round(i.duration) > o.targetDuration && r("EXTINF duration, when rounded to the nearest integer, MUST be less than or equal to the target duration"), E.duration = i.duration, E.title = i.title; else if ("EXT-X-BYTERANGE" === s) o.compatibleVersion < 4 && (o.compatibleVersion = 4), E.byterange = i; else if ("EXT-X-DISCONTINUITY" === s) E.parts.length > 0 && r("EXT-X-DISCONTINUITY must appear before the first EXT-X-PART tag of the Parent Segment."), E.discontinuity = !0; else if ("EXT-X-KEY" === s) E.parts.length > 0 && r("EXT-X-KEY must appear before the first EXT-X-PART tag of the Parent Segment."), G(o, n), E.key = new A({
|
||||
method: n.METHOD,
|
||||
uri: n.URI,
|
||||
iv: n.IV,
|
||||
format: n.KEYFORMAT,
|
||||
formatVersion: n.KEYFORMATVERSIONS
|
||||
}); else if ("EXT-X-MAP" === s) E.parts.length > 0 && r("EXT-X-MAP must appear before the first EXT-X-PART tag of the Parent Segment."), o.compatibleVersion < 5 && (o.compatibleVersion = 5), o.hasMap = !0, E.map = new f({
|
||||
uri: n.URI,
|
||||
byterange: n.BYTERANGE
|
||||
}); else if ("EXT-X-PROGRAM-DATE-TIME" === s) E.programDateTime = i; else if ("EXT-X-DATERANGE" === s) {
|
||||
const t = {};
|
||||
for (const e of Object.keys(n)) (e.startsWith("SCTE35-") || e.startsWith("X-")) && (t[e] = n[e]);
|
||||
E.dateRange = new S({
|
||||
id: n.ID,
|
||||
classId: n.CLASS,
|
||||
start: n["START-DATE"],
|
||||
end: n["END-DATE"],
|
||||
duration: n.DURATION,
|
||||
plannedDuration: n["PLANNED-DURATION"],
|
||||
endOnNext: n["END-ON-NEXT"],
|
||||
attributes: t
|
||||
})
|
||||
} else if ("EXT-X-CUE-OUT" === s) E.markers.push(new R({
|
||||
type: "OUT",
|
||||
duration: n && n.DURATION || i
|
||||
})); else if ("EXT-X-CUE-IN" === s) E.markers.push(new R({type: "IN"})); else if ("EXT-X-CUE-OUT-CONT" === s || "EXT-X-CUE" === s || "EXT-OATCLS-SCTE35" === s || "EXT-X-ASSET" === s || "EXT-X-SCTE35" === s) E.markers.push(new R({
|
||||
type: "RAW",
|
||||
tagName: s,
|
||||
value: i
|
||||
})); else if ("EXT-X-PRELOAD-HINT" !== s || n.TYPE) if ("EXT-X-PRELOAD-HINT" === s && "PART" === n.TYPE && u) r("Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist."); else if ("EXT-X-PART" !== s && "EXT-X-PRELOAD-HINT" !== s || n.URI) {
|
||||
if ("EXT-X-PRELOAD-HINT" === s && "MAP" === n.TYPE) T && r("Servers should not add more than one EXT-X-PRELOAD-HINT tag with the same TYPE attribute to a Playlist."), T = !0, o.hasMap = !0, E.map = new f({
|
||||
hint: !0,
|
||||
uri: n.URI,
|
||||
byterange: {length: n["BYTERANGE-LENGTH"], offset: n["BYTERANGE-START"] || 0}
|
||||
}); else if ("EXT-X-PART" === s || "EXT-X-PRELOAD-HINT" === s && "PART" === n.TYPE) {
|
||||
"EXT-X-PART" !== s || n.DURATION || r("EXT-X-PART: DURATION attribute is mandatory"), "EXT-X-PRELOAD-HINT" === s && (u = !0);
|
||||
const t = new y({
|
||||
hint: "EXT-X-PRELOAD-HINT" === s,
|
||||
uri: n.URI,
|
||||
byterange: "EXT-X-PART" === s ? n.BYTERANGE : {
|
||||
length: n["BYTERANGE-LENGTH"],
|
||||
offset: n["BYTERANGE-START"] || 0
|
||||
},
|
||||
duration: n.DURATION,
|
||||
independent: n.INDEPENDENT,
|
||||
gap: n.GAP
|
||||
});
|
||||
E.parts.push(t)
|
||||
}
|
||||
} else r("EXT-X-PART / EXT-X-PRELOAD-HINT: URI attribute is mandatory"); else r("EXT-X-PRELOAD-HINT: TYPE attribute is mandatory")
|
||||
}
|
||||
return E
|
||||
}
|
||||
|
||||
function W(t, e, s, i, n, a, o) {
|
||||
const E = new C({uri: e, mediaSequenceNumber: n, discontinuitySequence: a});
|
||||
for (let e = s; e <= i; e++) {
|
||||
const {name: s, attributes: i} = t[e];
|
||||
"EXTINF" === s ? r("A prefetch segment must not be advertised with an EXTINF tag.") : "EXT-X-DISCONTINUITY" === s ? r("A prefetch segment must not be advertised with an EXT-X-DISCONTINUITY tag.") : "EXT-X-PREFETCH-DISCONTINUITY" === s ? E.discontinuity = !0 : "EXT-X-KEY" === s ? (G(o, i), E.key = new A({
|
||||
method: i.METHOD,
|
||||
uri: i.URI,
|
||||
iv: i.IV,
|
||||
format: i.KEYFORMAT,
|
||||
formatVersion: i.KEYFORMATVERSIONS
|
||||
})) : "EXT-X-MAP" === s && r("Prefetch segments must not be advertised with an EXT-X-MAP tag.")
|
||||
}
|
||||
return E
|
||||
}
|
||||
|
||||
function q(t, e) {
|
||||
var s;
|
||||
const i = new D;
|
||||
let n = -1, a = 0, o = !1, E = !1, T = 0, u = null, c = null, l = !1;
|
||||
for (const [s, h] of t.entries()) {
|
||||
const {name: X, value: p, attributes: I, category: N} = h;
|
||||
if ("Segment" !== N) {
|
||||
if ("EXT-X-VERSION" === X) void 0 === i.version ? i.version = p : r("A Playlist file MUST NOT contain more than one EXT-X-VERSION tag."); else if ("EXT-X-TARGETDURATION" === X) i.targetDuration = e.targetDuration = p; else if ("EXT-X-MEDIA-SEQUENCE" === X) i.segments.length > 0 && r("The EXT-X-MEDIA-SEQUENCE tag MUST appear before the first Media Segment in the Playlist."), i.mediaSequenceBase = a = p; else if ("EXT-X-DISCONTINUITY-SEQUENCE" === X) i.segments.length > 0 && r("The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before the first Media Segment in the Playlist."), o && r("The EXT-X-DISCONTINUITY-SEQUENCE tag MUST appear before any EXT-X-DISCONTINUITY tag."), i.discontinuitySequenceBase = T = p; else if ("EXT-X-ENDLIST" === X) i.endlist = !0; else if ("EXT-X-PLAYLIST-TYPE" === X) i.playlistType = p; else if ("EXT-X-I-FRAMES-ONLY" === X) e.compatibleVersion < 4 && (e.compatibleVersion = 4), i.isIFrame = !0; else if ("EXT-X-INDEPENDENT-SEGMENTS" === X) i.independentSegments && r("EXT-X-INDEPENDENT-SEGMENTS tag MUST NOT appear more than once in a Playlist"), i.independentSegments = !0; else if ("EXT-X-START" === X) i.start && r("EXT-X-START tag MUST NOT appear more than once in a Playlist"), "number" != typeof I["TIME-OFFSET"] && r("EXT-X-START: TIME-OFFSET attribute is REQUIRED"), i.start = {
|
||||
offset: I["TIME-OFFSET"],
|
||||
precise: I.PRECISE || !1
|
||||
}; else if ("EXT-X-SERVER-CONTROL" === X) I["CAN-BLOCK-RELOAD"] || r("EXT-X-SERVER-CONTROL: CAN-BLOCK-RELOAD=YES is mandatory for Low-Latency HLS"), i.lowLatencyCompatibility = {
|
||||
canBlockReload: I["CAN-BLOCK-RELOAD"],
|
||||
canSkipUntil: I["CAN-SKIP-UNTIL"],
|
||||
holdBack: I["HOLD-BACK"],
|
||||
partHoldBack: I["PART-HOLD-BACK"]
|
||||
}; else if ("EXT-X-PART-INF" === X) I["PART-TARGET"] || r("EXT-X-PART-INF: PART-TARGET attribute is mandatory"), i.partTargetDuration = I["PART-TARGET"]; else if ("EXT-X-RENDITION-REPORT" === X) I.URI || r("EXT-X-RENDITION-REPORT: URI attribute is mandatory"), 0 === I.URI.search(/^[a-z]+:/) && r("EXT-X-RENDITION-REPORT: URI must be relative to the playlist uri"), i.renditionReports.push(new U({
|
||||
uri: I.URI,
|
||||
lastMSN: I["LAST-MSN"],
|
||||
lastPart: I["LAST-PART"]
|
||||
})); else if ("EXT-X-SKIP" === X) I["SKIPPED-SEGMENTS"] || r("EXT-X-SKIP: SKIPPED-SEGMENTS attribute is mandatory"), e.compatibleVersion < 9 && (e.compatibleVersion = 9), i.skip = I["SKIPPED-SEGMENTS"], a += i.skip; else if ("EXT-X-PREFETCH" === X) {
|
||||
const r = W(t, p, -1 === n ? s : n, s - 1, a++, T, e);
|
||||
r && (r.discontinuity && (r.discontinuitySequence++, T = r.discontinuitySequence), r.key ? u = r.key : r.key = u, i.prefetchSegments.push(r)), E = !0, n = -1
|
||||
} else if ("string" == typeof h) {
|
||||
-1 === n && r("A URI line is not preceded by any segment tags"), i.targetDuration || r("The EXT-X-TARGETDURATION tag is REQUIRED"), E && r("These segments must appear after all complete segments.");
|
||||
const o = k(t, h, n, s - 1, a++, T, e);
|
||||
o && ([T, u, c] = x(i, o, T, u, c), !l && o.parts.length > 0 && (l = !0)), n = -1
|
||||
}
|
||||
} else -1 === n && (n = s), "EXT-X-DISCONTINUITY" === X && (o = !0)
|
||||
}
|
||||
if (-1 !== n) {
|
||||
const o = k(t, "", n, t.length - 1, a++, T, e);
|
||||
if (o) {
|
||||
const {parts: t} = o;
|
||||
t.length > 0 && !i.endlist && !(null === (s = p(t, -1)) || void 0 === s ? void 0 : s.hint) && r("If the Playlist contains EXT-X-PART tags and does not contain an EXT-X-ENDLIST tag, the Playlist must contain an EXT-X-PRELOAD-HINT tag with a TYPE=PART attribute"), x(i, o, u, c), !l && o.parts.length > 0 && (l = !0)
|
||||
}
|
||||
}
|
||||
return function (t) {
|
||||
const e = new Map, s = new Map;
|
||||
let i = !1, n = !1;
|
||||
for (let a = t.length - 1; a >= 0; a--) {
|
||||
const {programDateTime: o, dateRange: E} = t[a];
|
||||
if (o && (n = !0), E && E.start) {
|
||||
i = !0, E.endOnNext && (E.end || E.duration) && r("An EXT-X-DATERANGE tag with an END-ON-NEXT=YES attribute MUST NOT contain DURATION or END-DATE attributes.");
|
||||
const t = E.start.getTime(), n = E.duration || 0;
|
||||
E.end && E.duration && t + 1e3 * n !== E.end.getTime() && r("END-DATE MUST be equal to the value of the START-DATE attribute plus the value of the DURATION"), E.endOnNext && (E.end = e.get(E.classId)), e.set(E.classId, E.start);
|
||||
const a = E.end ? E.end.getTime() : E.start.getTime() + 1e3 * (E.duration || 0), o = s.get(E.classId);
|
||||
if (o) {
|
||||
for (const e of o) (e.start <= t && e.end > t || e.start >= t && e.start < a) && r("DATERANGE tags with the same CLASS should not overlap");
|
||||
o.push({start: t, end: a})
|
||||
} else E.classId && s.set(E.classId, [{start: t, end: a}])
|
||||
}
|
||||
}
|
||||
i && !n && r("If a Playlist contains an EXT-X-DATERANGE tag, it MUST also contain at least one EXT-X-PROGRAM-DATE-TIME tag.")
|
||||
}(i.segments), i.lowLatencyCompatibility && function ({
|
||||
lowLatencyCompatibility: t,
|
||||
targetDuration: e,
|
||||
partTargetDuration: s,
|
||||
segments: i,
|
||||
renditionReports: n
|
||||
}, a) {
|
||||
const {canSkipUntil: o, holdBack: E, partHoldBack: T} = t;
|
||||
o < 6 * e && r("The Skip Boundary must be at least six times the EXT-X-TARGETDURATION.");
|
||||
E < 3 * e && r("HOLD-BACK must be at least three times the EXT-X-TARGETDURATION.");
|
||||
if (a) {
|
||||
void 0 === s && r("EXT-X-PART-INF is required if a Playlist contains one or more EXT-X-PART tags"), void 0 === T && r("EXT-X-PART: PART-HOLD-BACK attribute is mandatory"), T < s && r("PART-HOLD-BACK must be at least PART-TARGET");
|
||||
for (const [t, {parts: e}] of i.entries()) {
|
||||
e.length > 0 && t < i.length - 3 && r("Remove EXT-X-PART tags from the Playlist after they are greater than three target durations from the end of the Playlist.");
|
||||
for (const [t, {duration: i}] of e.entries()) void 0 !== i && (i > s && r("PART-TARGET is the maximum duration of any Partial Segment"), t < e.length - 1 && i < .85 * s && r("All Partial Segments except the last part of a segment must have a duration of at least 85% of PART-TARGET"))
|
||||
}
|
||||
}
|
||||
for (const t of n) {
|
||||
const e = i.at(-1);
|
||||
null !== t.lastMSN && void 0 !== t.lastMSN || (t.lastMSN = e.mediaSequenceNumber), (null === t.lastPart || void 0 === t.lastPart) && e.parts.length > 0 && (t.lastPart = e.parts.length - 1)
|
||||
}
|
||||
}(i, l), i
|
||||
}
|
||||
|
||||
function x(t, e, s, i, n) {
|
||||
const {discontinuity: a, key: o, map: E, byterange: T, uri: u} = e;
|
||||
if (a && (e.discontinuitySequence = s + 1), o || (e.key = i), E || (e.map = n), T && -1 === T.offset) {
|
||||
const {segments: e} = t;
|
||||
if (e.length > 0) {
|
||||
const t = p(e, -1);
|
||||
t.byterange && t.uri === u ? T.offset = t.byterange.offset + t.byterange.length : r("If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST be a sub-range of the same media resource")
|
||||
} else r("If offset of EXT-X-BYTERANGE is not present, a previous Media Segment MUST appear in the Playlist file")
|
||||
}
|
||||
return t.segments.push(e), [e.discontinuitySequence, e.key, e.map]
|
||||
}
|
||||
|
||||
function j(t, e) {
|
||||
const [s, i] = function (t) {
|
||||
const e = t.indexOf(":");
|
||||
return -1 === e ? [t.slice(1).trim(), null] : [t.slice(1, e).trim(), t.slice(e + 1).trim()]
|
||||
}(t), n = function (t) {
|
||||
switch (t) {
|
||||
case"EXTM3U":
|
||||
case"EXT-X-VERSION":
|
||||
return "Basic";
|
||||
case"EXTINF":
|
||||
case"EXT-X-BYTERANGE":
|
||||
case"EXT-X-DISCONTINUITY":
|
||||
case"EXT-X-PREFETCH-DISCONTINUITY":
|
||||
case"EXT-X-KEY":
|
||||
case"EXT-X-MAP":
|
||||
case"EXT-X-PROGRAM-DATE-TIME":
|
||||
case"EXT-X-DATERANGE":
|
||||
case"EXT-X-CUE-OUT":
|
||||
case"EXT-X-CUE-IN":
|
||||
case"EXT-X-CUE-OUT-CONT":
|
||||
case"EXT-X-CUE":
|
||||
case"EXT-OATCLS-SCTE35":
|
||||
case"EXT-X-ASSET":
|
||||
case"EXT-X-SCTE35":
|
||||
case"EXT-X-PART":
|
||||
case"EXT-X-PRELOAD-HINT":
|
||||
return "Segment";
|
||||
case"EXT-X-TARGETDURATION":
|
||||
case"EXT-X-MEDIA-SEQUENCE":
|
||||
case"EXT-X-DISCONTINUITY-SEQUENCE":
|
||||
case"EXT-X-ENDLIST":
|
||||
case"EXT-X-PLAYLIST-TYPE":
|
||||
case"EXT-X-I-FRAMES-ONLY":
|
||||
case"EXT-X-SERVER-CONTROL":
|
||||
case"EXT-X-PART-INF":
|
||||
case"EXT-X-PREFETCH":
|
||||
case"EXT-X-RENDITION-REPORT":
|
||||
case"EXT-X-SKIP":
|
||||
return "MediaPlaylist";
|
||||
case"EXT-X-MEDIA":
|
||||
case"EXT-X-STREAM-INF":
|
||||
case"EXT-X-I-FRAME-STREAM-INF":
|
||||
case"EXT-X-SESSION-DATA":
|
||||
case"EXT-X-SESSION-KEY":
|
||||
return "MasterPlaylist";
|
||||
case"EXT-X-INDEPENDENT-SEGMENTS":
|
||||
case"EXT-X-START":
|
||||
return "MediaorMasterPlaylist";
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}(s);
|
||||
if (function (t, e) {
|
||||
if ("Segment" === t || "MediaPlaylist" === t) return void 0 === e.isMasterPlaylist ? void (e.isMasterPlaylist = !1) : void (e.isMasterPlaylist && w());
|
||||
if ("MasterPlaylist" === t) {
|
||||
if (void 0 === e.isMasterPlaylist) return void (e.isMasterPlaylist = !0);
|
||||
!1 === e.isMasterPlaylist && w()
|
||||
}
|
||||
}(n, e), "Unknown" === n) return null;
|
||||
"MediaPlaylist" === n && "EXT-X-RENDITION-REPORT" !== s && "EXT-X-PREFETCH" !== s && (e.hash[s] && r("There MUST NOT be more than one Media Playlist tag of each type in any Media Playlist"), e.hash[s] = !0);
|
||||
const [a, E] = function (t, e) {
|
||||
switch (t) {
|
||||
case"EXTM3U":
|
||||
case"EXT-X-DISCONTINUITY":
|
||||
case"EXT-X-ENDLIST":
|
||||
case"EXT-X-I-FRAMES-ONLY":
|
||||
case"EXT-X-INDEPENDENT-SEGMENTS":
|
||||
case"EXT-X-CUE-IN":
|
||||
return [null, null];
|
||||
case"EXT-X-VERSION":
|
||||
case"EXT-X-TARGETDURATION":
|
||||
case"EXT-X-MEDIA-SEQUENCE":
|
||||
case"EXT-X-DISCONTINUITY-SEQUENCE":
|
||||
return [o(e), null];
|
||||
case"EXT-X-CUE-OUT":
|
||||
return Number.isNaN(Number(e)) ? [null, V(e)] : [o(e), null];
|
||||
case"EXT-X-KEY":
|
||||
case"EXT-X-MAP":
|
||||
case"EXT-X-DATERANGE":
|
||||
case"EXT-X-MEDIA":
|
||||
case"EXT-X-STREAM-INF":
|
||||
case"EXT-X-I-FRAME-STREAM-INF":
|
||||
case"EXT-X-SESSION-DATA":
|
||||
case"EXT-X-SESSION-KEY":
|
||||
case"EXT-X-START":
|
||||
case"EXT-X-SERVER-CONTROL":
|
||||
case"EXT-X-PART-INF":
|
||||
case"EXT-X-PART":
|
||||
case"EXT-X-PRELOAD-HINT":
|
||||
case"EXT-X-RENDITION-REPORT":
|
||||
case"EXT-X-SKIP":
|
||||
return [null, V(e)];
|
||||
case"EXTINF":
|
||||
return [L(e), null];
|
||||
case"EXT-X-BYTERANGE":
|
||||
return [v(e), null];
|
||||
case"EXT-X-PROGRAM-DATE-TIME":
|
||||
return [new Date(e), null];
|
||||
default:
|
||||
return [e, null]
|
||||
}
|
||||
}(s, i);
|
||||
return {name: s, category: n, value: a, attributes: E}
|
||||
}
|
||||
|
||||
function Q(t, e) {
|
||||
let s;
|
||||
return e.isMasterPlaylist ? s = function (t, e) {
|
||||
const s = new O;
|
||||
let i = !1;
|
||||
for (const [n, {
|
||||
name: a,
|
||||
value: o,
|
||||
attributes: E
|
||||
}] of t.entries()) if ("EXT-X-VERSION" === a) s.version = o; else if ("EXT-X-STREAM-INF" === a) {
|
||||
const a = t[n + 1];
|
||||
("string" != typeof a || a.startsWith("#EXT")) && r("EXT-X-STREAM-INF must be followed by a URI line");
|
||||
const o = H(t, E, a, !1, e);
|
||||
o && ("number" == typeof o.score && (i = !0, o.score < 0 && r("SCORE attribute on EXT-X-STREAM-INF must be positive decimal-floating-point number.")), s.variants.push(o))
|
||||
} else if ("EXT-X-I-FRAME-STREAM-INF" === a) {
|
||||
const i = H(t, E, E.URI, !0, e);
|
||||
i && s.variants.push(i)
|
||||
} else if ("EXT-X-SESSION-DATA" === a) {
|
||||
const t = new d({id: E["DATA-ID"], value: E.VALUE, uri: E.URI, language: E.LANGUAGE});
|
||||
s.sessionDataList.some((e => e.id === t.id && e.language === t.language)) && r("A Playlist MUST NOT contain more than one EXT-X-SESSION-DATA tag with the same DATA-ID attribute and the same LANGUAGE attribute."), s.sessionDataList.push(t)
|
||||
} else if ("EXT-X-SESSION-KEY" === a) {
|
||||
"NONE" === E.METHOD && r("EXT-X-SESSION-KEY: The value of the METHOD attribute MUST NOT be NONE");
|
||||
const t = new A({
|
||||
method: E.METHOD,
|
||||
uri: E.URI,
|
||||
iv: E.IV,
|
||||
format: E.KEYFORMAT,
|
||||
formatVersion: E.KEYFORMATVERSIONS
|
||||
});
|
||||
s.sessionKeyList.some((e => K(e, t))) && r("A Master Playlist MUST NOT contain more than one EXT-X-SESSION-KEY tag with the same METHOD, URI, IV, KEYFORMAT, and KEYFORMATVERSIONS attribute values."), G(e, E), s.sessionKeyList.push(t)
|
||||
} else "EXT-X-INDEPENDENT-SEGMENTS" === a ? (s.independentSegments && r("EXT-X-INDEPENDENT-SEGMENTS tag MUST NOT appear more than once in a Playlist"), s.independentSegments = !0) : "EXT-X-START" === a && (s.start && r("EXT-X-START tag MUST NOT appear more than once in a Playlist"), "number" != typeof E["TIME-OFFSET"] && r("EXT-X-START: TIME-OFFSET attribute is REQUIRED"), s.start = {
|
||||
offset: E["TIME-OFFSET"],
|
||||
precise: E.PRECISE || !1
|
||||
});
|
||||
if (i) for (const t of s.variants) "number" != typeof t.score && r("If any Variant Stream contains the SCORE attribute, then all Variant Streams in the Master Playlist SHOULD have a SCORE attribute");
|
||||
if (e.isClosedCaptionsNone) for (const t of s.variants) t.closedCaptions.length > 0 && r("If there is a variant with CLOSED-CAPTIONS attribute of NONE, all EXT-X-STREAM-INF tags MUST have this attribute with a value of NONE");
|
||||
return s
|
||||
}(t, e) : (s = q(t, e), !s.isIFrame && e.hasMap && e.compatibleVersion < 6 && (e.compatibleVersion = 6)), e.compatibleVersion > 1 && (!s.version || s.version < e.compatibleVersion) && r(`EXT-X-VERSION needs to be ${e.compatibleVersion} or higher.`), s
|
||||
}
|
||||
|
||||
function _(t) {
|
||||
const e = {
|
||||
version: void 0,
|
||||
isMasterPlaylist: void 0,
|
||||
hasMap: !1,
|
||||
targetDuration: 0,
|
||||
compatibleVersion: 1,
|
||||
isClosedCaptionsNone: !1,
|
||||
hash: {}
|
||||
}, s = function (t, e) {
|
||||
const s = [];
|
||||
for (const i of t.split("\n")) {
|
||||
const t = i.trim();
|
||||
if (t) if (t.startsWith("#")) {
|
||||
if (t.startsWith("#EXT")) {
|
||||
const i = j(t, e);
|
||||
i && s.push(i)
|
||||
}
|
||||
} else s.push(t)
|
||||
}
|
||||
return 0 !== s.length && "EXTM3U" === s[0].name || r("The EXTM3U tag MUST be the first line."), s
|
||||
}(t, e), i = Q(s, e);
|
||||
return i.source = t, i
|
||||
}
|
||||
|
||||
const z = ["#EXTINF", "#EXT-X-BYTERANGE", "#EXT-X-DISCONTINUITY", "#EXT-X-STREAM-INF", "#EXT-X-CUE-OUT", "#EXT-X-CUE-IN", "#EXT-X-KEY", "#EXT-X-MAP"],
|
||||
Z = ["#EXT-X-MEDIA"];
|
||||
|
||||
class J extends Array {
|
||||
constructor(t) {
|
||||
super(), this.baseUri = t
|
||||
}
|
||||
|
||||
push(...t) {
|
||||
for (const e of t) if (e.startsWith("#")) if (z.some((t => e.startsWith(t)))) super.push(e); else {
|
||||
if (this.includes(e)) {
|
||||
if (Z.some((t => e.startsWith(t)))) continue;
|
||||
r(`Redundant item (${e})`)
|
||||
}
|
||||
super.push(e)
|
||||
} else super.push(e);
|
||||
return this.length
|
||||
}
|
||||
}
|
||||
|
||||
function tt(t, e) {
|
||||
let s = 1e3;
|
||||
e && (s = Math.pow(10, e));
|
||||
const i = Math.round(t * s) / s;
|
||||
return e ? i.toFixed(e) : i
|
||||
}
|
||||
|
||||
function et(t) {
|
||||
const e = [`DATA-ID="${t.id}"`];
|
||||
return t.language && e.push(`LANGUAGE="${t.language}"`), t.value ? e.push(`VALUE="${t.value}"`) : t.uri && e.push(`URI="${t.uri}"`), `#EXT-X-SESSION-DATA:${e.join(",")}`
|
||||
}
|
||||
|
||||
function st(t, e) {
|
||||
const s = e ? "#EXT-X-SESSION-KEY" : "#EXT-X-KEY", i = [`METHOD=${t.method}`];
|
||||
return t.uri && i.push(`URI="${t.uri}"`), t.iv && (16 !== t.iv.length && r("IV must be a 128-bit unsigned integer"), i.push(`IV=${T(t.iv)}`)), t.format && i.push(`KEYFORMAT="${t.format}"`), t.formatVersion && i.push(`KEYFORMATVERSIONS="${t.formatVersion}"`), `${s}:${i.join(",")}`
|
||||
}
|
||||
|
||||
function it(t, e) {
|
||||
const s = e.isIFrameOnly ? "#EXT-X-I-FRAME-STREAM-INF" : "#EXT-X-STREAM-INF", i = [`BANDWIDTH=${e.bandwidth}`];
|
||||
if (e.averageBandwidth && i.push(`AVERAGE-BANDWIDTH=${e.averageBandwidth}`), e.isIFrameOnly && i.push(`URI="${e.uri}"`), e.codecs && i.push(`CODECS="${e.codecs}"`), e.resolution && i.push(`RESOLUTION=${e.resolution.width}x${e.resolution.height}`), e.frameRate && i.push(`FRAME-RATE=${tt(e.frameRate, 3)}`), e.hdcpLevel && i.push(`HDCP-LEVEL=${e.hdcpLevel}`), e.audio.length > 0) {
|
||||
i.push(`AUDIO="${e.audio[0].groupId}"`);
|
||||
for (const s of e.audio) t.push(nt(s))
|
||||
}
|
||||
if (e.video.length > 0) {
|
||||
i.push(`VIDEO="${e.video[0].groupId}"`);
|
||||
for (const s of e.video) t.push(nt(s))
|
||||
}
|
||||
if (e.subtitles.length > 0) {
|
||||
i.push(`SUBTITLES="${e.subtitles[0].groupId}"`);
|
||||
for (const s of e.subtitles) t.push(nt(s))
|
||||
}
|
||||
if (X().allowClosedCaptionsNone && 0 === e.closedCaptions.length) i.push("CLOSED-CAPTIONS=NONE"); else if (e.closedCaptions.length > 0) {
|
||||
i.push(`CLOSED-CAPTIONS="${e.closedCaptions[0].groupId}"`);
|
||||
for (const s of e.closedCaptions) t.push(nt(s))
|
||||
}
|
||||
if (e.score && i.push(`SCORE=${e.score}`), e.allowedCpc) {
|
||||
const t = [];
|
||||
for (const {format: s, cpcList: i} of e.allowedCpc) t.push(`${s}:${i.join("/")}`);
|
||||
i.push(`ALLOWED-CPC="${t.join(",")}"`)
|
||||
}
|
||||
e.videoRange && i.push(`VIDEO-RANGE=${e.videoRange}`), e.stableVariantId && i.push(`STABLE-VARIANT-ID="${e.stableVariantId}"`), e.programId && i.push(`PROGRAM-ID=${e.programId}`), t.push(`${s}:${i.join(",")}`), e.isIFrameOnly || t.push(`${e.uri}`)
|
||||
}
|
||||
|
||||
function nt(t) {
|
||||
const e = [`TYPE=${t.type}`, `GROUP-ID="${t.groupId}"`, `NAME="${t.name}"`];
|
||||
return void 0 !== t.isDefault && e.push("DEFAULT=" + (t.isDefault ? "YES" : "NO")), void 0 !== t.autoselect && e.push("AUTOSELECT=" + (t.autoselect ? "YES" : "NO")), void 0 !== t.forced && e.push("FORCED=" + (t.forced ? "YES" : "NO")), t.language && e.push(`LANGUAGE="${t.language}"`), t.assocLanguage && e.push(`ASSOC-LANGUAGE="${t.assocLanguage}"`), t.instreamId && e.push(`INSTREAM-ID="${t.instreamId}"`), t.characteristics && e.push(`CHARACTERISTICS="${t.characteristics}"`), t.channels && e.push(`CHANNELS="${t.channels}"`), t.uri && e.push(`URI="${t.uri}"`), `#EXT-X-MEDIA:${e.join(",")}`
|
||||
}
|
||||
|
||||
function at(t, e, s, i, n = 1, a = null) {
|
||||
let r = !1, o = "";
|
||||
if (e.discontinuity && t.push("#EXT-X-DISCONTINUITY"), e.key) {
|
||||
const i = st(e.key);
|
||||
i !== s && (t.push(i), s = i)
|
||||
}
|
||||
if (e.map) {
|
||||
const s = function (t) {
|
||||
const e = [`URI="${t.uri}"`];
|
||||
t.byterange && e.push(`BYTERANGE="${rt(t.byterange)}"`);
|
||||
return `#EXT-X-MAP:${e.join(",")}`
|
||||
}(e.map);
|
||||
s !== i && (t.push(s), i = s)
|
||||
}
|
||||
if (e.programDateTime && t.push(`#EXT-X-PROGRAM-DATE-TIME:${l(e.programDateTime)}`), e.dateRange && t.push(function (t) {
|
||||
const e = [`ID="${t.id}"`];
|
||||
t.start && e.push(`START-DATE="${l(t.start)}"`);
|
||||
t.end && e.push(`END-DATE="${l(t.end)}"`);
|
||||
t.duration && e.push(`DURATION=${t.duration}`);
|
||||
t.plannedDuration && e.push(`PLANNED-DURATION=${t.plannedDuration}`);
|
||||
t.classId && e.push(`CLASS="${t.classId}"`);
|
||||
t.endOnNext && e.push("END-ON-NEXT=YES");
|
||||
for (const s of Object.keys(t.attributes)) s.startsWith("X-") ? "number" == typeof t.attributes[s] ? e.push(`${s}=${t.attributes[s]}`) : e.push(`${s}="${t.attributes[s]}"`) : s.startsWith("SCTE35-") && e.push(`${s}=${T(t.attributes[s])}`);
|
||||
return `#EXT-X-DATERANGE:${e.join(",")}`
|
||||
}(e.dateRange)), e.markers.length > 0 && (o = function (t, e) {
|
||||
let s = "";
|
||||
for (const i of e) if ("OUT" === i.type) s = "OUT", t.push(`#EXT-X-CUE-OUT:DURATION=${i.duration}`); else if ("IN" === i.type) s = "IN", t.push("#EXT-X-CUE-IN"); else if ("RAW" === i.type) {
|
||||
const e = i.value ? `:${i.value}` : "";
|
||||
t.push(`#${i.tagName}${e}`)
|
||||
}
|
||||
return s
|
||||
}(t, e.markers)), e.parts.length > 0 && (r = function (t, e) {
|
||||
let s = !1;
|
||||
for (const i of e) if (i.hint) {
|
||||
const e = [];
|
||||
if (e.push("TYPE=PART", `URI="${i.uri}"`), i.byterange) {
|
||||
const {offset: t, length: s} = i.byterange;
|
||||
e.push(`BYTERANGE-START=${t}`), s && e.push(`BYTERANGE-LENGTH=${s}`)
|
||||
}
|
||||
t.push(`#EXT-X-PRELOAD-HINT:${e.join(",")}`), s = !0
|
||||
} else {
|
||||
const e = [];
|
||||
e.push(`DURATION=${i.duration}`, `URI="${i.uri}"`), i.byterange && e.push(`BYTERANGE=${rt(i.byterange)}`), i.independent && e.push("INDEPENDENT=YES"), i.gap && e.push("GAP=YES"), t.push(`#EXT-X-PART:${e.join(",")}`)
|
||||
}
|
||||
return s
|
||||
}(t, e.parts)), r) return [s, i];
|
||||
const E = n < 3 ? Math.round(e.duration) : tt(e.duration, function (t) {
|
||||
const e = t.toString(10), s = e.indexOf(".");
|
||||
return -1 === s ? 0 : e.length - s - 1
|
||||
}(e.duration));
|
||||
return t.push(`#EXTINF:${E},${unescape(encodeURIComponent(e.title || ""))}`), e.byterange && t.push(`#EXT-X-BYTERANGE:${rt(e.byterange)}`), null != a ? Array.prototype.push.call(t, a(e)) : Array.prototype.push.call(t, `${e.uri}`), [s, i, o]
|
||||
}
|
||||
|
||||
function rt({offset: t, length: e}) {
|
||||
return `${e}@${t}`
|
||||
}
|
||||
|
||||
function ot(t, e = null) {
|
||||
n(t), s("Not a playlist", "playlist" === t.type);
|
||||
const i = new J(t.uri);
|
||||
return i.push("#EXTM3U"), t.version && i.push(`#EXT-X-VERSION:${t.version}`), t.independentSegments && i.push("#EXT-X-INDEPENDENT-SEGMENTS"), t.start && i.push(`#EXT-X-START:TIME-OFFSET=${tt(t.start.offset)}${t.start.precise ? ",PRECISE=YES" : ""}`), t.isMasterPlaylist ? function (t, e) {
|
||||
for (const s of e.sessionDataList) t.push(et(s));
|
||||
for (const s of e.sessionKeyList) t.push(st(s, !0));
|
||||
for (const s of e.variants) it(t, s)
|
||||
}(i, t) : function (t, e, s = null) {
|
||||
let i = "", n = "", a = !1;
|
||||
if (e.targetDuration && t.push(`#EXT-X-TARGETDURATION:${e.targetDuration}`), e.lowLatencyCompatibility) {
|
||||
const {canBlockReload: s, canSkipUntil: i, holdBack: n, partHoldBack: a} = e.lowLatencyCompatibility,
|
||||
r = [];
|
||||
r.push("CAN-BLOCK-RELOAD=" + (s ? "YES" : "NO")), void 0 !== i && r.push(`CAN-SKIP-UNTIL=${i}`), void 0 !== n && r.push(`HOLD-BACK=${n}`), void 0 !== a && r.push(`PART-HOLD-BACK=${a}`), t.push(`#EXT-X-SERVER-CONTROL:${r.join(",")}`)
|
||||
}
|
||||
e.partTargetDuration && t.push(`#EXT-X-PART-INF:PART-TARGET=${e.partTargetDuration}`), e.mediaSequenceBase && t.push(`#EXT-X-MEDIA-SEQUENCE:${e.mediaSequenceBase}`), e.discontinuitySequenceBase && t.push(`#EXT-X-DISCONTINUITY-SEQUENCE:${e.discontinuitySequenceBase}`), e.playlistType && t.push(`#EXT-X-PLAYLIST-TYPE:${e.playlistType}`), e.isIFrame && t.push("#EXT-X-I-FRAMES-ONLY"), e.skip > 0 && t.push(`#EXT-X-SKIP:SKIPPED-SEGMENTS=${e.skip}`);
|
||||
for (const r of e.segments) {
|
||||
let o = "";
|
||||
[i, n, o] = at(t, r, i, n, e.version, s), "OUT" === o ? a = !0 : "IN" === o && a && (a = !1)
|
||||
}
|
||||
"VOD" === e.playlistType && a && t.push("#EXT-X-CUE-IN"), e.prefetchSegments.length > 2 && r("The server must deliver no more than two prefetch segments");
|
||||
for (const s of e.prefetchSegments) s.discontinuity && t.push("#EXT-X-PREFETCH-DISCONTINUITY"), t.push(`#EXT-X-PREFETCH:${s.uri}`);
|
||||
e.endlist && t.push("#EXT-X-ENDLIST");
|
||||
for (const s of e.renditionReports) {
|
||||
const e = [];
|
||||
e.push(`URI="${s.uri}"`, `LAST-MSN=${s.lastMSN}`), void 0 !== s.lastPart && e.push(`LAST-PART=${s.lastPart}`), t.push(`#EXT-X-RENDITION-REPORT:${e.join(",")}`)
|
||||
}
|
||||
}(i, t, e), i.join("\n")
|
||||
}
|
||||
|
||||
export {X as getOptions, _ as parse, h as setOptions, ot as stringify, M as types};
|
441
cat/tjs/lib/iso-2022-jp.js
Normal file
441
cat/tjs/lib/iso-2022-jp.js
Normal file
@ -0,0 +1,441 @@
|
||||
import { inRange, decoderError, encoderError, isASCIICodePoint,
|
||||
end_of_stream, finished, floor } from './text_decoder_utils.js'
|
||||
import index, { indexCodePointFor, indexPointerFor } from './text_decoder_indexes.js'
|
||||
|
||||
// 13.2 iso-2022-jp
|
||||
|
||||
// 13.2.1 iso-2022-jp decoder
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class ISO2022JPDecoder {
|
||||
constructor(options) {
|
||||
const { fatal } = options
|
||||
this.fatal = fatal
|
||||
/** @enum */
|
||||
this.states = {
|
||||
ASCII: 0,
|
||||
Roman: 1,
|
||||
Katakana: 2,
|
||||
LeadByte: 3,
|
||||
TrailByte: 4,
|
||||
EscapeStart: 5,
|
||||
Escape: 6,
|
||||
}
|
||||
// iso-2022-jp's decoder has an associated iso-2022-jp decoder
|
||||
// state (initially ASCII), iso-2022-jp decoder output state
|
||||
// (initially ASCII), iso-2022-jp lead (initially 0x00), and
|
||||
// iso-2022-jp output flag (initially unset).
|
||||
this.iso2022jp_decoder_state = this.states.ASCII
|
||||
this.iso2022jp_decoder_output_state = this.states.ASCII,
|
||||
this.iso2022jp_lead = 0x00
|
||||
this.iso2022jp_output_flag = false
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// switching on iso-2022-jp decoder state:
|
||||
switch (this.iso2022jp_decoder_state) {
|
||||
default:
|
||||
case this.states.ASCII:
|
||||
// ASCII
|
||||
// Based on byte:
|
||||
|
||||
// 0x1B
|
||||
if (bite === 0x1B) {
|
||||
// Set iso-2022-jp decoder state to escape start and return
|
||||
// continue.
|
||||
this.iso2022jp_decoder_state = this.states.EscapeStart
|
||||
return null
|
||||
}
|
||||
|
||||
// 0x00 to 0x7F, excluding 0x0E, 0x0F, and 0x1B
|
||||
if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E
|
||||
&& bite !== 0x0F && bite !== 0x1B) {
|
||||
// Unset the iso-2022-jp output flag and return a code point
|
||||
// whose value is byte.
|
||||
this.iso2022jp_output_flag = false
|
||||
return bite
|
||||
}
|
||||
|
||||
// end-of-stream
|
||||
if (bite === end_of_stream) {
|
||||
// Return finished.
|
||||
return finished
|
||||
}
|
||||
|
||||
// Otherwise
|
||||
// Unset the iso-2022-jp output flag and return error.
|
||||
this.iso2022jp_output_flag = false
|
||||
return decoderError(this.fatal)
|
||||
|
||||
case this.states.Roman:
|
||||
// Roman
|
||||
// Based on byte:
|
||||
|
||||
// 0x1B
|
||||
if (bite === 0x1B) {
|
||||
// Set iso-2022-jp decoder state to escape start and return
|
||||
// continue.
|
||||
this.iso2022jp_decoder_state = this.states.EscapeStart
|
||||
return null
|
||||
}
|
||||
|
||||
// 0x5C
|
||||
if (bite === 0x5C) {
|
||||
// Unset the iso-2022-jp output flag and return code point
|
||||
// U+00A5.
|
||||
this.iso2022jp_output_flag = false
|
||||
return 0x00A5
|
||||
}
|
||||
|
||||
// 0x7E
|
||||
if (bite === 0x7E) {
|
||||
// Unset the iso-2022-jp output flag and return code point
|
||||
// U+203E.
|
||||
this.iso2022jp_output_flag = false
|
||||
return 0x203E
|
||||
}
|
||||
|
||||
// 0x00 to 0x7F, excluding 0x0E, 0x0F, 0x1B, 0x5C, and 0x7E
|
||||
if (inRange(bite, 0x00, 0x7F) && bite !== 0x0E && bite !== 0x0F
|
||||
&& bite !== 0x1B && bite !== 0x5C && bite !== 0x7E) {
|
||||
// Unset the iso-2022-jp output flag and return a code point
|
||||
// whose value is byte.
|
||||
this.iso2022jp_output_flag = false
|
||||
return bite
|
||||
}
|
||||
|
||||
// end-of-stream
|
||||
if (bite === end_of_stream) {
|
||||
// Return finished.
|
||||
return finished
|
||||
}
|
||||
|
||||
// Otherwise
|
||||
// Unset the iso-2022-jp output flag and return error.
|
||||
this.iso2022jp_output_flag = false
|
||||
return decoderError(this.fatal)
|
||||
|
||||
case this.states.Katakana:
|
||||
// Katakana
|
||||
// Based on byte:
|
||||
|
||||
// 0x1B
|
||||
if (bite === 0x1B) {
|
||||
// Set iso-2022-jp decoder state to escape start and return
|
||||
// continue.
|
||||
this.iso2022jp_decoder_state = this.states.EscapeStart
|
||||
return null
|
||||
}
|
||||
|
||||
// 0x21 to 0x5F
|
||||
if (inRange(bite, 0x21, 0x5F)) {
|
||||
// Unset the iso-2022-jp output flag and return a code point
|
||||
// whose value is 0xFF61 − 0x21 + byte.
|
||||
this.iso2022jp_output_flag = false
|
||||
return 0xFF61 - 0x21 + bite
|
||||
}
|
||||
|
||||
// end-of-stream
|
||||
if (bite === end_of_stream) {
|
||||
// Return finished.
|
||||
return finished
|
||||
}
|
||||
|
||||
// Otherwise
|
||||
// Unset the iso-2022-jp output flag and return error.
|
||||
this.iso2022jp_output_flag = false
|
||||
return decoderError(this.fatal)
|
||||
|
||||
case this.states.LeadByte:
|
||||
// Lead byte
|
||||
// Based on byte:
|
||||
|
||||
// 0x1B
|
||||
if (bite === 0x1B) {
|
||||
// Set iso-2022-jp decoder state to escape start and return
|
||||
// continue.
|
||||
this.iso2022jp_decoder_state = this.states.EscapeStart
|
||||
return null
|
||||
}
|
||||
|
||||
// 0x21 to 0x7E
|
||||
if (inRange(bite, 0x21, 0x7E)) {
|
||||
// Unset the iso-2022-jp output flag, set iso-2022-jp lead
|
||||
// to byte, iso-2022-jp decoder state to trail byte, and
|
||||
// return continue.
|
||||
this.iso2022jp_output_flag = false
|
||||
this.iso2022jp_lead = bite
|
||||
this.iso2022jp_decoder_state = this.states.TrailByte
|
||||
return null
|
||||
}
|
||||
|
||||
// end-of-stream
|
||||
if (bite === end_of_stream) {
|
||||
// Return finished.
|
||||
return finished
|
||||
}
|
||||
|
||||
// Otherwise
|
||||
// Unset the iso-2022-jp output flag and return error.
|
||||
this.iso2022jp_output_flag = false
|
||||
return decoderError(this.fatal)
|
||||
|
||||
case this.states.TrailByte:
|
||||
// Trail byte
|
||||
// Based on byte:
|
||||
|
||||
// 0x1B
|
||||
if (bite === 0x1B) {
|
||||
// Set iso-2022-jp decoder state to escape start and return
|
||||
// continue.
|
||||
this.iso2022jp_decoder_state = this.states.EscapeStart
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 0x21 to 0x7E
|
||||
if (inRange(bite, 0x21, 0x7E)) {
|
||||
// 1. Set the iso-2022-jp decoder state to lead byte.
|
||||
this.iso2022jp_decoder_state = this.states.LeadByte
|
||||
|
||||
// 2. Let pointer be (iso-2022-jp lead − 0x21) × 94 + byte − 0x21.
|
||||
const pointer = (this.iso2022jp_lead - 0x21) * 94 + bite - 0x21
|
||||
|
||||
// 3. Let code point be the index code point for pointer in
|
||||
// index jis0208.
|
||||
const code_point = indexCodePointFor(pointer, index('jis0208'))
|
||||
|
||||
// 4. If code point is null, return error.
|
||||
if (code_point === null)
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 5. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
|
||||
// end-of-stream
|
||||
if (bite === end_of_stream) {
|
||||
// Set the iso-2022-jp decoder state to lead byte, prepend
|
||||
// byte to stream, and return error.
|
||||
this.iso2022jp_decoder_state = this.states.LeadByte
|
||||
stream.prepend(bite)
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// Otherwise
|
||||
// Set iso-2022-jp decoder state to lead byte and return
|
||||
// error.
|
||||
this.iso2022jp_decoder_state = this.states.LeadByte
|
||||
return decoderError(this.fatal)
|
||||
|
||||
case this.states.EscapeStart:
|
||||
// Escape start
|
||||
|
||||
// 1. If byte is either 0x24 or 0x28, set iso-2022-jp lead to
|
||||
// byte, iso-2022-jp decoder state to escape, and return
|
||||
// continue.
|
||||
if (bite === 0x24 || bite === 0x28) {
|
||||
this.iso2022jp_lead = bite
|
||||
this.iso2022jp_decoder_state = this.states.Escape
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Prepend byte to stream.
|
||||
stream.prepend(bite)
|
||||
|
||||
// 3. Unset the iso-2022-jp output flag, set iso-2022-jp
|
||||
// decoder state to iso-2022-jp decoder output state, and
|
||||
// return error.
|
||||
this.iso2022jp_output_flag = false
|
||||
this.iso2022jp_decoder_state = this.iso2022jp_decoder_output_state
|
||||
return decoderError(this.fatal)
|
||||
|
||||
case this.states.Escape: {
|
||||
// Escape
|
||||
|
||||
// 1. Let lead be iso-2022-jp lead and set iso-2022-jp lead to
|
||||
// 0x00.
|
||||
const lead = this.iso2022jp_lead
|
||||
this.iso2022jp_lead = 0x00
|
||||
|
||||
// 2. Let state be null.
|
||||
let state = null
|
||||
|
||||
// 3. If lead is 0x28 and byte is 0x42, set state to ASCII.
|
||||
if (lead === 0x28 && bite === 0x42)
|
||||
state = this.states.ASCII
|
||||
|
||||
// 4. If lead is 0x28 and byte is 0x4A, set state to Roman.
|
||||
if (lead === 0x28 && bite === 0x4A)
|
||||
state = this.states.Roman
|
||||
|
||||
// 5. If lead is 0x28 and byte is 0x49, set state to Katakana.
|
||||
if (lead === 0x28 && bite === 0x49)
|
||||
state = this.states.Katakana
|
||||
|
||||
// 6. If lead is 0x24 and byte is either 0x40 or 0x42, set
|
||||
// state to lead byte.
|
||||
if (lead === 0x24 && (bite === 0x40 || bite === 0x42))
|
||||
state = this.states.LeadByte
|
||||
|
||||
// 7. If state is non-null, run these substeps:
|
||||
if (state !== null) {
|
||||
// 1. Set iso-2022-jp decoder state and iso-2022-jp decoder
|
||||
// output state to this.states.
|
||||
this.iso2022jp_decoder_state = this.iso2022jp_decoder_state = state
|
||||
|
||||
// 2. Let output flag be the iso-2022-jp output flag.
|
||||
const output_flag = this.iso2022jp_output_flag
|
||||
|
||||
// 3. Set the iso-2022-jp output flag.
|
||||
this.iso2022jp_output_flag = true
|
||||
|
||||
// 4. Return continue, if output flag is unset, and error
|
||||
// otherwise.
|
||||
return !output_flag ? null : decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 8. Prepend lead and byte to stream.
|
||||
stream.prepend([lead, bite])
|
||||
|
||||
// 9. Unset the iso-2022-jp output flag, set iso-2022-jp
|
||||
// decoder state to iso-2022-jp decoder output state and
|
||||
// return error.
|
||||
this.iso2022jp_output_flag = false
|
||||
this.iso2022jp_decoder_state = this.iso2022jp_decoder_output_state
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 13.2.2 iso-2022-jp encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class ISO2022JPEncoder {
|
||||
constructor() {
|
||||
// iso-2022-jp's encoder has an associated iso-2022-jp encoder
|
||||
// state which is one of ASCII, Roman, and jis0208 (initially
|
||||
// ASCII).
|
||||
/** @enum */
|
||||
this.states = {
|
||||
ASCII: 0,
|
||||
Roman: 1,
|
||||
jis0208: 2,
|
||||
}
|
||||
this.iso2022jp_state = this.states.ASCII
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1. If code point is end-of-stream and iso-2022-jp encoder
|
||||
// state is not ASCII, prepend code point to stream, set
|
||||
// iso-2022-jp encoder state to ASCII, and return three bytes
|
||||
// 0x1B 0x28 0x42.
|
||||
if (code_point === end_of_stream &&
|
||||
this.iso2022jp_state !== this.states.ASCII) {
|
||||
stream.prepend(code_point)
|
||||
this.iso2022jp_state = this.states.ASCII
|
||||
return [0x1B, 0x28, 0x42]
|
||||
}
|
||||
|
||||
// 2. If code point is end-of-stream and iso-2022-jp encoder
|
||||
// state is ASCII, return finished.
|
||||
if (code_point === end_of_stream && this.iso2022jp_state === this.states.ASCII)
|
||||
return finished
|
||||
|
||||
// 3. If ISO-2022-JP encoder state is ASCII or Roman, and code
|
||||
// point is U+000E, U+000F, or U+001B, return error with U+FFFD.
|
||||
if ((this.iso2022jp_state === this.states.ASCII ||
|
||||
this.iso2022jp_state === this.states.Roman) &&
|
||||
(code_point === 0x000E || code_point === 0x000F ||
|
||||
code_point === 0x001B)) {
|
||||
return encoderError(0xFFFD)
|
||||
}
|
||||
|
||||
// 4. If iso-2022-jp encoder state is ASCII and code point is an
|
||||
// ASCII code point, return a byte whose value is code point.
|
||||
if (this.iso2022jp_state === this.states.ASCII &&
|
||||
isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 5. If iso-2022-jp encoder state is Roman and code point is an
|
||||
// ASCII code point, excluding U+005C and U+007E, or is U+00A5
|
||||
// or U+203E, run these substeps:
|
||||
if (this.iso2022jp_state === this.states.Roman &&
|
||||
((isASCIICodePoint(code_point) &&
|
||||
code_point !== 0x005C && code_point !== 0x007E) ||
|
||||
(code_point == 0x00A5 || code_point == 0x203E))) {
|
||||
// 1. If code point is an ASCII code point, return a byte
|
||||
// whose value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 2. If code point is U+00A5, return byte 0x5C.
|
||||
if (code_point === 0x00A5)
|
||||
return 0x5C
|
||||
|
||||
// 3. If code point is U+203E, return byte 0x7E.
|
||||
if (code_point === 0x203E)
|
||||
return 0x7E
|
||||
}
|
||||
|
||||
// 6. If code point is an ASCII code point, and iso-2022-jp
|
||||
// encoder state is not ASCII, prepend code point to stream, set
|
||||
// iso-2022-jp encoder state to ASCII, and return three bytes
|
||||
// 0x1B 0x28 0x42.
|
||||
if (isASCIICodePoint(code_point) &&
|
||||
this.iso2022jp_state !== this.states.ASCII) {
|
||||
stream.prepend(code_point)
|
||||
this.iso2022jp_state = this.states.ASCII
|
||||
return [0x1B, 0x28, 0x42]
|
||||
}
|
||||
|
||||
// 7. If code point is either U+00A5 or U+203E, and iso-2022-jp
|
||||
// encoder state is not Roman, prepend code point to stream, set
|
||||
// iso-2022-jp encoder state to Roman, and return three bytes
|
||||
// 0x1B 0x28 0x4A.
|
||||
if ((code_point === 0x00A5 || code_point === 0x203E) &&
|
||||
this.iso2022jp_state !== this.states.Roman) {
|
||||
stream.prepend(code_point)
|
||||
this.iso2022jp_state = this.states.Roman
|
||||
return [0x1B, 0x28, 0x4A]
|
||||
}
|
||||
|
||||
// 8. If code point is U+2212, set it to U+FF0D.
|
||||
if (code_point === 0x2212)
|
||||
code_point = 0xFF0D
|
||||
|
||||
// 9. Let pointer be the index pointer for code point in index
|
||||
// jis0208.
|
||||
const pointer = indexPointerFor(code_point, index('jis0208'))
|
||||
|
||||
// 10. If pointer is null, return error with code point.
|
||||
if (pointer === null)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 11. If iso-2022-jp encoder state is not jis0208, prepend code
|
||||
// point to stream, set iso-2022-jp encoder state to jis0208,
|
||||
// and return three bytes 0x1B 0x24 0x42.
|
||||
if (this.iso2022jp_state !== this.states.jis0208) {
|
||||
stream.prepend(code_point)
|
||||
this.iso2022jp_state = this.states.jis0208
|
||||
return [0x1B, 0x24, 0x42]
|
||||
}
|
||||
|
||||
// 12. Let lead be floor(pointer / 94) + 0x21.
|
||||
const lead = floor(pointer / 94) + 0x21
|
||||
|
||||
// 13. Let trail be pointer % 94 + 0x21.
|
||||
const trail = pointer % 94 + 0x21
|
||||
|
||||
// 14. Return two bytes whose values are lead and trail.
|
||||
return [lead, trail]
|
||||
}
|
||||
}
|
86
cat/tjs/lib/log.js
Normal file
86
cat/tjs/lib/log.js
Normal file
@ -0,0 +1,86 @@
|
||||
const level_list = ["DEBUG", "INFO", "WARNING", "ERROR"];
|
||||
const file_path = "log"
|
||||
|
||||
class JadeLogging {
|
||||
|
||||
constructor(app_name, level = "DEBUG") {
|
||||
this.app_name = app_name
|
||||
this.level = level
|
||||
this.level_index = level_list.indexOf(level)
|
||||
}
|
||||
|
||||
|
||||
format(level, message) {
|
||||
let max_format = 80
|
||||
switch (level) {
|
||||
case "INFO":
|
||||
max_format = max_format + 1
|
||||
break
|
||||
case "WARNING":
|
||||
max_format = max_format - 2
|
||||
break
|
||||
default :
|
||||
break
|
||||
}
|
||||
if (message.length < max_format) {
|
||||
if ((max_format - message.length) % 2 === 0) {
|
||||
message = "#".repeat(Math.floor((max_format - message.length) / 2)) + message + "#".repeat(Math.floor((max_format - message.length) / 2))
|
||||
} else {
|
||||
message = "#".repeat(Math.floor((max_format - message.length) / 2)) + message + "#".repeat(Math.floor((max_format - message.length) / 2) + 1)
|
||||
}
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
getTime() {
|
||||
const timestamp = new Date();
|
||||
// 获取当前时间戳
|
||||
return timestamp.toLocaleDateString().replace(/\//g, "-") + " " + timestamp.toTimeString().substr(0, 8) + "," + timestamp.getMilliseconds().toString()
|
||||
}
|
||||
|
||||
formatMessage(log_level, message, is_format) {
|
||||
// 获取北京时间
|
||||
// 格式化消息
|
||||
//2023-12-13 15:15:21,409 - 阿里玩偶 -
|
||||
//2023-12-14T01:43:31.278Z
|
||||
//2023-12-13 15:15:21,409 - 阿里玩偶 - INFO:
|
||||
//2023-12-13 15:15:21,409 - 阿里玩偶 - ERROR:
|
||||
if (is_format) {
|
||||
message = this.format(log_level, message)
|
||||
}
|
||||
return `${this.getTime()} - ${this.app_name} - ${log_level}: ${message}`
|
||||
|
||||
}
|
||||
|
||||
async log(message) {
|
||||
console.debug(message)
|
||||
await local.set(file_path,this.getTime(), message);
|
||||
}
|
||||
|
||||
async info(message, is_format=false) {
|
||||
if (this.level_index <= 1) {
|
||||
await this.log(this.formatMessage("INFO", message, is_format))
|
||||
}
|
||||
}
|
||||
|
||||
async warning(message, is_format=false) {
|
||||
if (this.level_index <= 2) {
|
||||
await this.log(this.formatMessage("WARNING", message, is_format))
|
||||
}
|
||||
}
|
||||
|
||||
async error(message, is_format=false) {
|
||||
if (this.level_index <= 3) {
|
||||
await this.log(this.formatMessage("ERROR", message, is_format))
|
||||
}
|
||||
}
|
||||
|
||||
async debug(message, is_format=false) {
|
||||
if (this.level_index <= 0) {
|
||||
await this.log(this.formatMessage("DEBUG", message, is_format))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试日志记录函数
|
||||
export {JadeLogging}
|
129
cat/tjs/lib/misc.js
Normal file
129
cat/tjs/lib/misc.js
Normal file
@ -0,0 +1,129 @@
|
||||
var charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
export function rand(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
export function randStr(len, withNum, onlyNum) {
|
||||
var _str = '';
|
||||
let containsNum = withNum === undefined ? true : withNum;
|
||||
for (var i = 0; i < len; i++) {
|
||||
let idx = onlyNum ? rand(charStr.length - 10, charStr.length - 1) : rand(0, containsNum ? charStr.length - 1 : charStr.length - 11);
|
||||
_str += charStr[idx];
|
||||
}
|
||||
return _str;
|
||||
}
|
||||
|
||||
export function randUUID() {
|
||||
return randStr(8).toLowerCase() + '-' + randStr(4).toLowerCase() + '-' + randStr(4).toLowerCase() + '-' + randStr(4).toLowerCase() + '-' + randStr(12).toLowerCase();
|
||||
}
|
||||
|
||||
export function randMAC() {
|
||||
return randStr(2).toUpperCase() + ':' + randStr(2).toUpperCase() + ':' + randStr(2).toUpperCase() + ':' + randStr(2).toUpperCase() + ':' + randStr(2).toUpperCase() + ':' + randStr(2).toUpperCase();
|
||||
}
|
||||
|
||||
const deviceBrands = ['Huawei', 'Xiaomi'];
|
||||
const deviceModels = [
|
||||
['MHA-AL00', 'HUAWEI Mate 9', 'MHA-TL00', 'HUAWEI Mate 9', 'LON-AL00', 'HUAWEI Mate 9 Pro', 'ALP-AL00', 'HUAWEI Mate 10', 'ALP-TL00', 'HUAWEI Mate 10', 'BLA-AL00', 'HUAWEI Mate 10 Pro', 'BLA-TL00', 'HUAWEI Mate 10 Pro', 'HMA-AL00', 'HUAWEI Mate 20', 'HMA-TL00', 'HUAWEI Mate 20', 'LYA-AL00', 'HUAWEI Mate 20 Pro', 'LYA-AL10', 'HUAWEI Mate 20 Pro', 'LYA-TL00', 'HUAWEI Mate 20 Pro', 'EVR-AL00', 'HUAWEI Mate 20 X', 'EVR-TL00', 'HUAWEI Mate 20 X', 'EVR-AN00', 'HUAWEI Mate 20 X', 'TAS-AL00', 'HUAWEI Mate 30', 'TAS-TL00', 'HUAWEI Mate 30', 'TAS-AN00', 'HUAWEI Mate 30', 'TAS-TN00', 'HUAWEI Mate 30', 'LIO-AL00', 'HUAWEI Mate 30 Pro', 'LIO-TL00', 'HUAWEI Mate 30 Pro', 'LIO-AN00', 'HUAWEI Mate 30 Pro', 'LIO-TN00', 'HUAWEI Mate 30 Pro', 'LIO-AN00m', 'HUAWEI Mate 30E Pro', 'OCE-AN10', 'HUAWEI Mate 40', 'OCE-AN50', 'HUAWEI Mate 40E', 'OCE-AL50', 'HUAWEI Mate 40E', 'NOH-AN00', 'HUAWEI Mate 40 Pro', 'NOH-AN01', 'HUAWEI Mate 40 Pro', 'NOH-AL00', 'HUAWEI Mate 40 Pro', 'NOH-AL10', 'HUAWEI Mate 40 Pro', 'NOH-AN50', 'HUAWEI Mate 40E Pro', 'NOP-AN00', 'HUAWEI Mate 40 Pro', 'CET-AL00', 'HUAWEI Mate 50', 'CET-AL60', 'HUAWEI Mate 50E', 'DCO-AL00', 'HUAWEI Mate 50 Pro', 'TAH-AN00', 'HUAWEI Mate X', 'TAH-AN00m', 'HUAWEI Mate Xs', 'TET-AN00', 'HUAWEI Mate X2', 'TET-AN10', 'HUAWEI Mate X2', 'TET-AN50', 'HUAWEI Mate X2', 'TET-AL00', 'HUAWEI Mate X2', 'PAL-AL00', 'HUAWEI Mate Xs 2', 'PAL-AL10', 'HUAWEI Mate Xs 2', 'EVA-AL00', 'HUAWEI P9', 'EVA-AL10', 'HUAWEI P9', 'EVA-TL00', 'HUAWEI P9', 'EVA-DL00', 'HUAWEI P9', 'EVA-CL00', 'HUAWEI P9', 'VIE-AL10', 'HUAWEI P9 Plus', 'VTR-AL00', 'HUAWEI P10', 'VTR-TL00', 'HUAWEI P10', 'VKY-AL00', 'HUAWEI P10 Plus', 'VKY-TL00', 'HUAWEI P10 Plus', 'EML-AL00', 'HUAWEI P20', 'EML-TL00', 'HUAWEI P20', 'CLT-AL00', 'HUAWEI P20 Pro', 'CLT-AL01', 'HUAWEI P20 Pro', 'CLT-AL00l', 'HUAWEI P20 Pro', 'CLT-TL00', 'HUAWEI P20 Pro', 'CLT-TL01', 'HUAWEI P20 Pro', 'ELE-AL00', 'HUAWEI P30', 'ELE-TL00', 'HUAWEI P30', 'VOG-AL00', 'HUAWEI P30 Pro', 'VOG-AL10', 'HUAWEI P30 Pro', 'VOG-TL00', 'HUAWEI P30 Pro', 'ANA-AL00', 'HUAWEI P40', 'ANA-AN00', 'HUAWEI P40', 'ANA-TN00', 'HUAWEI P40', 'ELS-AN00', 'HUAWEI P40 Pro', 'ELS-TN00', 'HUAWEI P40 Pro', 'ELS-AN10', 'HUAWEI P40 Pro', 'ELS-TN10', 'HUAWEI P40 Pro', 'ABR-AL00', 'HUAWEI P50', 'ABR-AL80', 'HUAWEI P50', 'ABR-AL60', 'HUAWEI P50E', 'ABR-AL90', 'HUAWEI P50E', 'JAD-AL00', 'HUAWEI P50 Pro', 'JAD-AL80', 'HUAWEI P50 Pro', 'JAD-AL50', 'HUAWEI P50 Pro', 'JAD-AL60', 'HUAWEI P50 Pro', 'BAL-AL00', 'HUAWEI P50 Pocket', 'BAL-AL60', 'HUAWEI Pocket S', 'PIC-AL00', 'HUAWEI nova 2', 'PIC-TL00', 'HUAWEI nova 2', 'BAC-AL00', 'HUAWEI nova 2 Plus', 'BAC-TL00', 'HUAWEI nova 2 Plus', 'HWI-AL00', 'HUAWEI nova 2s', 'HWI-TL00', 'HUAWEI nova 2s', 'ANE-AL00', 'HUAWEI nova 3e', 'ANE-TL00', 'HUAWEI nova 3e', 'PAR-AL00', 'HUAWEI nova 3', 'PAR-TL00', 'HUAWEI nova 3', 'INE-AL00', 'HUAWEI nova 3i', 'INE-TL00', 'HUAWEI nova 3i', 'VCE-AL00', 'HUAWEI nova 4', 'VCE-TL00', 'HUAWEI nova 4', 'MAR-AL00', 'HUAWEI nova 4e', 'MAR-TL00', 'HUAWEI nova 4e', 'SEA-AL00', 'HUAWEI nova 5', 'SEA-TL00', 'HUAWEI nova 5', 'SEA-AL10', 'HUAWEI nova 5 Pro', 'SEA-TL10', 'HUAWEI nova 5 Pro', 'GLK-AL00', 'HUAWEI nova 5i', 'GLK-TL00', 'HUAWEI nova 5i', 'GLK-LX1U', 'HUAWEI nova 5i', 'SPN-TL00', 'HUAWEI nova 5i Pro', 'SPN-AL00', 'HUAWEI nova 5z', 'WLZ-AL10', 'HUAWEI nova 6', 'WLZ-AN00', 'HUAWEI nova 6', 'JNY-AL10', 'HUAWEI nova 6 SE', 'JNY-TL10', 'HUAWEI nova 6 SE', 'JEF-AN00', 'HUAWEI nova 7', 'JEF-AN20', 'HUAWEI nova 7', 'JEF-TN00', 'HUAWEI nova 7', 'JEF-TN20', 'HUAWEI nova 7', 'JER-AN10', 'HUAWEI nova 7 Pro', 'JER-AN20', 'HUAWEI nova 7 Pro', 'JER-TN10', 'HUAWEI nova 7 Pro', 'JER-TN20', 'HUAWEI nova 7 Pro', 'CDY-AN00', 'HUAWEI nova 7 SE', 'CDY-AN20', 'HUAWEI nova 7 SE', 'CDY-TN00', 'HUAWEI nova 7 SE', 'CDY-TN20', 'HUAWEI nova 7 SE', 'ANG-AN00', 'HUAWEI nova 8', 'BRQ-AN00', 'HUAWEI nova 8 Pro', 'BRQ-AL00', 'HUAWEI nova 8 Pro', 'JSC-AN00', 'HUAWEI nova 8 SE', 'JSC-TN00', 'HUAWEI nova 8 SE', 'JSC-AL50', 'HUAWEI nova 8 SE', 'NAM-AL00', 'HUAWEI nova 9', 'RTE-AL00', 'HUAWEI nova 9 Pro', 'JLN-AL00', 'HUAWEI nova 9 SE', 'NCO-AL00', 'HUAWEI nova 10', 'GLA-AL00', 'HUAWEI nova 10 Pro', 'CHA-AL80', 'HUAWEI nova 10z'],
|
||||
['M2001J2C', 'Xiaomi 10', 'M2001J2G', 'Xiaomi 10', 'M2001J2I', 'Xiaomi 10', 'M2011K2C', 'Xiaomi 11', 'M2011K2G', 'Xiaomi 11', '2201123C', 'Xiaomi 12', '2201123G', 'Xiaomi 12', '2112123AC', 'Xiaomi 12X', '2112123AG', 'Xiaomi 12X', '2201122C', 'Xiaomi 12 Pro', '2201122G', 'Xiaomi 12 Pro'],
|
||||
];
|
||||
export function randDevice() {
|
||||
let brandIdx = rand(0, deviceBrands.length - 1);
|
||||
let brand = deviceBrands[brandIdx];
|
||||
let modelIdx = rand(0, deviceModels[brandIdx].length / 2 - 1);
|
||||
let model = deviceModels[brandIdx][modelIdx * 2 + 1];
|
||||
let release = rand(8, 13);
|
||||
let buildId = randStr(3, false).toUpperCase() + rand(11, 99) + randStr(1, false).toUpperCase();
|
||||
return {
|
||||
brand: brand,
|
||||
model: model,
|
||||
release: release,
|
||||
buildId: buildId,
|
||||
};
|
||||
}
|
||||
|
||||
export function randDeviceWithId(len) {
|
||||
let device = randDevice();
|
||||
device['id'] = randStr(len);
|
||||
return device;
|
||||
}
|
||||
|
||||
export const MOBILE_UA = 'Mozilla/5.0 (Linux; Android 11; M2007J3SC Build/RKQ1.200826.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/6.2 TBS/045714 Mobile Safari/537.36';
|
||||
export const PC_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36';
|
||||
export const UA = 'Mozilla/5.0';
|
||||
export const UC_UA = 'Mozilla/5.0 (Linux; U; Android 9; zh-CN; MI 9 Build/PKQ1.181121.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.5.5.1035 Mobile Safari/537.36';
|
||||
export const IOS_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1';
|
||||
export const MAC_UA = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36 SE 2.X MetaSr 1.0';
|
||||
|
||||
export function formatPlayUrl(src, name) {
|
||||
if (src.trim() == name.trim()) {
|
||||
return name;
|
||||
}
|
||||
return name
|
||||
.trim()
|
||||
.replaceAll(src, '')
|
||||
.replace(/<|>|《|》/g, '')
|
||||
.replace(/\$|#/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
export function formatPlayUrl2(src, name) {
|
||||
var idx = name.indexOf('$');
|
||||
if (idx <= 0) {
|
||||
return formatPlayUrl(src, name);
|
||||
}
|
||||
return formatPlayUrl(src, name.substring(0, idx)) + name.substring(idx);
|
||||
}
|
||||
|
||||
export function stripHtmlTag(src) {
|
||||
return src
|
||||
.replace(/<\/?[^>]+(>|$)/g, '')
|
||||
.replace(/&.{1,5};/g, '')
|
||||
.replace(/\s{2,}/g, ' ');
|
||||
}
|
||||
|
||||
export function fixUrl(base, src) {
|
||||
try {
|
||||
if (src.startsWith('//')) {
|
||||
let parse = new URL(base);
|
||||
let host = src.substring(2, src.indexOf('/', 2));
|
||||
if (!host.includes('.')) {
|
||||
src = parse.protocol + '://' + parse.host + src.substring(1);
|
||||
} else {
|
||||
src = parse.protocol + ':' + src;
|
||||
}
|
||||
} else if (!src.includes('://')) {
|
||||
let parse = new URL(base);
|
||||
src = parse.protocol + '://' + parse.host + src;
|
||||
}
|
||||
} catch (error) {}
|
||||
return src;
|
||||
}
|
||||
|
||||
export function jsonParse(input, json) {
|
||||
try {
|
||||
let url = json.url || '';
|
||||
if (url.startsWith('//')) {
|
||||
url = 'https:' + url;
|
||||
}
|
||||
if (!url.startsWith('http')) {
|
||||
return {};
|
||||
}
|
||||
let headers = json['headers'] || {};
|
||||
let ua = (json['user-agent'] || '').trim();
|
||||
if (ua.length > 0) {
|
||||
headers['User-Agent'] = ua;
|
||||
}
|
||||
let referer = (json['referer'] || '').trim();
|
||||
if (referer.length > 0) {
|
||||
headers['Referer'] = referer;
|
||||
}
|
||||
return {
|
||||
header: headers,
|
||||
url: url,
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
return {};
|
||||
}
|
255
cat/tjs/lib/nivid_object.js
Normal file
255
cat/tjs/lib/nivid_object.js
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
* @File : nivid_object.js
|
||||
* @Author : jade
|
||||
* @Date : 2023/12/20 9:50
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
import {Crypto} from "./cat.js";
|
||||
|
||||
let DesKey = "diao.com"
|
||||
|
||||
class ChannelResponse {
|
||||
// classes
|
||||
constructor() {
|
||||
this.channelMsg = ""
|
||||
this.channelStatus = 0
|
||||
this.channelList = []
|
||||
this.channelFilters = {}
|
||||
}
|
||||
|
||||
fromJsonString(json_str, remove18ChannelCode = 0) {
|
||||
let json_dic = JSON.parse(json_str)
|
||||
this.channelMsg = json_dic.msg
|
||||
this.channelStatus = json_dic.status
|
||||
let channel_list = []
|
||||
for (const channel_info of json_dic.list) {
|
||||
let new_channel_info = new ChannelInfo()
|
||||
switch (remove18ChannelCode) {
|
||||
case 0:
|
||||
new_channel_info.fromJson(channel_info)
|
||||
channel_list.push(new_channel_info)
|
||||
break
|
||||
case 1:
|
||||
if (channel_info.channelName !== "午夜场" && channel_info.channelName !== "午夜直播") {
|
||||
new_channel_info.fromJson(channel_info)
|
||||
channel_list.push(new_channel_info)
|
||||
}
|
||||
break
|
||||
case 2:
|
||||
if (channel_info.channelName === "午夜场" || channel_info.channelName === "午夜直播") {
|
||||
new_channel_info.fromJson(channel_info)
|
||||
channel_list.push(new_channel_info)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
this.channelList = channel_list
|
||||
this.channelFilters = json_dic.filter
|
||||
}
|
||||
|
||||
setChannelFilters(filter_str) {
|
||||
this.channelFilters = JSON.parse(filter_str)
|
||||
}
|
||||
|
||||
getValues(typeList, name_key, id_key) {
|
||||
let values = []
|
||||
values.push({"n": "全部", "v": "0"})
|
||||
for (const obj of typeList) {
|
||||
values.push({"n": obj[name_key], "v": obj[id_key].toString()})
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
getFilters() {
|
||||
let filters = {}
|
||||
for (const channel_info of this.channelList) {
|
||||
filters[channel_info.channelId] = []
|
||||
let sortMapList = this.channelFilters["sortsMap"][parseInt(channel_info.channelId)]
|
||||
let sortValues = this.getValues(sortMapList, "title", "id")
|
||||
filters[channel_info.channelId].push({"key": "1", "name": "排序", "value": sortValues})
|
||||
let typeMapList = this.channelFilters["typesMap"][parseInt(channel_info.channelId)]
|
||||
let typeValues = this.getValues(typeMapList, "showTypeName", "showTypeId")
|
||||
filters[channel_info.channelId].push({"key": "2", "name": "类型", "value": typeValues})
|
||||
let areaValues = this.getValues(this.channelFilters["regions"], "regionName", "regionId")
|
||||
filters[channel_info.channelId].push({"key": "3", "name": "地区", "value": areaValues})
|
||||
let langValues = this.getValues(this.channelFilters["langs"], "langName", "langId")
|
||||
filters[channel_info.channelId].push({"key": "4", "name": "语言", "value": langValues})
|
||||
let yearValues = this.getValues(this.channelFilters["yearRanges"], "name", "code")
|
||||
filters[channel_info.channelId].push({"key": "5", "name": "年份", "value": yearValues})
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
getChannelFilters() {
|
||||
return this.channelFilters
|
||||
}
|
||||
|
||||
|
||||
|
||||
getChannelMsg() {
|
||||
return this.channelMsg
|
||||
}
|
||||
|
||||
getChannelStatus() {
|
||||
return this.channelStatus
|
||||
}
|
||||
|
||||
getChannelList() {
|
||||
return this.channelList
|
||||
}
|
||||
|
||||
getClassList() {
|
||||
let classes = []
|
||||
for (const channel_info of this.channelList) {
|
||||
classes.push({"type_id": channel_info.channelId, "type_name": channel_info.channelName})
|
||||
}
|
||||
return classes
|
||||
}
|
||||
|
||||
|
||||
async save() {
|
||||
await local.set("niba", "niba_channel", this.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.channelMsg = ""
|
||||
this.channelStatus = 0
|
||||
this.channelList = []
|
||||
}
|
||||
|
||||
async clearCache() {
|
||||
this.clear()
|
||||
await local.set("niba", "niba_channel", "{}");
|
||||
}
|
||||
|
||||
toString() {
|
||||
const params = {
|
||||
msg: this.getChannelMsg(),
|
||||
status: this.getChannelStatus(),
|
||||
list: this.getChannelList(),
|
||||
filter: this.getChannelFilters()
|
||||
};
|
||||
return JSON.stringify(params);
|
||||
}
|
||||
}
|
||||
|
||||
async function getChannelCache() {
|
||||
return await local.get("niba", "niba_channel");
|
||||
}
|
||||
|
||||
class ChannelInfo {
|
||||
constructor() {
|
||||
this.channelId = 0
|
||||
this.channelName = ""
|
||||
}
|
||||
|
||||
fromJsonString(json_str) {
|
||||
let json_dic = JSON.parse(json_str)
|
||||
this.channelId = json_dic.channelId
|
||||
this.channelName = json_dic.channelName
|
||||
}
|
||||
|
||||
fromJson(json) {
|
||||
this.channelId = json.channelId
|
||||
this.channelName = json.channelName
|
||||
}
|
||||
|
||||
getChannelName() {
|
||||
return this.channelName
|
||||
}
|
||||
|
||||
getChannelId() {
|
||||
return this.channelId
|
||||
}
|
||||
}
|
||||
|
||||
function isNumeric(str) {
|
||||
return !isNaN(parseInt(str));
|
||||
}
|
||||
|
||||
function getVod(video_dic_list, play_foramt_list, showIdCode) {
|
||||
let episode_list = [], episode_str_list = [];
|
||||
for (const video_dic of video_dic_list) {
|
||||
let video_name = ""
|
||||
if (isNumeric((video_dic['episodeName']))) {
|
||||
video_name = "第" + video_dic["episodeName"] + "集"
|
||||
} else {
|
||||
video_name = video_dic["episodeName"]
|
||||
}
|
||||
episode_list.push(video_name + "$" + video_dic["playIdCode"] + "@" + showIdCode);
|
||||
}
|
||||
|
||||
for (let index = 0; index < play_foramt_list.length; index++) {
|
||||
episode_str_list.push(episode_list.join("#"));
|
||||
}
|
||||
return {
|
||||
vod_play_url: episode_str_list.join("$$$"),
|
||||
vod_play_from: play_foramt_list.map(item => item).join("$$$"),
|
||||
};
|
||||
}
|
||||
|
||||
function getHeader() {
|
||||
return {
|
||||
"User-Agent": "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",
|
||||
"Referer": "https://m.nivod.tv/",
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function md5(text) {
|
||||
return Crypto.MD5(text).toString()
|
||||
}
|
||||
|
||||
//加密
|
||||
async function createSign(body = null) {
|
||||
let params = {
|
||||
"_ts": Date.now(), "app_version": "1.0",
|
||||
"platform": "3", "market_id": "web_nivod",
|
||||
"device_code": "web", "versioncode": 1,
|
||||
"oid": "8ca275aa5e12ba504b266d4c70d95d77a0c2eac5726198ea"
|
||||
}
|
||||
/**
|
||||
* __QUERY::_ts=1702973558399&app_version=1.0&device_code=web&market_id=web_nivod&oid=8ca275aa5e12ba504b266d4c70d95d77a0c2eac5726198ea&platform=3&versioncode=1&__BODY::__KEY::2x_Give_it_a_shot
|
||||
*/
|
||||
let params_list = []
|
||||
for (const key of Object.keys(params).sort()) {
|
||||
params_list.push(`${key}=${params[key]}`)
|
||||
}
|
||||
let body_str = "&__BODY::"
|
||||
if (body !== null) {
|
||||
let body_list = []
|
||||
for (const key of Object.keys(body).sort()) {
|
||||
body_list.push(`${key}=${body[key]}`)
|
||||
}
|
||||
body_str = body_str + body_list.join("&") + "&"
|
||||
}
|
||||
|
||||
|
||||
let params_str = "__QUERY::" + params_list.join("&") + body_str + "__KEY::2x_Give_it_a_shot"
|
||||
let sign_code = md5(params_str)
|
||||
params_list.push(`sign=${sign_code}`)
|
||||
return "?" + params_list.join("&")
|
||||
|
||||
}
|
||||
|
||||
//解密
|
||||
function desDecrypt(content) {
|
||||
// 定义密钥
|
||||
const key = Crypto.enc.Utf8.parse(DesKey); // 密钥需要进行字节数转换
|
||||
/*
|
||||
const encrypted = Crypto.DES.encrypt(content, key, {
|
||||
mode: Crypto.mode.ECB, // 使用ECB模式
|
||||
padding: Crypto.pad.Pkcs7, // 使用Pkcs7填充
|
||||
}).ciphertext.toString();
|
||||
*/
|
||||
return Crypto.DES.decrypt({ciphertext: Crypto.enc.Hex.parse(content)}, key, {
|
||||
mode: Crypto.mode.ECB,
|
||||
padding: Crypto.pad.Pkcs7,
|
||||
}).toString(Crypto.enc.Utf8);
|
||||
}
|
||||
|
||||
export {getChannelCache, desDecrypt, createSign, ChannelResponse, getHeader, getVod};
|
146
cat/tjs/lib/pipiXiaObject.js
Normal file
146
cat/tjs/lib/pipiXiaObject.js
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* @File : pipiXiaObject.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/2/4 14:33
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
const UID = "DCC147D11943AF75"
|
||||
const chrsz = 8;
|
||||
const hexcase = 0;
|
||||
|
||||
function hex_md5(s) {
|
||||
return binl2hex(core_md5(str2binl(s), s.length * chrsz))
|
||||
}
|
||||
function str2binl(str) {
|
||||
var bin = Array();
|
||||
var mask = (1 << chrsz) - 1;
|
||||
for (var i = 0; i < str.length * chrsz; i += chrsz)
|
||||
bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (i % 32);
|
||||
return bin
|
||||
}
|
||||
function core_md5(x, len) {
|
||||
x[len >> 5] |= 0x80 << ((len) % 32);
|
||||
x[(((len + 64) >>> 9) << 4) + 14] = len;
|
||||
var a = 1732584193;
|
||||
var b = -271733879;
|
||||
var c = -1732584194;
|
||||
var d = 271733878;
|
||||
for (var i = 0; i < x.length; i += 16) {
|
||||
var olda = a;
|
||||
var oldb = b;
|
||||
var oldc = c;
|
||||
var oldd = d;
|
||||
a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
|
||||
d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
|
||||
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
|
||||
b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
||||
a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
|
||||
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
||||
c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
||||
b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
|
||||
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
||||
d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
||||
c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
|
||||
b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
||||
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
||||
d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
|
||||
c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
||||
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
||||
a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
|
||||
d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
||||
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
|
||||
b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
|
||||
a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
|
||||
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
|
||||
c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
|
||||
b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
|
||||
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
|
||||
d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
||||
c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
|
||||
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
||||
a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
||||
d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
|
||||
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
||||
b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
||||
a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
|
||||
d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
||||
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
||||
b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
|
||||
a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
||||
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
||||
c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
|
||||
b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
||||
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
|
||||
d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
|
||||
c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
|
||||
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
|
||||
a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
|
||||
d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
|
||||
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
|
||||
b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
|
||||
a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
|
||||
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
||||
c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
||||
b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
|
||||
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
||||
d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
||||
c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
|
||||
b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
||||
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
||||
d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
|
||||
c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
||||
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
||||
a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
|
||||
d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
||||
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
|
||||
b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
|
||||
a = safe_add(a, olda);
|
||||
b = safe_add(b, oldb);
|
||||
c = safe_add(c, oldc);
|
||||
d = safe_add(d, oldd)
|
||||
}
|
||||
return Array(a, b, c, d)
|
||||
}
|
||||
function md5_gg(a, b, c, d, x, s, t) {
|
||||
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t)
|
||||
}
|
||||
|
||||
function md5_ff(a, b, c, d, x, s, t) {
|
||||
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t)
|
||||
}
|
||||
|
||||
function md5_hh(a, b, c, d, x, s, t) {
|
||||
return md5_cmn(b ^ c ^ d, a, b, x, s, t)
|
||||
}
|
||||
function md5_ii(a, b, c, d, x, s, t) {
|
||||
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t)
|
||||
}
|
||||
function md5_cmn(q, a, b, x, s, t) {
|
||||
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b)
|
||||
}
|
||||
function safe_add(x, y) {
|
||||
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
|
||||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xFFFF)
|
||||
}
|
||||
function bit_rol(num, cnt) {
|
||||
return (num << cnt) | (num >>> (32 - cnt))
|
||||
}
|
||||
function binl2hex(binarray) {
|
||||
let hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
|
||||
let str = "";
|
||||
for (let i = 0; i < binarray.length * 4; i++) {
|
||||
str += hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8 + 4)) & 0xF) + hex_tab.charAt((binarray[i >> 2] >> ((i % 4) * 8)) & 0xF)
|
||||
}
|
||||
return str
|
||||
}
|
||||
function pipixiaMd5(date_time) {
|
||||
return hex_md5('DS' + date_time + UID)
|
||||
}
|
||||
export {pipixiaMd5}
|
||||
|
||||
|
||||
// let key = pipixiaMd5(Math.floor(new Date() / 1000),"DCC147D11943AF75")
|
||||
// let x = 0
|
66
cat/tjs/lib/quark.js
Normal file
66
cat/tjs/lib/quark.js
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* File: h:\PycharmProjects\Github\TVSpider\lib\quark.js
|
||||
* Project: h:\PycharmProjects\Github\TVSpider
|
||||
* Created Date: Monday, May 20th 2024, 4:54:26 pm
|
||||
* Author: jade
|
||||
* -----
|
||||
* Last Modified: Tue May 21 2024
|
||||
* Modified By: jade
|
||||
* -----
|
||||
* Copyright (c) 2024 samples
|
||||
* ------------------------------------
|
||||
* Javascript will save your soul!
|
||||
*/
|
||||
|
||||
import {JadeLogging} from "./log.js";
|
||||
import {Quark} from "./quark_api.js"
|
||||
const quarkName = "夸克云盘"
|
||||
const JadeLog = new JadeLogging(quarkName)
|
||||
const quark = new Quark()
|
||||
async function initQuark(cookie) {
|
||||
quark.initQuark(cookie)
|
||||
await JadeLog.info(`夸克云盘初始化完成,Cookie为:${cookie}`, true)
|
||||
}
|
||||
async function detailContentQuark(share_url_list, type_name = "电影") {
|
||||
try {
|
||||
let video_items = [], sub_items = []
|
||||
for (let i=0;i<share_url_list.length;i++){
|
||||
let share_url = share_url_list[i]
|
||||
await quark.getFilesByShareUrl(i+1,share_url,video_items,sub_items)
|
||||
}
|
||||
if (video_items.length > 0) {
|
||||
await JadeLog.info(`获取播放链接成功,分享链接为:${share_url_list.join("\t")}`)
|
||||
} else {
|
||||
await JadeLog.error(`获取播放链接失败,检查分享链接为:${share_url_list.join("\t")}`)
|
||||
}
|
||||
return {"video_items":video_items,"sub_items":sub_items}
|
||||
} catch (e) {
|
||||
await JadeLog.error('获取夸克视频失败,失败原因为:' + e.message + ' 行数为:' + e.lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
function getQuarkPlayFormatList(){
|
||||
return quark.getPlayFormatList()
|
||||
}
|
||||
|
||||
async function playContentQuark(flag, id, flags){
|
||||
|
||||
let id_list = id.split("++")
|
||||
let shareId = id_list[2],stoken = id_list[3], fileId = id_list[0], fileToken = id_list[1]
|
||||
let playUrl = ""
|
||||
if (flag.indexOf("原画") > -1){
|
||||
playUrl = (await quark.getDownload(shareId, stoken, fileId, fileToken,true)).download_url
|
||||
}else{
|
||||
playUrl = await quark.getLiveTranscoding(shareId, stoken, fileId, fileToken,flag)
|
||||
}
|
||||
return playUrl
|
||||
}
|
||||
|
||||
function getQuarkHeaders(){
|
||||
let headers = quark.getHeaders()
|
||||
delete headers["Host"]
|
||||
return headers
|
||||
}
|
||||
|
||||
export {initQuark,detailContentQuark,playContentQuark,getQuarkPlayFormatList,quarkName,getQuarkHeaders}
|
||||
|
313
cat/tjs/lib/quark_api.js
Normal file
313
cat/tjs/lib/quark_api.js
Normal file
@ -0,0 +1,313 @@
|
||||
/**
|
||||
* File: /workspaces/TVSpider/lib/quark_api.js
|
||||
* Project: /workspaces/TVSpider
|
||||
* Created Date: Monday, May 20th 2024, 6:38:12 am
|
||||
* Author: jade
|
||||
* -----
|
||||
* Last Modified: Tue May 21 2024
|
||||
* Modified By: jade
|
||||
* -----
|
||||
* Copyright (c) 2024 samples
|
||||
* ------------------------------------
|
||||
* Javascript will save your soul!
|
||||
*/
|
||||
import {_,Crypto} from "../lib/cat.js";
|
||||
import * as Utils from "../lib/utils.js"
|
||||
import {Item} from "../lib/quark_object.js"
|
||||
|
||||
class Quark{
|
||||
constructor(){
|
||||
this.apiUrl = "https://drive-pc.quark.cn/1/clouddrive/"
|
||||
this.cookie = ""
|
||||
this.ckey = ""
|
||||
this.shareTokenCache = {}
|
||||
this.pr = "pr=ucpro&fr=pc"
|
||||
this.subtitleExts = ['.srt', '.ass', '.scc', '.stl', '.ttml'];
|
||||
this.saveFileIdCaches = {}
|
||||
this.saveDirId = null
|
||||
this.saveDirName = 'TV';
|
||||
this.isVip = false;
|
||||
|
||||
}
|
||||
async initQuark(cookie) {
|
||||
this.ckey = Crypto.enc.Hex.stringify(Crypto.MD5(cookie)).toString();
|
||||
this.cookie = cookie
|
||||
this.isVip = await this.getVip()
|
||||
}
|
||||
|
||||
getHeaders(){
|
||||
return {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch',
|
||||
'Referer': 'https://pan.quark.cn/',
|
||||
"Content-Type":"application/json",
|
||||
"Cookie":this.cookie,
|
||||
"Host":"drive-pc.quark.cn"
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
async api(url,data,retry,method){
|
||||
const leftRetry = retry || 3;
|
||||
let resp = await req(this.apiUrl + url,{
|
||||
method:method || "post",
|
||||
data:data,
|
||||
headers:this.getHeaders()
|
||||
})
|
||||
if (resp.headers['set-cookie']) {
|
||||
const puus = [resp.headers['set-cookie']].join(';;;').match(/__puus=([^;]+)/);
|
||||
if (puus) {
|
||||
if (this.cookie.match(/__puus=([^;]+)/)[1] != puus[1]) {
|
||||
this.cookie = this.cookie.replace(/__puus=[^;]+/, `__puus=${puus[1]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resp.code !== 200 && leftRetry > 0) {
|
||||
Utils.sleep(1)
|
||||
return await this.api(url, data, leftRetry - 1);
|
||||
}
|
||||
return JSON.parse(resp.content) || {};
|
||||
}
|
||||
|
||||
getShareData(url) {
|
||||
let regex = /https:\/\/pan\.quark\.cn\/s\/([^\\|#/]+)/;
|
||||
let matches = regex.exec(url);
|
||||
if (matches) {
|
||||
return {
|
||||
shareId: matches[1],
|
||||
folderId: '0',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getVip(){
|
||||
const listData = await this.api(`member?pr=ucpro&fr=pc&uc_param_str=&fetch_subscribe=true&_ch=home&fetch_identity=true`, null,null, 'get');
|
||||
if (listData.data.member_type==="EXP_SVIP"){
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
getPlayFormatList(){
|
||||
if(this.isVip){
|
||||
return ["4K","超清","高清","普画"]
|
||||
}else{
|
||||
return ["普画"]
|
||||
}
|
||||
}
|
||||
|
||||
getPlayFormtQuarkList(){
|
||||
if(this.isVip){
|
||||
return ["4k","2k","super","high","normal","low"]
|
||||
}{
|
||||
return ["low"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getShareToken(shareData) {
|
||||
if (!this.shareTokenCache[shareData.shareId]) {
|
||||
delete this.shareTokenCache[shareData.shareId];
|
||||
const shareToken = await this.api(`share/sharepage/token?${this.pr}`, {
|
||||
pwd_id: shareData.shareId,
|
||||
passcode: shareData.sharePwd || '',
|
||||
});
|
||||
if (shareToken.data && shareToken.data.stoken) {
|
||||
this.shareTokenCache[shareData.shareId] = shareToken.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
async listFile (shareIndex,shareData,videos,subtitles,shareId, folderId, page) {
|
||||
const prePage = 200;
|
||||
page = page || 1;
|
||||
const listData = await this.api(`share/sharepage/detail?${this.pr}&pwd_id=${shareId}&stoken=${encodeURIComponent(this.shareTokenCache[shareId].stoken)}&pdir_fid=${folderId}&force=0&_page=${page}&_size=${prePage}&_sort=file_type:asc,file_name:asc`, null,null, 'get');
|
||||
if (!listData.data) return [];
|
||||
const items = listData.data.list;
|
||||
if (!items) return [];
|
||||
const subDir = [];
|
||||
for (const item of items) {
|
||||
if (item.dir === true) {
|
||||
subDir.push(item);
|
||||
} else if (item.file === true && item.obj_category === 'video') {
|
||||
if (item.size < 1024 * 1024 * 5) continue;
|
||||
item.stoken = this.shareTokenCache[shareData.shareId].stoken;
|
||||
videos.push(Item.objectFrom(item,shareData.shareId,shareIndex));
|
||||
} else if (item.type === 'file' && this.subtitleExts.some((x) => item.file_name.endsWith(x))) {
|
||||
subtitles.push(Item.objectFrom(item,shareData,shareIndex));
|
||||
}
|
||||
}
|
||||
if (page < Math.ceil(listData.metadata._total / prePage)) {
|
||||
const nextItems = await this.listFile(shareIndex,shareData.shareId,videos,subtitles,shareId, folderId, page + 1);
|
||||
for (const item of nextItems) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
for (const dir of subDir) {
|
||||
const subItems = await this.listFile(shareIndex,shareData,videos,subtitles,shareId, dir.fid);
|
||||
for (const item of subItems) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
};
|
||||
|
||||
findBestLCS(mainItem, targetItems) {
|
||||
const results = [];
|
||||
let bestMatchIndex = 0;
|
||||
for (let i = 0; i < targetItems.length; i++) {
|
||||
const currentLCS = Utils.lcs(mainItem.name, targetItems[i].name);
|
||||
results.push({ target: targetItems[i], lcs: currentLCS });
|
||||
if (currentLCS.length > results[bestMatchIndex].lcs.length) {
|
||||
bestMatchIndex = i;
|
||||
}
|
||||
}
|
||||
const bestMatch = results[bestMatchIndex];
|
||||
return { allLCS: results, bestMatch: bestMatch, bestMatchIndex: bestMatchIndex };
|
||||
}
|
||||
|
||||
async getFilesByShareUrl(shareIndex,shareInfo,videos,subtitles){
|
||||
const shareData = typeof shareInfo === 'string' ? this.getShareData(shareInfo) : shareInfo;
|
||||
if (!shareData) return [];
|
||||
await this.getShareToken(shareData);
|
||||
if (!this.shareTokenCache[shareData.shareId]) return [];
|
||||
await this.listFile(shareIndex,shareData,videos,subtitles,shareData.shareId, shareData.folderId);
|
||||
if (subtitles.length > 0) {
|
||||
videos.forEach((item) => {
|
||||
var matchSubtitle = this.findBestLCS(item, subtitles);
|
||||
if (matchSubtitle.bestMatch) {
|
||||
item.subtitle = matchSubtitle.bestMatch.target;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clean(){
|
||||
const saves = Object.keys(this.saveFileIdCaches);
|
||||
for (const save of saves) {
|
||||
delete this.saveFileIdCaches[save];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async clearSaveDir() {
|
||||
const listData = await this.api(`file/sort?${this.pr}&pdir_fid=${this.saveDirId}&_page=1&_size=200&_sort=file_type:asc,updated_at:desc`, {}, {}, 'get');
|
||||
if (listData.data && listData.data.list && listData.data.list.length > 0) {
|
||||
await this.api(`file/delete?${this.pr}`, {
|
||||
action_type: 2,
|
||||
filelist: listData.data.list.map((v) => v.fid),
|
||||
exclude_fids: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async createSaveDir(clean) {
|
||||
if (this.saveDirId) {
|
||||
// 删除所有子文件
|
||||
if (clean) await this.clearSaveDir();
|
||||
return;
|
||||
}
|
||||
const listData = await this.api(`file/sort?${this.pr}&pdir_fid=0&_page=1&_size=200&_sort=file_type:asc,updated_at:desc`, {}, {}, 'get');
|
||||
if (listData.data && listData.data.list)
|
||||
for (const item of listData.data.list) {
|
||||
if (item.file_name === this.saveDirName) {
|
||||
this.saveDirId = item.fid;
|
||||
await this.clearSaveDir();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!this.saveDirId) {
|
||||
const create = await this.api(`file?${this.pr}`, {
|
||||
pdir_fid: '0',
|
||||
file_name: this.saveDirName,
|
||||
dir_path: '',
|
||||
dir_init_lock: false,
|
||||
});
|
||||
if (create.data && create.data.fid) {
|
||||
this.saveDirId = create.data.fid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async save(shareId, stoken, fileId, fileToken, clean) {
|
||||
await this.createSaveDir(clean);
|
||||
if (clean) {
|
||||
this.clean()
|
||||
}
|
||||
if (!this.saveDirId) return null;
|
||||
if (!stoken) {
|
||||
await this.getShareToken({
|
||||
shareId: shareId,
|
||||
});
|
||||
if (!this.shareTokenCache[shareId]) return null;
|
||||
}
|
||||
const saveResult = await this.api(`share/sharepage/save?${this.pr}`, {
|
||||
fid_list: [fileId],
|
||||
fid_token_list: [fileToken],
|
||||
to_pdir_fid: this.saveDirId,
|
||||
pwd_id: shareId,
|
||||
stoken: stoken || this.shareTokenCache[shareId].stoken,
|
||||
pdir_fid: '0',
|
||||
scene: 'link',
|
||||
});
|
||||
if (saveResult.data && saveResult.data.task_id) {
|
||||
let retry = 0;
|
||||
// wait task finish
|
||||
while (true) {
|
||||
const taskResult = await this.api(`task?${this.pr}&task_id=${saveResult.data.task_id}&retry_index=${retry}`, {}, {}, 'get');
|
||||
if (taskResult.data && taskResult.data.save_as && taskResult.data.save_as.save_as_top_fids && taskResult.data.save_as.save_as_top_fids.length > 0) {
|
||||
return taskResult.data.save_as.save_as_top_fids[0];
|
||||
}
|
||||
retry++;
|
||||
if (retry > 2) break;
|
||||
Utils.sleep(1);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
async getLiveTranscoding(shareId, stoken, fileId, fileToken,flag) {
|
||||
if (!this.saveFileIdCaches[fileId]) {
|
||||
const saveFileId = await this.save(shareId, stoken, fileId, fileToken, true);
|
||||
if (!saveFileId) return null;
|
||||
this.saveFileIdCaches[fileId] = saveFileId;
|
||||
}
|
||||
const transcoding = await this.api(`file/v2/play?${this.pr}`, {
|
||||
fid: this.saveFileIdCaches[fileId],
|
||||
resolutions: 'normal,low,high,super,2k,4k',
|
||||
supports: 'fmp4',
|
||||
});
|
||||
|
||||
|
||||
if (transcoding.data && transcoding.data.video_list) {
|
||||
let flag_id = flag.split("-").slice(-1)[0]
|
||||
let index = Utils.findAllIndexes(this.getPlayFormatList(),flag_id);
|
||||
let quarkFormat = this.getPlayFormtQuarkList()[index]
|
||||
for (const video of transcoding.data.video_list){
|
||||
if (video.resolution === quarkFormat){
|
||||
return video.video_info.url
|
||||
}
|
||||
}
|
||||
return transcoding.data.video_list[index].video_info.url
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getDownload(shareId, stoken, fileId, fileToken, clean) {
|
||||
if (!this.saveFileIdCaches[fileId]) {
|
||||
const saveFileId = await this.save(shareId, stoken, fileId, fileToken, clean);
|
||||
if (!saveFileId) return null;
|
||||
this.saveFileIdCaches[fileId] = saveFileId;
|
||||
}
|
||||
const down = await this.api(`file/download?${this.pr}&uc_param_str=`, {
|
||||
fids: [this.saveFileIdCaches[fileId]],
|
||||
});
|
||||
if (down.data) {
|
||||
return down.data[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
export {Quark}
|
98
cat/tjs/lib/quark_object.js
Normal file
98
cat/tjs/lib/quark_object.js
Normal file
@ -0,0 +1,98 @@
|
||||
/**
|
||||
* File: h:\PycharmProjects\Github\TVSpider\lib\quark_object.js
|
||||
* Project: h:\PycharmProjects\Github\TVSpider
|
||||
* Created Date: Monday, May 20th 2024, 5:26:45 pm
|
||||
* Author: jade
|
||||
* -----
|
||||
* Last Modified: Tue May 21 2024
|
||||
* Modified By: jade
|
||||
* -----
|
||||
* Copyright (c) 2024 samples
|
||||
* ------------------------------------
|
||||
* Javascript will save your soul!
|
||||
*/
|
||||
import {_} from "../lib/cat.js";
|
||||
import * as Utils from "./utils.js";
|
||||
|
||||
|
||||
class Item {
|
||||
constructor() {
|
||||
this.fileId = "";
|
||||
this.shareId = ""
|
||||
this.shareToken = "";
|
||||
this.shareFileToken = ""
|
||||
this.seriesId = ""
|
||||
this.name = "";
|
||||
this.type = "";
|
||||
this.formatType = "";
|
||||
this.size = "";
|
||||
this.parent = "";
|
||||
this.shareData = null;
|
||||
this.shareIndex = 0;
|
||||
this.lastUpdateAt = 0
|
||||
}
|
||||
|
||||
static objectFrom(item_json, shareId,shareIndex) {
|
||||
let item = new Item();
|
||||
item.fileId = typeof item_json.fid == undefined ? "" : item_json.fid;
|
||||
item.shareId = shareId
|
||||
item.shareToken = typeof item_json.stoken == undefined ? "" : item_json.stoken;
|
||||
item.shareFileToken = typeof item_json.share_fid_token == undefined ? "" : item_json.share_fid_token;
|
||||
item.seriesId = typeof item_json.series_id == undefined? "":item_json.series_id
|
||||
item.name = typeof item_json.file_name == undefined ? "" : item_json.file_name;
|
||||
item.type = typeof item_json.obj_category == undefined ? "" : item_json.obj_category;
|
||||
item.formatType = typeof item_json.format_type == undefined? "" : item_json.format_type;
|
||||
item.size = typeof item_json.size == undefined ? "" : item_json.size;
|
||||
item.parent = typeof item_json.pdir_fid == undefined ? "" : item_json.pdir_fid;
|
||||
item.lastUpdateAt = typeof item_json.last_update_at == undefined ? "" : item_json.last_update_at
|
||||
item.shareIndex = shareIndex
|
||||
return item;
|
||||
}
|
||||
|
||||
getFileExtension(){
|
||||
return this.name.split(".").slice(-1)[0]
|
||||
}
|
||||
|
||||
getFileId() {
|
||||
return _.isEmpty(this.fileId) ? "" : this.fileId
|
||||
}
|
||||
|
||||
getName() {
|
||||
return _.isEmpty(this.name) ? "" : this.name;
|
||||
}
|
||||
|
||||
getParent() {
|
||||
return _.isEmpty(this.parent) ? "" : "[" + this.parent + "]";
|
||||
}
|
||||
|
||||
getSize() {
|
||||
return this.size === 0 ? "" : "[" + Utils.getSize(this.size) + "]";
|
||||
}
|
||||
|
||||
getShareIndex(){
|
||||
return this.shareIndex
|
||||
}
|
||||
getDisplayName(type_name) {
|
||||
let name = this.getName();
|
||||
if (type_name === "电视剧") {
|
||||
let replaceNameList = ["4k", "4K"]
|
||||
name = name.replaceAll("." + this.getFileExtension(), "")
|
||||
name = name.replaceAll(" ", "").replaceAll(" ", "")
|
||||
for (const replaceName of replaceNameList) {
|
||||
name = name.replaceAll(replaceName, "")
|
||||
}
|
||||
name = Utils.getStrByRegexDefault(/\.S01E(.*?)\./, name)
|
||||
const numbers = name.match(/\d+/g);
|
||||
if (!_.isEmpty(numbers) && numbers.length > 0) {
|
||||
name = numbers[0]
|
||||
}
|
||||
}
|
||||
|
||||
return name + " " + this.getSize();
|
||||
}
|
||||
|
||||
getEpisodeUrl(type_name){
|
||||
return this.getDisplayName(type_name) + "$" + this.getFileId() + "++" + this.shareFileToken + "++" + this.shareId + "++" + this.shareToken
|
||||
}
|
||||
}
|
||||
export {Item}
|
170
cat/tjs/lib/shift-jis.js
Normal file
170
cat/tjs/lib/shift-jis.js
Normal file
@ -0,0 +1,170 @@
|
||||
import { inRange, decoderError, encoderError, floor, isASCIICodePoint, isASCIIByte,
|
||||
end_of_stream, finished } from './text_decoder_utils.js'
|
||||
import index, { indexCodePointFor, indexShiftJISPointerFor } from './text_decoder_indexes.js'
|
||||
|
||||
|
||||
// 13.3 Shift_JIS
|
||||
|
||||
// 13.3.1 Shift_JIS decoder
|
||||
/**
|
||||
* @constructor
|
||||
* @implements {Decoder}
|
||||
* @param {{fatal: boolean}} options
|
||||
*/
|
||||
export class ShiftJISDecoder {
|
||||
constructor(options) {
|
||||
const { fatal } = options
|
||||
this.fatal = fatal
|
||||
// Shift_JIS's decoder has an associated Shift_JIS lead (initially
|
||||
// 0x00).
|
||||
this.Shift_JIS_lead = 0x00
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream and Shift_JIS lead is not 0x00,
|
||||
// set Shift_JIS lead to 0x00 and return error.
|
||||
if (bite === end_of_stream && this.Shift_JIS_lead !== 0x00) {
|
||||
this.Shift_JIS_lead = 0x00
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 2. If byte is end-of-stream and Shift_JIS lead is 0x00,
|
||||
// return finished.
|
||||
if (bite === end_of_stream && this.Shift_JIS_lead === 0x00)
|
||||
return finished
|
||||
|
||||
// 3. If Shift_JIS lead is not 0x00, let lead be Shift_JIS lead,
|
||||
// let pointer be null, set Shift_JIS lead to 0x00, and then run
|
||||
// these substeps:
|
||||
if (this.Shift_JIS_lead !== 0x00) {
|
||||
var lead = this.Shift_JIS_lead
|
||||
var pointer = null
|
||||
this.Shift_JIS_lead = 0x00
|
||||
|
||||
// 1. Let offset be 0x40, if byte is less than 0x7F, and 0x41
|
||||
// otherwise.
|
||||
var offset = (bite < 0x7F) ? 0x40 : 0x41
|
||||
|
||||
// 2. Let lead offset be 0x81, if lead is less than 0xA0, and
|
||||
// 0xC1 otherwise.
|
||||
var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1
|
||||
|
||||
// 3. If byte is in the range 0x40 to 0x7E, inclusive, or 0x80
|
||||
// to 0xFC, inclusive, set pointer to (lead − lead offset) ×
|
||||
// 188 + byte − offset.
|
||||
if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC))
|
||||
pointer = (lead - lead_offset) * 188 + bite - offset
|
||||
|
||||
// 4. If pointer is in the range 8836 to 10715, inclusive,
|
||||
// return a code point whose value is 0xE000 − 8836 + pointer.
|
||||
if (inRange(pointer, 8836, 10715))
|
||||
return 0xE000 - 8836 + pointer
|
||||
|
||||
// 5. Let code point be null, if pointer is null, and the
|
||||
// index code point for pointer in index jis0208 otherwise.
|
||||
var code_point = (pointer === null) ? null :
|
||||
indexCodePointFor(pointer, index('jis0208'))
|
||||
|
||||
// 6. If code point is null and byte is an ASCII byte, prepend
|
||||
// byte to stream.
|
||||
if (code_point === null && isASCIIByte(bite))
|
||||
stream.prepend(bite)
|
||||
|
||||
// 7. If code point is null, return error.
|
||||
if (code_point === null)
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 8. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
|
||||
// 4. If byte is an ASCII byte or 0x80, return a code point
|
||||
// whose value is byte.
|
||||
if (isASCIIByte(bite) || bite === 0x80)
|
||||
return bite
|
||||
|
||||
// 5. If byte is in the range 0xA1 to 0xDF, inclusive, return a
|
||||
// code point whose value is 0xFF61 − 0xA1 + byte.
|
||||
if (inRange(bite, 0xA1, 0xDF))
|
||||
return 0xFF61 - 0xA1 + bite
|
||||
|
||||
// 6. If byte is in the range 0x81 to 0x9F, inclusive, or 0xE0
|
||||
// to 0xFC, inclusive, set Shift_JIS lead to byte and return
|
||||
// continue.
|
||||
if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) {
|
||||
this.Shift_JIS_lead = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 7. Return error.
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
}
|
||||
|
||||
// 13.3.2 Shift_JIS encoder
|
||||
/**
|
||||
* @constructor
|
||||
* @implements {Encoder}
|
||||
* @param {{fatal: boolean}} options
|
||||
*/
|
||||
export class ShiftJISEncoder {
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point or U+0080, return a
|
||||
// byte whose value is code point.
|
||||
if (isASCIICodePoint(code_point) || code_point === 0x0080)
|
||||
return code_point
|
||||
|
||||
// 3. If code point is U+00A5, return byte 0x5C.
|
||||
if (code_point === 0x00A5)
|
||||
return 0x5C
|
||||
|
||||
// 4. If code point is U+203E, return byte 0x7E.
|
||||
if (code_point === 0x203E)
|
||||
return 0x7E
|
||||
|
||||
// 5. If code point is in the range U+FF61 to U+FF9F, inclusive,
|
||||
// return a byte whose value is code point − 0xFF61 + 0xA1.
|
||||
if (inRange(code_point, 0xFF61, 0xFF9F))
|
||||
return code_point - 0xFF61 + 0xA1
|
||||
|
||||
// 6. If code point is U+2212, set it to U+FF0D.
|
||||
if (code_point === 0x2212)
|
||||
code_point = 0xFF0D
|
||||
|
||||
// 7. Let pointer be the index Shift_JIS pointer for code point.
|
||||
var pointer = indexShiftJISPointerFor(code_point)
|
||||
|
||||
// 8. If pointer is null, return error with code point.
|
||||
if (pointer === null)
|
||||
return encoderError(code_point)
|
||||
|
||||
// 9. Let lead be floor(pointer / 188).
|
||||
var lead = floor(pointer / 188)
|
||||
|
||||
// 10. Let lead offset be 0x81, if lead is less than 0x1F, and
|
||||
// 0xC1 otherwise.
|
||||
var lead_offset = (lead < 0x1F) ? 0x81 : 0xC1
|
||||
|
||||
// 11. Let trail be pointer % 188.
|
||||
var trail = pointer % 188
|
||||
|
||||
// 12. Let offset be 0x40, if trail is less than 0x3F, and 0x41
|
||||
// otherwise.
|
||||
var offset = (trail < 0x3F) ? 0x40 : 0x41
|
||||
|
||||
// 13. Return two bytes whose values are lead + lead offset and
|
||||
// trail + offset.
|
||||
return [lead + lead_offset, trail + offset]
|
||||
}
|
||||
}
|
53
cat/tjs/lib/similarity.js
Normal file
53
cat/tjs/lib/similarity.js
Normal file
@ -0,0 +1,53 @@
|
||||
function compareTwoStrings(first, second) {
|
||||
if ((first = first.replace(/\s+/g, "")) === (second = second.replace(/\s+/g, ""))) return 1;
|
||||
if (first.length < 2 || second.length < 2) return 0;
|
||||
var firstBigrams = new Map;
|
||||
for (let i = 0; i < first.length - 1; i++) {
|
||||
var bigram = first.substring(i, i + 2), count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) + 1 : 1;
|
||||
firstBigrams.set(bigram, count)
|
||||
}
|
||||
let intersectionSize = 0;
|
||||
for (let i = 0; i < second.length - 1; i++) {
|
||||
const bigram = second.substring(i, i + 2), count = firstBigrams.has(bigram) ? firstBigrams.get(bigram) : 0;
|
||||
0 < count && (firstBigrams.set(bigram, count - 1), intersectionSize++)
|
||||
}
|
||||
return 2 * intersectionSize / (first.length + second.length - 2)
|
||||
}
|
||||
|
||||
function findBestMatch(mainString, targetStrings) {
|
||||
var ratings = [];
|
||||
let bestMatchIndex = 0;
|
||||
for (let i = 0; i < targetStrings.length; i++) {
|
||||
var currentTargetString = targetStrings[i], currentRating = compareTwoStrings(mainString, currentTargetString);
|
||||
ratings.push({
|
||||
target: currentTargetString,
|
||||
rating: currentRating
|
||||
}), currentRating > ratings[bestMatchIndex].rating && (bestMatchIndex = i)
|
||||
}
|
||||
return {ratings: ratings, bestMatch: ratings[bestMatchIndex], bestMatchIndex: bestMatchIndex}
|
||||
}
|
||||
|
||||
function lcs(str1, str2) {
|
||||
if (!str1 || !str2) return {length: 0, sequence: "", offset: 0};
|
||||
for (var sequence = "", str1Length = str1.length, str2Length = str2.length, num = new Array(str1Length), maxlen = 0, lastSubsBegin = 0, i = 0; i < str1Length; i++) {
|
||||
for (var subArray = new Array(str2Length), j = 0; j < str2Length; j++) subArray[j] = 0;
|
||||
num[i] = subArray
|
||||
}
|
||||
for (var thisSubsBegin = null, i = 0; i < str1Length; i++) for (j = 0; j < str2Length; j++) str1[i] !== str2[j] ? num[i][j] = 0 : (num[i][j] = 0 === i || 0 === j ? 1 : 1 + num[i - 1][j - 1], num[i][j] > maxlen && (maxlen = num[i][j], lastSubsBegin === (thisSubsBegin = i - num[i][j] + 1) ? sequence += str1[i] : (lastSubsBegin = thisSubsBegin, sequence = "", sequence += str1.substr(lastSubsBegin, i + 1 - lastSubsBegin))));
|
||||
return {length: maxlen, sequence: sequence, offset: thisSubsBegin}
|
||||
}
|
||||
|
||||
function findBestLCS(mainString, targetStrings) {
|
||||
var results = [];
|
||||
let bestMatchIndex = 0;
|
||||
for (let i = 0; i < targetStrings.length; i++) {
|
||||
var currentTargetString = targetStrings[i], currentLCS = lcs(mainString, currentTargetString);
|
||||
results.push({
|
||||
target: currentTargetString,
|
||||
lcs: currentLCS
|
||||
}), currentLCS.length > results[bestMatchIndex].lcs.length && (bestMatchIndex = i)
|
||||
}
|
||||
return {allLCS: results, bestMatch: results[bestMatchIndex], bestMatchIndex: bestMatchIndex}
|
||||
}
|
||||
|
||||
export {compareTwoStrings, findBestMatch, findBestLCS};
|
86
cat/tjs/lib/single-byte.js
Normal file
86
cat/tjs/lib/single-byte.js
Normal file
@ -0,0 +1,86 @@
|
||||
import { end_of_stream, finished, isASCIIByte, decoderError, encoderError, isASCIICodePoint } from './text_decoder_utils.js'
|
||||
import { indexPointerFor } from './text_decoder_indexes.js'
|
||||
|
||||
//
|
||||
// 10. Legacy single-byte encodings
|
||||
//
|
||||
|
||||
// 10.1 single-byte decoder
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class SingleByteDecoder {
|
||||
/**
|
||||
* @param {!Array.<number>} index The encoding index.
|
||||
* @param {{fatal: boolean}} options
|
||||
*/
|
||||
constructor(index, options) {
|
||||
const { fatal } = options
|
||||
this.fatal = fatal
|
||||
this.index = index
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream, return finished.
|
||||
if (bite === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If byte is an ASCII byte, return a code point whose value
|
||||
// is byte.
|
||||
if (isASCIIByte(bite))
|
||||
return bite
|
||||
|
||||
// 3. Let code point be the index code point for byte − 0x80 in
|
||||
// index single-byte.
|
||||
var code_point = this.index[bite - 0x80]
|
||||
|
||||
// 4. If code point is null, return error.
|
||||
if (code_point === null)
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 5. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
}
|
||||
|
||||
// 10.2 single-byte encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class SingleByteEncoder {
|
||||
/**
|
||||
* @param {!Array.<?number>} index The encoding index.
|
||||
*/
|
||||
constructor(index) {
|
||||
this.index = index
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
* @return {(number|!Array.<number>)} Byte(s) to emit.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point, return a byte whose
|
||||
// value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 3. Let pointer be the index pointer for code point in index
|
||||
// single-byte.
|
||||
const pointer = indexPointerFor(code_point, this.index)
|
||||
|
||||
// 4. If pointer is null, return error with code point.
|
||||
if (pointer === null)
|
||||
encoderError(code_point)
|
||||
|
||||
// 5. Return a byte whose value is pointer + 0x80.
|
||||
return pointer + 0x80
|
||||
}
|
||||
}
|
119
cat/tjs/lib/table.js
Normal file
119
cat/tjs/lib/table.js
Normal file
@ -0,0 +1,119 @@
|
||||
import Encodings from './encodings.js'
|
||||
import { UTF8Decoder, UTF8Encoder } from './utf8.js'
|
||||
import { UTF16Decoder, UTF16Encoder } from './utf16.js'
|
||||
import { GB18030Decoder, GB18030Encoder } from './gb18030.js'
|
||||
import { Big5Decoder, Big5Encoder } from './big5.js'
|
||||
import { EUCJPDecoder, EUCJPEncoder } from './euc-jp.js'
|
||||
import { EUCKRDecoder, EUCKREncoder } from './euc-kr.js'
|
||||
import { ISO2022JPDecoder, ISO2022JPEncoder } from './iso-2022-jp.js'
|
||||
import { XUserDefinedDecoder, XUserDefinedEncoder } from './x-user-defined.js'
|
||||
import { ShiftJISDecoder, ShiftJISEncoder } from './shift-jis.js'
|
||||
import { SingleByteDecoder, SingleByteEncoder } from './single-byte.js'
|
||||
import index from './text_decoder_indexes.js';
|
||||
|
||||
// 5.2 Names and labels
|
||||
|
||||
// TODO: Define @typedef for Encoding: {name:string,labels:Array.<string>}
|
||||
// https://github.com/google/closure-compiler/issues/247
|
||||
|
||||
|
||||
// Label to encoding registry.
|
||||
/** @type {Object.<string,{name:string,labels:Array.<string>}>} */
|
||||
export const label_to_encoding = {}
|
||||
Encodings.forEach(({ encodings }) => {
|
||||
encodings.forEach((encoding) => {
|
||||
encoding.labels.forEach((label) => {
|
||||
label_to_encoding[label] = encoding
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Registry of of encoder/decoder factories, by encoding name.
|
||||
export const encoders = {
|
||||
'UTF-8'() { // 9.1 utf-8
|
||||
return new UTF8Encoder()
|
||||
},
|
||||
'GBK'(options) { // 11.1.2 gbk encoder;
|
||||
// gbk's encoder is gb18030's encoder with its gbk flag set.
|
||||
return new GB18030Encoder(options, true)
|
||||
},
|
||||
'gb18030'() {
|
||||
return new GB18030Encoder()
|
||||
},
|
||||
'Big5'() {
|
||||
return new Big5Encoder()
|
||||
},
|
||||
'EUC-JP'() {
|
||||
return new EUCJPEncoder()
|
||||
},
|
||||
'EUC-KR'() {
|
||||
return new EUCKREncoder()
|
||||
},
|
||||
'ISO-2022-JP'() {
|
||||
return new ISO2022JPEncoder()
|
||||
},
|
||||
'UTF-16BE'() { // 15.3 utf-16be
|
||||
return new UTF16Encoder(true)
|
||||
},
|
||||
'UTF-16LE'() { // 15.4 utf-16le
|
||||
return new UTF16Encoder()
|
||||
},
|
||||
'x-user-defined'() {
|
||||
return new XUserDefinedEncoder()
|
||||
},
|
||||
'Shift_JIS'() {
|
||||
return new ShiftJISEncoder()
|
||||
},
|
||||
}
|
||||
|
||||
/** @type {Object.<string, function({fatal:boolean}): Decoder>} */
|
||||
export const decoders = {
|
||||
'UTF-8'(options) { // 9.1.1 utf-8 decoder
|
||||
return new UTF8Decoder(options)
|
||||
},
|
||||
'GBK'(options) { // 11.1.1 gbk decoder; gbk's decoder is gb18030's decoder.
|
||||
return new GB18030Decoder(options)
|
||||
},
|
||||
'gb18030'(options) {
|
||||
return new GB18030Decoder(options)
|
||||
},
|
||||
'Big5'(options) {
|
||||
return new Big5Decoder(options)
|
||||
},
|
||||
'EUC-JP'(options) {
|
||||
return new EUCJPDecoder(options)
|
||||
},
|
||||
'EUC-KR'(options) {
|
||||
return new EUCKRDecoder(options)
|
||||
},
|
||||
'ISO-2022-JP'(options) {
|
||||
return new ISO2022JPDecoder(options)
|
||||
},
|
||||
'UTF-16BE'(options) { // 15.3.1 utf-16be decoder
|
||||
return new UTF16Decoder(true, options)
|
||||
},
|
||||
'UTF-16LE'(options) { // 15.4.1 utf-16le decoder
|
||||
return new UTF16Decoder(false, options)
|
||||
},
|
||||
'x-user-defined'() {
|
||||
return new XUserDefinedDecoder()
|
||||
},
|
||||
'Shift_JIS'(options) {
|
||||
return new ShiftJISDecoder(options)
|
||||
},
|
||||
}
|
||||
|
||||
Encodings.forEach(({ heading, encodings }) => {
|
||||
if (heading != 'Legacy single-byte encodings')
|
||||
return
|
||||
encodings.forEach((encoding) => {
|
||||
const name = encoding.name
|
||||
const idx = index(name.toLowerCase())
|
||||
decoders[name] = (options) => {
|
||||
return new SingleByteDecoder(idx, options)
|
||||
}
|
||||
encoders[name] = (options) => {
|
||||
return new SingleByteEncoder(idx, options)
|
||||
}
|
||||
})
|
||||
})
|
84
cat/tjs/lib/tencentDanmu.js
Normal file
84
cat/tjs/lib/tencentDanmu.js
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* @File : tencentDanmu.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/3/13 13:17
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
|
||||
import {DammuSpider} from "./danmuSpider.js";
|
||||
import {VodDetail} from "./vod.js";
|
||||
import * as Utils from "./utils.js";
|
||||
|
||||
class TencentDammuSpider extends DammuSpider {
|
||||
constructor() {
|
||||
super()
|
||||
this.siteUrl = "https://v.qq.com"
|
||||
this.reconnectTimes = 0
|
||||
this.maxReconnectTimes = 5
|
||||
}
|
||||
|
||||
getAppName() {
|
||||
return "腾讯视频"
|
||||
}
|
||||
|
||||
async parseVodShortListFromDoc($) {
|
||||
let vodElements = $("[class=\"_infos\"]")
|
||||
let vod_list = []
|
||||
for (const vodElement of vodElements) {
|
||||
let vodDetail = new VodDetail()
|
||||
let titleElement = $(vodElement).find("[class=\"result_title\"]")
|
||||
let infoItemEvenElenet = $(vodElement).find("[class=\"info_item info_item_even\"]")
|
||||
let infoItemOddElement = $(vodElement).find("[class=\"info_item info_item_odd\"]")
|
||||
let descElement = $(vodElement).find("[class=\"info_item info_item_desc\"]")
|
||||
vodDetail.vod_name = $($(titleElement).find("[class=\"hl\"]")).text()
|
||||
vodDetail.vod_year = $($(titleElement).find("[class=\"sub\"]")).text().replaceAll("\n","").replaceAll("(","").replaceAll(")","").replaceAll("\t","").split("/").slice(-1)[0]
|
||||
vodDetail.type_name = $($(titleElement).find("[class=\"type\"]")).text()
|
||||
vodDetail.vod_director = $($($(infoItemEvenElenet).find("[class=\"content\"]")).find("span")).text()
|
||||
let actorList = $( $(infoItemOddElement.slice(-1)[0]).find("[class=\"content\"]")).find("a")
|
||||
let vodActorList = []
|
||||
for (const actorElement of actorList){
|
||||
vodActorList.push($(actorElement).text())
|
||||
}
|
||||
vodDetail.vod_actor = vodActorList.join(" * ")
|
||||
vodDetail.vod_content = $($(descElement).find("[class=\"desc_text\"]")[0]).text()
|
||||
let url = $(vodElement).find("a")[0].attribs.href
|
||||
if (url.indexOf("cover") > -1){
|
||||
let detail$ = await this.getHtml(url)
|
||||
let video_ids = JSON.parse(Utils.getStrByRegex(/"video_ids":(.*?),/,detail$.html()))
|
||||
vodDetail.vod_id = video_ids[0]
|
||||
vod_list.push(vodDetail)
|
||||
}
|
||||
}
|
||||
return vod_list
|
||||
}
|
||||
|
||||
async search(wd) {
|
||||
await this.jadeLog.debug(`正在搜索:${wd}`, true)
|
||||
let searchUrl = this.siteUrl + `/x/search/?q=${wd}`
|
||||
let $ = await this.getHtml(searchUrl)
|
||||
return this.parseVodShortListFromDoc($)
|
||||
}
|
||||
|
||||
parseDammu(id){
|
||||
|
||||
}
|
||||
|
||||
|
||||
async getDammu(voddetail, episodeId) {
|
||||
let vod_list = await this.search(voddetail.vod_name)
|
||||
for (const searchVodDetail of vod_list){
|
||||
if (voddetail.vod_director === searchVodDetail.vod_director){
|
||||
await this.jadeLog.debug("搜索匹配成功",true)
|
||||
return
|
||||
}
|
||||
}
|
||||
await this.jadeLog.warning(`搜索匹配失败,原:${JSON.stringify(voddetail)},搜索:${JSON.stringify(vod_list)}`)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
export {TencentDammuSpider}
|
||||
|
||||
|
118
cat/tjs/lib/text_decoder_index.js
Normal file
118
cat/tjs/lib/text_decoder_index.js
Normal file
@ -0,0 +1,118 @@
|
||||
import { end_of_stream } from './text_decoder_utils.js'
|
||||
import { label_to_encoding } from './table.js'
|
||||
|
||||
export default class Stream {
|
||||
/**
|
||||
* A stream represents an ordered sequence of tokens.
|
||||
* @param {!(Array.<number>|Uint8Array)} tokens Array of tokens that provide
|
||||
* the stream.
|
||||
*/
|
||||
constructor(tokens) {
|
||||
this.tokens = [...tokens]
|
||||
// Reversed as push/pop is more efficient than shift/unshift.
|
||||
this.tokens.reverse()
|
||||
}
|
||||
/**
|
||||
* @returns True if end-of-stream has been hit.
|
||||
*/
|
||||
endOfStream() {
|
||||
return !this.tokens.length
|
||||
}
|
||||
/**
|
||||
* When a token is read from a stream, the first token in the
|
||||
* stream must be returned and subsequently removed, and
|
||||
* end-of-stream must be returned otherwise.
|
||||
*
|
||||
* @return Get the next token from the stream, or end_of_stream.
|
||||
*/
|
||||
read() {
|
||||
if (!this.tokens.length)
|
||||
return end_of_stream
|
||||
return this.tokens.pop()
|
||||
}
|
||||
/**
|
||||
* When one or more tokens are prepended to a stream, those tokens
|
||||
* must be inserted, in given order, before the first token in the
|
||||
* stream.
|
||||
*
|
||||
* @param {(number|!Array.<number>)} token The token(s) to prepend to the
|
||||
* stream.
|
||||
*/
|
||||
prepend(token) {
|
||||
if (Array.isArray(token)) {
|
||||
var tokens = /**@type {!Array.<number>}*/(token)
|
||||
while (tokens.length)
|
||||
this.tokens.push(tokens.pop())
|
||||
} else {
|
||||
this.tokens.push(token)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* When one or more tokens are pushed to a stream, those tokens
|
||||
* must be inserted, in given order, after the last token in the
|
||||
* stream.
|
||||
*
|
||||
* @param {(number|!Array.<number>)} token The tokens(s) to push to the
|
||||
* stream.
|
||||
*/
|
||||
push(token) {
|
||||
if (Array.isArray(token)) {
|
||||
const tokens = /**@type {!Array.<number>}*/(token)
|
||||
while (tokens.length)
|
||||
this.tokens.unshift(tokens.shift())
|
||||
} else {
|
||||
this.tokens.unshift(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const DEFAULT_ENCODING = 'utf-8'
|
||||
|
||||
|
||||
/**
|
||||
* Returns the encoding for the label.
|
||||
* @param {string} label The encoding label.
|
||||
*/
|
||||
export function getEncoding(label) {
|
||||
// 1. Remove any leading and trailing ASCII whitespace from label.
|
||||
label = String(label).trim().toLowerCase()
|
||||
|
||||
// 2. If label is an ASCII case-insensitive match for any of the
|
||||
// labels listed in the table below, return the corresponding
|
||||
// encoding, and failure otherwise.
|
||||
if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) {
|
||||
return label_to_encoding[label]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 5. Encodings
|
||||
//
|
||||
|
||||
// 5.1 Encoders and decoders
|
||||
|
||||
// /** @interface */
|
||||
// function Decoder() {}
|
||||
// Decoder.prototype = {
|
||||
// /**
|
||||
// * @param {Stream} stream The stream of bytes being decoded.
|
||||
// * @param {number} bite The next byte read from the stream.
|
||||
// * @return {?(number|!Array.<number>)} The next code point(s)
|
||||
// * decoded, or null if not enough data exists in the input
|
||||
// * stream to decode a complete code point, or |finished|.
|
||||
// */
|
||||
// handler: function(stream, bite) {},
|
||||
// }
|
||||
|
||||
// /** @interface */
|
||||
// function Encoder() {}
|
||||
// Encoder.prototype = {
|
||||
// /**
|
||||
// * @param {Stream} stream The stream of code points being encoded.
|
||||
// * @param {number} code_point Next code point read from the stream.
|
||||
// * @return {(number|!Array.<number>)} Byte(s) to emit, or |finished|.
|
||||
// */
|
||||
// handler: function(stream, code_point) {},
|
||||
// }
|
153
cat/tjs/lib/text_decoder_indexes.js
Normal file
153
cat/tjs/lib/text_decoder_indexes.js
Normal file
@ -0,0 +1,153 @@
|
||||
import { inRange } from './text_decoder_utils.js'
|
||||
import {Indexes} from './encoding-indexes.js'
|
||||
|
||||
//
|
||||
// 6. Indexes
|
||||
//
|
||||
|
||||
/**
|
||||
* @param {number} pointer The |pointer| to search for.
|
||||
* @param {(!Array.<?number>|undefined)} index The |index| to search within.
|
||||
* @return {?number} The code point corresponding to |pointer| in |index|,
|
||||
* or null if |code point| is not in |index|.
|
||||
*/
|
||||
export function indexCodePointFor(pointer, i) {
|
||||
if (!i) return null
|
||||
return i[pointer] || null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} code_point The |code point| to search for.
|
||||
* @param {!Array.<?number>} i The |index| to search within.
|
||||
* @return {?number} The first pointer corresponding to |code point| in
|
||||
* |index|, or null if |code point| is not in |index|.
|
||||
*/
|
||||
export function indexPointerFor(code_point, i) {
|
||||
var pointer = i.indexOf(code_point)
|
||||
return pointer === -1 ? null : pointer
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name Name of the index.
|
||||
*/
|
||||
export default function index(name) {
|
||||
return Indexes[name]
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} pointer The |pointer| to search for in the gb18030 index.
|
||||
* @return The code point corresponding to |pointer| in |index|,
|
||||
* or null if |code point| is not in the gb18030 index.
|
||||
*/
|
||||
export function indexGB18030RangesCodePointFor(pointer) {
|
||||
// 1. If pointer is greater than 39419 and less than 189000, or
|
||||
// pointer is greater than 1237575, return null.
|
||||
if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575))
|
||||
return null
|
||||
|
||||
// 2. If pointer is 7457, return code point U+E7C7.
|
||||
if (pointer === 7457) return 0xE7C7
|
||||
|
||||
// 3. Let offset be the last pointer in index gb18030 ranges that
|
||||
// is equal to or less than pointer and let code point offset be
|
||||
// its corresponding code point.
|
||||
var offset = 0
|
||||
var code_point_offset = 0
|
||||
var idx = index('gb18030-ranges')
|
||||
var i
|
||||
for (i = 0; i < idx.length; ++i) {
|
||||
/** @type {!Array.<number>} */
|
||||
var entry = idx[i]
|
||||
if (entry[0] <= pointer) {
|
||||
offset = entry[0]
|
||||
code_point_offset = entry[1]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Return a code point whose value is code point offset +
|
||||
// pointer − offset.
|
||||
return code_point_offset + pointer - offset
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} code_point The |code point| to locate in the gb18030 index.
|
||||
* @return {number} The first pointer corresponding to |code point| in the
|
||||
* gb18030 index.
|
||||
*/
|
||||
export function indexGB18030RangesPointerFor(code_point) {
|
||||
// 1. If code point is U+E7C7, return pointer 7457.
|
||||
if (code_point === 0xE7C7) return 7457
|
||||
|
||||
// 2. Let offset be the last code point in index gb18030 ranges
|
||||
// that is equal to or less than code point and let pointer offset
|
||||
// be its corresponding pointer.
|
||||
var offset = 0
|
||||
var pointer_offset = 0
|
||||
var idx = index('gb18030-ranges')
|
||||
var i
|
||||
for (i = 0; i < idx.length; ++i) {
|
||||
/** @type {!Array.<number>} */
|
||||
var entry = idx[i]
|
||||
if (entry[1] <= code_point) {
|
||||
offset = entry[1]
|
||||
pointer_offset = entry[0]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return a pointer whose value is pointer offset + code point
|
||||
// − offset.
|
||||
return pointer_offset + code_point - offset
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} code_point The |code_point| to search for in the Shift_JIS
|
||||
* index.
|
||||
* @return {?number} The code point corresponding to |pointer| in |index|,
|
||||
* or null if |code point| is not in the Shift_JIS index.
|
||||
*/
|
||||
export function indexShiftJISPointerFor(code_point) {
|
||||
// 1. Let index be index jis0208 excluding all entries whose
|
||||
// pointer is in the range 8272 to 8835, inclusive.
|
||||
shift_jis_index = shift_jis_index ||
|
||||
index('jis0208').map((cp, pointer) => {
|
||||
return inRange(pointer, 8272, 8835) ? null : cp
|
||||
})
|
||||
const index_ = shift_jis_index
|
||||
|
||||
// 2. Return the index pointer for code point in index.
|
||||
return index_.indexOf(code_point)
|
||||
}
|
||||
var shift_jis_index
|
||||
|
||||
/**
|
||||
* @param {number} code_point The |code_point| to search for in the big5
|
||||
* index.
|
||||
* @return {?number} The code point corresponding to |pointer| in |index|,
|
||||
* or null if |code point| is not in the big5 index.
|
||||
*/
|
||||
export function indexBig5PointerFor(code_point) {
|
||||
// 1. Let index be index Big5 excluding all entries whose pointer
|
||||
big5_index_no_hkscs = big5_index_no_hkscs ||
|
||||
index('big5').map((cp, pointer) => {
|
||||
return (pointer < (0xA1 - 0x81) * 157) ? null : cp
|
||||
})
|
||||
var index_ = big5_index_no_hkscs
|
||||
|
||||
// 2. If code point is U+2550, U+255E, U+2561, U+256A, U+5341, or
|
||||
// U+5345, return the last pointer corresponding to code point in
|
||||
// index.
|
||||
if (code_point === 0x2550 || code_point === 0x255E ||
|
||||
code_point === 0x2561 || code_point === 0x256A ||
|
||||
code_point === 0x5341 || code_point === 0x5345) {
|
||||
return index_.lastIndexOf(code_point)
|
||||
}
|
||||
|
||||
// 3. Return the index pointer for code point in index.
|
||||
return indexPointerFor(code_point, index_)
|
||||
}
|
||||
|
||||
var big5_index_no_hkscs
|
180
cat/tjs/lib/text_decoder_utils.js
Normal file
180
cat/tjs/lib/text_decoder_utils.js
Normal file
@ -0,0 +1,180 @@
|
||||
//
|
||||
// Utilities
|
||||
//
|
||||
/**
|
||||
* @param {number} a The number to test.
|
||||
* @param {number} min The minimum value in the range, inclusive.
|
||||
* @param {number} max The maximum value in the range, inclusive.
|
||||
* @return {boolean} True if a >= min and a <= max.
|
||||
*/
|
||||
export function inRange(a, min, max) {
|
||||
return min <= a && a <= max
|
||||
}
|
||||
|
||||
export const floor = Math.floor
|
||||
|
||||
/**
|
||||
* @param {string} string Input string of UTF-16 code units.
|
||||
* @return {!Array.<number>} Code points.
|
||||
*/
|
||||
export function stringToCodePoints(string) {
|
||||
// https://heycam.github.io/webidl/#dfn-obtain-unicode
|
||||
|
||||
// 1. Let S be the DOMString value.
|
||||
var s = String(string)
|
||||
|
||||
// 2. Let n be the length of S.
|
||||
var n = s.length
|
||||
|
||||
// 3. Initialize i to 0.
|
||||
var i = 0
|
||||
|
||||
// 4. Initialize U to be an empty sequence of Unicode characters.
|
||||
var u = []
|
||||
|
||||
// 5. While i < n:
|
||||
while (i < n) {
|
||||
// 1. Let c be the code unit in S at index i.
|
||||
var c = s.charCodeAt(i)
|
||||
|
||||
// 2. Depending on the value of c:
|
||||
|
||||
// c < 0xD800 or c > 0xDFFF
|
||||
if (c < 0xD800 || c > 0xDFFF) {
|
||||
// Append to U the Unicode character with code point c.
|
||||
u.push(c)
|
||||
}
|
||||
|
||||
// 0xDC00 ≤ c ≤ 0xDFFF
|
||||
else if (0xDC00 <= c && c <= 0xDFFF) {
|
||||
// Append to U a U+FFFD REPLACEMENT CHARACTER.
|
||||
u.push(0xFFFD)
|
||||
}
|
||||
|
||||
// 0xD800 ≤ c ≤ 0xDBFF
|
||||
else if (0xD800 <= c && c <= 0xDBFF) {
|
||||
// 1. If i = n−1, then append to U a U+FFFD REPLACEMENT
|
||||
// CHARACTER.
|
||||
if (i === n - 1) {
|
||||
u.push(0xFFFD)
|
||||
}
|
||||
// 2. Otherwise, i < n−1:
|
||||
else {
|
||||
// 1. Let d be the code unit in S at index i+1.
|
||||
var d = s.charCodeAt(i + 1)
|
||||
|
||||
// 2. If 0xDC00 ≤ d ≤ 0xDFFF, then:
|
||||
if (0xDC00 <= d && d <= 0xDFFF) {
|
||||
// 1. Let a be c & 0x3FF.
|
||||
var a = c & 0x3FF
|
||||
|
||||
// 2. Let b be d & 0x3FF.
|
||||
var b = d & 0x3FF
|
||||
|
||||
// 3. Append to U the Unicode character with code point
|
||||
// 2^16+2^10*a+b.
|
||||
u.push(0x10000 + (a << 10) + b)
|
||||
|
||||
// 4. Set i to i+1.
|
||||
i += 1
|
||||
}
|
||||
|
||||
// 3. Otherwise, d < 0xDC00 or d > 0xDFFF. Append to U a
|
||||
// U+FFFD REPLACEMENT CHARACTER.
|
||||
else {
|
||||
u.push(0xFFFD)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Set i to i+1.
|
||||
i += 1
|
||||
}
|
||||
|
||||
// 6. Return U.
|
||||
return u
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array.<number>} code_points Array of code points.
|
||||
* @return {string} string String of UTF-16 code units.
|
||||
*/
|
||||
export function codePointsToString(code_points) {
|
||||
var s = ''
|
||||
for (var i = 0; i < code_points.length; ++i) {
|
||||
var cp = code_points[i]
|
||||
if (cp <= 0xFFFF) {
|
||||
s += String.fromCharCode(cp)
|
||||
} else {
|
||||
cp -= 0x10000
|
||||
s += String.fromCharCode((cp >> 10) + 0xD800,
|
||||
(cp & 0x3FF) + 0xDC00)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} fatal If true, decoding errors raise an exception.
|
||||
* @param {number=} opt_code_point Override the standard fallback code point.
|
||||
* @return The code point to insert on a decoding error.
|
||||
*/
|
||||
export function decoderError(fatal, opt_code_point) {
|
||||
if (fatal)
|
||||
throw TypeError('Decoder error')
|
||||
return opt_code_point || 0xFFFD
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} code_point The code point that could not be encoded.
|
||||
* @return {number} Always throws, no value is actually returned.
|
||||
*/
|
||||
export function encoderError(code_point) {
|
||||
throw TypeError('The code point ' + code_point + ' could not be encoded.')
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} code_unit
|
||||
* @param {boolean} utf16be
|
||||
*/
|
||||
export function convertCodeUnitToBytes(code_unit, utf16be) {
|
||||
// 1. Let byte1 be code unit >> 8.
|
||||
const byte1 = code_unit >> 8
|
||||
|
||||
// 2. Let byte2 be code unit & 0x00FF.
|
||||
const byte2 = code_unit & 0x00FF
|
||||
|
||||
// 3. Then return the bytes in order:
|
||||
// utf-16be flag is set: byte1, then byte2.
|
||||
if (utf16be)
|
||||
return [byte1, byte2]
|
||||
// utf-16be flag is unset: byte2, then byte1.
|
||||
return [byte2, byte1]
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// 4. Terminology
|
||||
//
|
||||
|
||||
/**
|
||||
* An ASCII byte is a byte in the range 0x00 to 0x7F, inclusive.
|
||||
* @param {number} a The number to test.
|
||||
* @return {boolean} True if a is in the range 0x00 to 0x7F, inclusive.
|
||||
*/
|
||||
export function isASCIIByte(a) {
|
||||
return 0x00 <= a && a <= 0x7F
|
||||
}
|
||||
|
||||
/**
|
||||
* An ASCII code point is a code point in the range U+0000 to
|
||||
* U+007F, inclusive.
|
||||
*/
|
||||
export const isASCIICodePoint = isASCIIByte
|
||||
|
||||
/**
|
||||
* End-of-stream is a special token that signifies no more tokens are in the stream.
|
||||
*/
|
||||
export const end_of_stream = -1
|
||||
|
||||
export const finished = -1
|
139
cat/tjs/lib/utf16.js
Normal file
139
cat/tjs/lib/utf16.js
Normal file
@ -0,0 +1,139 @@
|
||||
import { inRange, decoderError, end_of_stream, finished, convertCodeUnitToBytes } from './text_decoder_utils.js'
|
||||
|
||||
// 15.2.1 shared utf-16 decoder
|
||||
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class UTF16Decoder {
|
||||
/**
|
||||
* @param {boolean} utf16_be True if big-endian, false if little-endian.
|
||||
* @param {{fatal: boolean}} options
|
||||
*/
|
||||
constructor(utf16_be, options) {
|
||||
const { fatal } = options
|
||||
this.utf16_be = utf16_be
|
||||
this.fatal = fatal
|
||||
this.utf16_lead_byte = null
|
||||
this.utf16_lead_surrogate = null
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream and either utf-16 lead byte or
|
||||
// utf-16 lead surrogate is not null, set utf-16 lead byte and
|
||||
// utf-16 lead surrogate to null, and return error.
|
||||
if (bite === end_of_stream && (this.utf16_lead_byte !== null ||
|
||||
this.utf16_lead_surrogate !== null)) {
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 2. If byte is end-of-stream and utf-16 lead byte and utf-16
|
||||
// lead surrogate are null, return finished.
|
||||
if (bite === end_of_stream && this.utf16_lead_byte === null &&
|
||||
this.utf16_lead_surrogate === null) {
|
||||
return finished
|
||||
}
|
||||
|
||||
// 3. If utf-16 lead byte is null, set utf-16 lead byte to byte
|
||||
// and return continue.
|
||||
if (this.utf16_lead_byte === null) {
|
||||
this.utf16_lead_byte = bite
|
||||
return null
|
||||
}
|
||||
|
||||
// 4. Let code unit be the result of:
|
||||
let code_unit
|
||||
if (this.utf16_be) {
|
||||
// utf-16be decoder flag is set
|
||||
// (utf-16 lead byte << 8) + byte.
|
||||
code_unit = (this.utf16_lead_byte << 8) + bite
|
||||
} else {
|
||||
// utf-16be decoder flag is unset
|
||||
// (byte << 8) + utf-16 lead byte.
|
||||
code_unit = (bite << 8) + this.utf16_lead_byte
|
||||
}
|
||||
// Then set utf-16 lead byte to null.
|
||||
this.utf16_lead_byte = null
|
||||
|
||||
// 5. If utf-16 lead surrogate is not null, let lead surrogate
|
||||
// be utf-16 lead surrogate, set utf-16 lead surrogate to null,
|
||||
// and then run these substeps:
|
||||
if (this.utf16_lead_surrogate !== null) {
|
||||
const lead_surrogate = this.utf16_lead_surrogate
|
||||
this.utf16_lead_surrogate = null
|
||||
|
||||
// 1. If code unit is in the range U+DC00 to U+DFFF,
|
||||
// inclusive, return a code point whose value is 0x10000 +
|
||||
// ((lead surrogate − 0xD800) << 10) + (code unit − 0xDC00).
|
||||
if (inRange(code_unit, 0xDC00, 0xDFFF)) {
|
||||
return 0x10000 + (lead_surrogate - 0xD800) * 0x400 +
|
||||
(code_unit - 0xDC00)
|
||||
}
|
||||
|
||||
// 2. Prepend the sequence resulting of converting code unit
|
||||
// to bytes using utf-16be decoder flag to stream and return
|
||||
// error.
|
||||
stream.prepend(convertCodeUnitToBytes(code_unit, this.utf16_be))
|
||||
return decoderError(this.fatal)
|
||||
}
|
||||
|
||||
// 6. If code unit is in the range U+D800 to U+DBFF, inclusive,
|
||||
// set utf-16 lead surrogate to code unit and return continue.
|
||||
if (inRange(code_unit, 0xD800, 0xDBFF)) {
|
||||
this.utf16_lead_surrogate = code_unit
|
||||
return null
|
||||
}
|
||||
|
||||
// 7. If code unit is in the range U+DC00 to U+DFFF, inclusive,
|
||||
// return error.
|
||||
if (inRange(code_unit, 0xDC00, 0xDFFF))
|
||||
return decoderError(this.fatal)
|
||||
|
||||
// 8. Return code point code unit.
|
||||
return code_unit
|
||||
}
|
||||
}
|
||||
|
||||
// 15.2.2 shared utf-16 encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class UTF16Encoder {
|
||||
/**
|
||||
* @param {boolean} [utf16_be] True if big-endian, false if little-endian.
|
||||
*/
|
||||
constructor(utf16_be = false) {
|
||||
this.utf16_be = utf16_be
|
||||
}
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is in the range U+0000 to U+FFFF, inclusive,
|
||||
// return the sequence resulting of converting code point to
|
||||
// bytes using utf-16be encoder flag.
|
||||
if (inRange(code_point, 0x0000, 0xFFFF))
|
||||
return convertCodeUnitToBytes(code_point, this.utf16_be)
|
||||
|
||||
// 3. Let lead be ((code point − 0x10000) >> 10) + 0xD800,
|
||||
// converted to bytes using utf-16be encoder flag.
|
||||
const lead = convertCodeUnitToBytes(
|
||||
((code_point - 0x10000) >> 10) + 0xD800, this.utf16_be)
|
||||
|
||||
// 4. Let trail be ((code point − 0x10000) & 0x3FF) + 0xDC00,
|
||||
// converted to bytes using utf-16be encoder flag.
|
||||
const trail = convertCodeUnitToBytes(
|
||||
((code_point - 0x10000) & 0x3FF) + 0xDC00, this.utf16_be)
|
||||
|
||||
// 5. Return a byte sequence of lead followed by trail.
|
||||
return lead.concat(trail)
|
||||
}
|
||||
}
|
208
cat/tjs/lib/utf8.js
Normal file
208
cat/tjs/lib/utf8.js
Normal file
@ -0,0 +1,208 @@
|
||||
import { inRange, decoderError, isASCIICodePoint,
|
||||
end_of_stream, finished } from './text_decoder_utils.js'
|
||||
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class UTF8Decoder {
|
||||
/**
|
||||
* @param {{fatal: boolean}} options
|
||||
*/
|
||||
constructor(options) {
|
||||
const { fatal } = options
|
||||
|
||||
// utf-8's decoder's has an associated utf-8 code point, utf-8
|
||||
// bytes seen, and utf-8 bytes needed (all initially 0), a utf-8
|
||||
// lower boundary (initially 0x80), and a utf-8 upper boundary
|
||||
// (initially 0xBF).
|
||||
let /** @type {number} */ utf8_code_point = 0,
|
||||
/** @type {number} */ utf8_bytes_seen = 0,
|
||||
/** @type {number} */ utf8_bytes_needed = 0,
|
||||
/** @type {number} */ utf8_lower_boundary = 0x80,
|
||||
/** @type {number} */ utf8_upper_boundary = 0xBF
|
||||
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
* @return {?(number|!Array.<number>)} The next code point(s)
|
||||
* decoded, or null if not enough data exists in the input
|
||||
* stream to decode a complete code point.
|
||||
*/
|
||||
this.handler = function(stream, bite) {
|
||||
// 1. If byte is end-of-stream and utf-8 bytes needed is not 0,
|
||||
// set utf-8 bytes needed to 0 and return error.
|
||||
if (bite === end_of_stream && utf8_bytes_needed !== 0) {
|
||||
utf8_bytes_needed = 0
|
||||
return decoderError(fatal)
|
||||
}
|
||||
|
||||
// 2. If byte is end-of-stream, return finished.
|
||||
if (bite === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 3. If utf-8 bytes needed is 0, based on byte:
|
||||
if (utf8_bytes_needed === 0) {
|
||||
// 0x00 to 0x7F
|
||||
if (inRange(bite, 0x00, 0x7F)) {
|
||||
// Return a code point whose value is byte.
|
||||
return bite
|
||||
}
|
||||
|
||||
// 0xC2 to 0xDF
|
||||
else if (inRange(bite, 0xC2, 0xDF)) {
|
||||
// 1. Set utf-8 bytes needed to 1.
|
||||
utf8_bytes_needed = 1
|
||||
|
||||
// 2. Set UTF-8 code point to byte & 0x1F.
|
||||
utf8_code_point = bite & 0x1F
|
||||
}
|
||||
|
||||
// 0xE0 to 0xEF
|
||||
else if (inRange(bite, 0xE0, 0xEF)) {
|
||||
// 1. If byte is 0xE0, set utf-8 lower boundary to 0xA0.
|
||||
if (bite === 0xE0)
|
||||
utf8_lower_boundary = 0xA0
|
||||
// 2. If byte is 0xED, set utf-8 upper boundary to 0x9F.
|
||||
if (bite === 0xED)
|
||||
utf8_upper_boundary = 0x9F
|
||||
// 3. Set utf-8 bytes needed to 2.
|
||||
utf8_bytes_needed = 2
|
||||
// 4. Set UTF-8 code point to byte & 0xF.
|
||||
utf8_code_point = bite & 0xF
|
||||
}
|
||||
|
||||
// 0xF0 to 0xF4
|
||||
else if (inRange(bite, 0xF0, 0xF4)) {
|
||||
// 1. If byte is 0xF0, set utf-8 lower boundary to 0x90.
|
||||
if (bite === 0xF0)
|
||||
utf8_lower_boundary = 0x90
|
||||
// 2. If byte is 0xF4, set utf-8 upper boundary to 0x8F.
|
||||
if (bite === 0xF4)
|
||||
utf8_upper_boundary = 0x8F
|
||||
// 3. Set utf-8 bytes needed to 3.
|
||||
utf8_bytes_needed = 3
|
||||
// 4. Set UTF-8 code point to byte & 0x7.
|
||||
utf8_code_point = bite & 0x7
|
||||
}
|
||||
|
||||
// Otherwise
|
||||
else {
|
||||
// Return error.
|
||||
return decoderError(fatal)
|
||||
}
|
||||
|
||||
// Return continue.
|
||||
return null
|
||||
}
|
||||
|
||||
// 4. If byte is not in the range utf-8 lower boundary to utf-8
|
||||
// upper boundary, inclusive, run these substeps:
|
||||
if (!inRange(bite, utf8_lower_boundary, utf8_upper_boundary)) {
|
||||
// 1. Set utf-8 code point, utf-8 bytes needed, and utf-8
|
||||
// bytes seen to 0, set utf-8 lower boundary to 0x80, and set
|
||||
// utf-8 upper boundary to 0xBF.
|
||||
utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0
|
||||
utf8_lower_boundary = 0x80
|
||||
utf8_upper_boundary = 0xBF
|
||||
|
||||
// 2. Prepend byte to stream.
|
||||
stream.prepend(bite)
|
||||
|
||||
// 3. Return error.
|
||||
return decoderError(fatal)
|
||||
}
|
||||
|
||||
// 5. Set utf-8 lower boundary to 0x80 and utf-8 upper boundary
|
||||
// to 0xBF.
|
||||
utf8_lower_boundary = 0x80
|
||||
utf8_upper_boundary = 0xBF
|
||||
|
||||
// 6. Set UTF-8 code point to (UTF-8 code point << 6) | (byte &
|
||||
// 0x3F)
|
||||
utf8_code_point = (utf8_code_point << 6) | (bite & 0x3F)
|
||||
|
||||
// 7. Increase utf-8 bytes seen by one.
|
||||
utf8_bytes_seen += 1
|
||||
|
||||
// 8. If utf-8 bytes seen is not equal to utf-8 bytes needed,
|
||||
// continue.
|
||||
if (utf8_bytes_seen !== utf8_bytes_needed)
|
||||
return null
|
||||
|
||||
// 9. Let code point be utf-8 code point.
|
||||
var code_point = utf8_code_point
|
||||
|
||||
// 10. Set utf-8 code point, utf-8 bytes needed, and utf-8 bytes
|
||||
// seen to 0.
|
||||
utf8_code_point = utf8_bytes_needed = utf8_bytes_seen = 0
|
||||
|
||||
// 11. Return a code point whose value is code point.
|
||||
return code_point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 9.1.2 utf-8 encoder
|
||||
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class UTF8Encoder {
|
||||
constructor() {
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
* @return {(number|!Array.<number>)} Byte(s) to emit.
|
||||
*/
|
||||
this.handler = function(stream, code_point) {
|
||||
// 1. If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point, return a byte whose
|
||||
// value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 3. Set count and offset based on the range code point is in:
|
||||
var count, offset
|
||||
// U+0080 to U+07FF, inclusive:
|
||||
if (inRange(code_point, 0x0080, 0x07FF)) {
|
||||
// 1 and 0xC0
|
||||
count = 1
|
||||
offset = 0xC0
|
||||
}
|
||||
// U+0800 to U+FFFF, inclusive:
|
||||
else if (inRange(code_point, 0x0800, 0xFFFF)) {
|
||||
// 2 and 0xE0
|
||||
count = 2
|
||||
offset = 0xE0
|
||||
}
|
||||
// U+10000 to U+10FFFF, inclusive:
|
||||
else if (inRange(code_point, 0x10000, 0x10FFFF)) {
|
||||
// 3 and 0xF0
|
||||
count = 3
|
||||
offset = 0xF0
|
||||
}
|
||||
|
||||
// 4. Let bytes be a byte sequence whose first byte is (code
|
||||
// point >> (6 × count)) + offset.
|
||||
var bytes = [(code_point >> (6 * count)) + offset]
|
||||
|
||||
// 5. Run these substeps while count is greater than 0:
|
||||
while (count > 0) {
|
||||
// 1. Set temp to code point >> (6 × (count − 1)).
|
||||
var temp = code_point >> (6 * (count - 1))
|
||||
|
||||
// 2. Append to bytes 0x80 | (temp & 0x3F).
|
||||
bytes.push(0x80 | (temp & 0x3F))
|
||||
|
||||
// 3. Decrease count by one.
|
||||
count -= 1
|
||||
}
|
||||
|
||||
// 6. Return bytes bytes, in order.
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
}
|
385
cat/tjs/lib/utils.js
Normal file
385
cat/tjs/lib/utils.js
Normal file
@ -0,0 +1,385 @@
|
||||
/*
|
||||
* @File : utils.js
|
||||
* @Author : jade
|
||||
* @Date : 2024/1/25 15:01
|
||||
* @Email : jadehh@1ive.com
|
||||
* @Software : Samples
|
||||
* @Desc :
|
||||
*/
|
||||
import {Crypto} from "./cat.js";
|
||||
import {TextDecoder} from "./TextDecoder.js";
|
||||
import {TextEncoder} from "./TextEncoder.js";
|
||||
// import {TextDecoder} from "text-decoding";
|
||||
const deviceBrands = ['Huawei', 'Xiaomi'];
|
||||
const deviceModels = [
|
||||
['MHA-AL00', 'HUAWEI Mate 9', 'MHA-TL00', 'HUAWEI Mate 9', 'LON-AL00', 'HUAWEI Mate 9 Pro', 'ALP-AL00', 'HUAWEI Mate 10', 'ALP-TL00', 'HUAWEI Mate 10', 'BLA-AL00', 'HUAWEI Mate 10 Pro', 'BLA-TL00', 'HUAWEI Mate 10 Pro', 'HMA-AL00', 'HUAWEI Mate 20', 'HMA-TL00', 'HUAWEI Mate 20', 'LYA-AL00', 'HUAWEI Mate 20 Pro', 'LYA-AL10', 'HUAWEI Mate 20 Pro', 'LYA-TL00', 'HUAWEI Mate 20 Pro', 'EVR-AL00', 'HUAWEI Mate 20 X', 'EVR-TL00', 'HUAWEI Mate 20 X', 'EVR-AN00', 'HUAWEI Mate 20 X', 'TAS-AL00', 'HUAWEI Mate 30', 'TAS-TL00', 'HUAWEI Mate 30', 'TAS-AN00', 'HUAWEI Mate 30', 'TAS-TN00', 'HUAWEI Mate 30', 'LIO-AL00', 'HUAWEI Mate 30 Pro', 'LIO-TL00', 'HUAWEI Mate 30 Pro', 'LIO-AN00', 'HUAWEI Mate 30 Pro', 'LIO-TN00', 'HUAWEI Mate 30 Pro', 'LIO-AN00m', 'HUAWEI Mate 30E Pro', 'OCE-AN10', 'HUAWEI Mate 40', 'OCE-AN50', 'HUAWEI Mate 40E', 'OCE-AL50', 'HUAWEI Mate 40E', 'NOH-AN00', 'HUAWEI Mate 40 Pro', 'NOH-AN01', 'HUAWEI Mate 40 Pro', 'NOH-AL00', 'HUAWEI Mate 40 Pro', 'NOH-AL10', 'HUAWEI Mate 40 Pro', 'NOH-AN50', 'HUAWEI Mate 40E Pro', 'NOP-AN00', 'HUAWEI Mate 40 Pro', 'CET-AL00', 'HUAWEI Mate 50', 'CET-AL60', 'HUAWEI Mate 50E', 'DCO-AL00', 'HUAWEI Mate 50 Pro', 'TAH-AN00', 'HUAWEI Mate X', 'TAH-AN00m', 'HUAWEI Mate Xs', 'TET-AN00', 'HUAWEI Mate X2', 'TET-AN10', 'HUAWEI Mate X2', 'TET-AN50', 'HUAWEI Mate X2', 'TET-AL00', 'HUAWEI Mate X2', 'PAL-AL00', 'HUAWEI Mate Xs 2', 'PAL-AL10', 'HUAWEI Mate Xs 2', 'EVA-AL00', 'HUAWEI P9', 'EVA-AL10', 'HUAWEI P9', 'EVA-TL00', 'HUAWEI P9', 'EVA-DL00', 'HUAWEI P9', 'EVA-CL00', 'HUAWEI P9', 'VIE-AL10', 'HUAWEI P9 Plus', 'VTR-AL00', 'HUAWEI P10', 'VTR-TL00', 'HUAWEI P10', 'VKY-AL00', 'HUAWEI P10 Plus', 'VKY-TL00', 'HUAWEI P10 Plus', 'EML-AL00', 'HUAWEI P20', 'EML-TL00', 'HUAWEI P20', 'CLT-AL00', 'HUAWEI P20 Pro', 'CLT-AL01', 'HUAWEI P20 Pro', 'CLT-AL00l', 'HUAWEI P20 Pro', 'CLT-TL00', 'HUAWEI P20 Pro', 'CLT-TL01', 'HUAWEI P20 Pro', 'ELE-AL00', 'HUAWEI P30', 'ELE-TL00', 'HUAWEI P30', 'VOG-AL00', 'HUAWEI P30 Pro', 'VOG-AL10', 'HUAWEI P30 Pro', 'VOG-TL00', 'HUAWEI P30 Pro', 'ANA-AL00', 'HUAWEI P40', 'ANA-AN00', 'HUAWEI P40', 'ANA-TN00', 'HUAWEI P40', 'ELS-AN00', 'HUAWEI P40 Pro', 'ELS-TN00', 'HUAWEI P40 Pro', 'ELS-AN10', 'HUAWEI P40 Pro', 'ELS-TN10', 'HUAWEI P40 Pro', 'ABR-AL00', 'HUAWEI P50', 'ABR-AL80', 'HUAWEI P50', 'ABR-AL60', 'HUAWEI P50E', 'ABR-AL90', 'HUAWEI P50E', 'JAD-AL00', 'HUAWEI P50 Pro', 'JAD-AL80', 'HUAWEI P50 Pro', 'JAD-AL50', 'HUAWEI P50 Pro', 'JAD-AL60', 'HUAWEI P50 Pro', 'BAL-AL00', 'HUAWEI P50 Pocket', 'BAL-AL60', 'HUAWEI Pocket S', 'PIC-AL00', 'HUAWEI nova 2', 'PIC-TL00', 'HUAWEI nova 2', 'BAC-AL00', 'HUAWEI nova 2 Plus', 'BAC-TL00', 'HUAWEI nova 2 Plus', 'HWI-AL00', 'HUAWEI nova 2s', 'HWI-TL00', 'HUAWEI nova 2s', 'ANE-AL00', 'HUAWEI nova 3e', 'ANE-TL00', 'HUAWEI nova 3e', 'PAR-AL00', 'HUAWEI nova 3', 'PAR-TL00', 'HUAWEI nova 3', 'INE-AL00', 'HUAWEI nova 3i', 'INE-TL00', 'HUAWEI nova 3i', 'VCE-AL00', 'HUAWEI nova 4', 'VCE-TL00', 'HUAWEI nova 4', 'MAR-AL00', 'HUAWEI nova 4e', 'MAR-TL00', 'HUAWEI nova 4e', 'SEA-AL00', 'HUAWEI nova 5', 'SEA-TL00', 'HUAWEI nova 5', 'SEA-AL10', 'HUAWEI nova 5 Pro', 'SEA-TL10', 'HUAWEI nova 5 Pro', 'GLK-AL00', 'HUAWEI nova 5i', 'GLK-TL00', 'HUAWEI nova 5i', 'GLK-LX1U', 'HUAWEI nova 5i', 'SPN-TL00', 'HUAWEI nova 5i Pro', 'SPN-AL00', 'HUAWEI nova 5z', 'WLZ-AL10', 'HUAWEI nova 6', 'WLZ-AN00', 'HUAWEI nova 6', 'JNY-AL10', 'HUAWEI nova 6 SE', 'JNY-TL10', 'HUAWEI nova 6 SE', 'JEF-AN00', 'HUAWEI nova 7', 'JEF-AN20', 'HUAWEI nova 7', 'JEF-TN00', 'HUAWEI nova 7', 'JEF-TN20', 'HUAWEI nova 7', 'JER-AN10', 'HUAWEI nova 7 Pro', 'JER-AN20', 'HUAWEI nova 7 Pro', 'JER-TN10', 'HUAWEI nova 7 Pro', 'JER-TN20', 'HUAWEI nova 7 Pro', 'CDY-AN00', 'HUAWEI nova 7 SE', 'CDY-AN20', 'HUAWEI nova 7 SE', 'CDY-TN00', 'HUAWEI nova 7 SE', 'CDY-TN20', 'HUAWEI nova 7 SE', 'ANG-AN00', 'HUAWEI nova 8', 'BRQ-AN00', 'HUAWEI nova 8 Pro', 'BRQ-AL00', 'HUAWEI nova 8 Pro', 'JSC-AN00', 'HUAWEI nova 8 SE', 'JSC-TN00', 'HUAWEI nova 8 SE', 'JSC-AL50', 'HUAWEI nova 8 SE', 'NAM-AL00', 'HUAWEI nova 9', 'RTE-AL00', 'HUAWEI nova 9 Pro', 'JLN-AL00', 'HUAWEI nova 9 SE', 'NCO-AL00', 'HUAWEI nova 10', 'GLA-AL00', 'HUAWEI nova 10 Pro', 'CHA-AL80', 'HUAWEI nova 10z'],
|
||||
['M2001J2C', 'Xiaomi 10', 'M2001J2G', 'Xiaomi 10', 'M2001J2I', 'Xiaomi 10', 'M2011K2C', 'Xiaomi 11', 'M2011K2G', 'Xiaomi 11', '2201123C', 'Xiaomi 12', '2201123G', 'Xiaomi 12', '2112123AC', 'Xiaomi 12X', '2112123AG', 'Xiaomi 12X', '2201122C', 'Xiaomi 12 Pro', '2201122G', 'Xiaomi 12 Pro'],
|
||||
];
|
||||
var charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789';
|
||||
|
||||
let CHROME = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36";
|
||||
const MOBILEUA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1';
|
||||
let RESOURCEURL = "https://gh.con.sh/https://raw.githubusercontent.com/jadehh/TV/js"
|
||||
|
||||
function isSub(ext) {
|
||||
return ext === "srt" || ext === "ass" || ext === "ssa";
|
||||
}
|
||||
|
||||
function isNumeric(str) {
|
||||
return !isNaN(parseInt(str));
|
||||
}
|
||||
|
||||
function getSize(size) {
|
||||
if (size <= 0) return "";
|
||||
if (size > 1024 * 1024 * 1024 * 1024.0) {
|
||||
size /= (1024 * 1024 * 1024 * 1024.0);
|
||||
return size.toFixed(2) + "TB";
|
||||
} else if (size > 1024 * 1024 * 1024.0) {
|
||||
size /= (1024 * 1024 * 1024.0);
|
||||
return size.toFixed(2) + "GB";
|
||||
} else if (size > 1024 * 1024.0) {
|
||||
size /= (1024 * 1024.0);
|
||||
return size.toFixed(2) + "MB";
|
||||
} else {
|
||||
size /= 1024.0;
|
||||
return size.toFixed(2) + "KB";
|
||||
}
|
||||
}
|
||||
|
||||
function removeExt(text) {
|
||||
return text.indexOf('.') > -1 ? text.substring(0, text.lastIndexOf(".")) : text;
|
||||
}
|
||||
|
||||
async function log(str) {
|
||||
console.debug(str);
|
||||
}
|
||||
|
||||
function isVideoFormat(url) {
|
||||
var RULE = /http((?!http).){12,}?\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg|m4a|mp3)\?.*|http((?!http).){12,}\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg|m4a|mp3)|http((?!http).)*?video\/tos*/;
|
||||
if (url.indexOf("url=http") > -1 || url.indexOf(".js") > -1 || url.indexOf(".css") > -1 || url.indexOf(".html") > -1) {
|
||||
return false;
|
||||
}
|
||||
return RULE.test(url);
|
||||
}
|
||||
|
||||
function jsonParse(input, json) {
|
||||
var jsonPlayData = JSON.parse(json);
|
||||
var url = jsonPlayData.url;
|
||||
if (url.startsWith("//")) {
|
||||
url = "https:" + url;
|
||||
}
|
||||
if (!url.startsWith("http")) {
|
||||
return null;
|
||||
}
|
||||
if (url === input) {
|
||||
if (!isVideoFormat(url)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var headers = {};
|
||||
var ua = jsonPlayData["user-agent"] || "";
|
||||
if (ua.trim().length > 0) {
|
||||
headers["User-Agent"] = " " + ua;
|
||||
}
|
||||
var referer = jsonPlayData.referer || "";
|
||||
if (referer.trim().length > 0) {
|
||||
headers["Referer"] = " " + referer;
|
||||
}
|
||||
var taskResult = {
|
||||
header: headers, url: url
|
||||
};
|
||||
return taskResult;
|
||||
}
|
||||
|
||||
function debug(obj) {
|
||||
for (var a in obj) {
|
||||
if (typeof (obj[a]) == "object") {
|
||||
debug(obj[a]); //递归遍历
|
||||
} else {
|
||||
console.debug(a + "=" + obj[a]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function objectToStr(params = null, isBase64Encode = false) {
|
||||
let params_str_list = []
|
||||
if (params !== null) {
|
||||
for (const key of Object.keys(params)) {
|
||||
if (isBase64Encode) {
|
||||
params_str_list.push(`${key}=${encodeURIComponent(params[key])}`)
|
||||
} else {
|
||||
params_str_list.push(`${key}=${params[key]}`)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return params_str_list.join("&")
|
||||
}
|
||||
|
||||
function sleep(delay) {
|
||||
const start = (new Date()).getTime();
|
||||
while ((new Date()).getTime() - start < delay * 1000) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
function getStrByRegex(pattern, str) {
|
||||
let matcher = pattern.exec(str);
|
||||
if (matcher !== null) {
|
||||
if (matcher.length >= 1) {
|
||||
if (matcher.length >= 1) return matcher[1]
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getStrByRegexDefault(pattern, str) {
|
||||
let matcher = pattern.exec(str);
|
||||
if (matcher !== null) {
|
||||
if (matcher.length >= 1) {
|
||||
if (matcher.length >= 1) return matcher[1]
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
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 unescape(code) {
|
||||
return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " ");
|
||||
}
|
||||
|
||||
function decode(buffer, encode_type) {
|
||||
let decoder = new TextDecoder(encode_type)
|
||||
return decoder.decode(buffer)
|
||||
}
|
||||
|
||||
function encode(buffer, encode_type) {
|
||||
let encoder = new TextEncoder(encode_type)
|
||||
return encoder.encode(buffer)
|
||||
}
|
||||
|
||||
function getHost(url) {
|
||||
let url_list = url.split("/")
|
||||
return url_list[0] + "//" + url_list[2]
|
||||
}
|
||||
|
||||
function unquote(str) {
|
||||
return str.replace(/^"(.*)"$/, '$1');
|
||||
}
|
||||
|
||||
function md5Encode(text) {
|
||||
return Crypto.MD5(Crypto.enc.Utf8.parse(text)).toString();
|
||||
}
|
||||
|
||||
|
||||
function getUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
}) + "-" + new Date().getTime().toString(16)
|
||||
|
||||
}
|
||||
|
||||
function objToList(list, key, split_value = "*") {
|
||||
let value_list = []
|
||||
for (const dic of list) {
|
||||
value_list.push(dic[key])
|
||||
}
|
||||
return value_list.join(split_value)
|
||||
}
|
||||
|
||||
function getPropertiesAndMethods(obj) {
|
||||
let str = ""
|
||||
for (let key in obj) {
|
||||
if (typeof obj[key] === 'function') {
|
||||
str = str + "方法名:" + key + '()' + "\n";
|
||||
} else {
|
||||
str = str + "属性名:" + (key + ': ' + obj[key]) + "\n";
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function formatPlayUrl(src, name) {
|
||||
return name
|
||||
.trim()
|
||||
.replaceAll(src, '')
|
||||
.replace(/<|>|《|》/g, '')
|
||||
.replace(/\$|#/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
function rand(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function randDevice() {
|
||||
let brandIdx = rand(0, deviceBrands.length - 1);
|
||||
let brand = deviceBrands[brandIdx];
|
||||
let modelIdx = rand(0, deviceModels[brandIdx].length / 2 - 1);
|
||||
let model = deviceModels[brandIdx][modelIdx * 2 + 1];
|
||||
let release = rand(8, 13);
|
||||
let buildId = randStr(3, false).toUpperCase() + rand(11, 99) + randStr(1, false).toUpperCase();
|
||||
return {
|
||||
brand: brand,
|
||||
model: model,
|
||||
release: release,
|
||||
buildId: buildId,
|
||||
};
|
||||
}
|
||||
|
||||
function randStr(len, withNum, onlyNum) {
|
||||
let _str = '';
|
||||
let containsNum = withNum === undefined ? true : withNum;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let idx = onlyNum ? rand(charStr.length - 10, charStr.length - 1) : rand(0, containsNum ? charStr.length - 1 : charStr.length - 11);
|
||||
_str += charStr[idx];
|
||||
}
|
||||
return _str;
|
||||
}
|
||||
|
||||
function randDeviceWithId(len) {
|
||||
let device = randDevice();
|
||||
device['id'] = randStr(len);
|
||||
return device;
|
||||
}
|
||||
|
||||
function randUUID() {
|
||||
return randStr(8).toLowerCase() + '-' + randStr(4).toLowerCase() + '-' + randStr(4).toLowerCase() + '-' + randStr(4).toLowerCase() + '-' + randStr(12).toLowerCase();
|
||||
}
|
||||
|
||||
function formatUrl(url) {
|
||||
if (url.indexOf("https:////") > -1) {
|
||||
url = "https://" + url.replaceAll("https:////", "")
|
||||
}
|
||||
if (url.indexOf("http:////") > -1) {
|
||||
url = "http://" + url.replaceAll("http:////", "")
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
function formatContent(content) {
|
||||
return content.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '\"')
|
||||
.replaceAll(/<\/?[^>]+>/g, '');
|
||||
}
|
||||
/**
|
||||
* 字符串相似度匹配
|
||||
* @returns
|
||||
*/
|
||||
function lcs(str1, str2) {
|
||||
if (!str1 || !str2) {
|
||||
return {
|
||||
length: 0,
|
||||
sequence: '',
|
||||
offset: 0,
|
||||
};
|
||||
}
|
||||
|
||||
var sequence = '';
|
||||
var str1Length = str1.length;
|
||||
var str2Length = str2.length;
|
||||
var num = new Array(str1Length);
|
||||
var maxlen = 0;
|
||||
var lastSubsBegin = 0;
|
||||
|
||||
for (var i = 0; i < str1Length; i++) {
|
||||
var subArray = new Array(str2Length);
|
||||
for (var j = 0; j < str2Length; j++) {
|
||||
subArray[j] = 0;
|
||||
}
|
||||
num[i] = subArray;
|
||||
}
|
||||
var thisSubsBegin = null;
|
||||
for (i = 0; i < str1Length; i++) {
|
||||
for (j = 0; j < str2Length; j++) {
|
||||
if (str1[i] !== str2[j]) {
|
||||
num[i][j] = 0;
|
||||
} else {
|
||||
if (i === 0 || j === 0) {
|
||||
num[i][j] = 1;
|
||||
} else {
|
||||
num[i][j] = 1 + num[i - 1][j - 1];
|
||||
}
|
||||
|
||||
if (num[i][j] > maxlen) {
|
||||
maxlen = num[i][j];
|
||||
thisSubsBegin = i - num[i][j] + 1;
|
||||
if (lastSubsBegin === thisSubsBegin) {
|
||||
// if the current LCS is the same as the last time this block ran
|
||||
sequence += str1[i];
|
||||
} else {
|
||||
// this block resets the string builder if a different LCS is found
|
||||
lastSubsBegin = thisSubsBegin;
|
||||
sequence = ''; // clear it
|
||||
sequence += str1.substr(lastSubsBegin, i + 1 - lastSubsBegin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
length: maxlen,
|
||||
sequence: sequence,
|
||||
offset: thisSubsBegin,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function findAllIndexes(arr, value) {
|
||||
const indexes = arr.map((item, index) => index).filter(index => arr[index] === value);
|
||||
return indexes;
|
||||
}
|
||||
|
||||
let patternAli = /(https:\/\/www\.aliyundrive\.com\/s\/[^"]+|https:\/\/www\.alipan\.com\/s\/[^"]+)/
|
||||
let patternQuark = /(https:\/\/pan\.quark\.cn\/s\/[^"]+)/
|
||||
|
||||
|
||||
export {
|
||||
isSub,
|
||||
getSize,
|
||||
removeExt,
|
||||
log,
|
||||
isVideoFormat,
|
||||
jsonParse,
|
||||
debug,
|
||||
CHROME,
|
||||
objectToStr,
|
||||
sleep,
|
||||
getStrByRegex,
|
||||
RESOURCEURL,
|
||||
base64Encode,
|
||||
base64Decode,
|
||||
patternAli,
|
||||
patternQuark,
|
||||
unescape,
|
||||
decode,
|
||||
MOBILEUA,
|
||||
isNumeric,
|
||||
getHost,
|
||||
unquote,
|
||||
md5Encode,
|
||||
getStrByRegexDefault,
|
||||
getUUID,
|
||||
objToList,
|
||||
getPropertiesAndMethods,
|
||||
formatPlayUrl,
|
||||
randDeviceWithId,
|
||||
randStr,
|
||||
randUUID,
|
||||
formatContent,
|
||||
formatUrl,
|
||||
encode,
|
||||
lcs,
|
||||
findAllIndexes
|
||||
};
|
62
cat/tjs/lib/vod.js
Normal file
62
cat/tjs/lib/vod.js
Normal file
@ -0,0 +1,62 @@
|
||||
// LocalAddress = "http://192.168.29.156:8099"
|
||||
import * as Utils from "./utils.js";
|
||||
|
||||
export class VodShort {
|
||||
constructor() {
|
||||
this.vod_id = "" //id
|
||||
this.vod_name = "" //名称
|
||||
this.vod_pic = Utils.RESOURCEURL + "/resources/ali.jpg" //图片
|
||||
this.vod_remarks = "" //备注
|
||||
}
|
||||
|
||||
to_dict() {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
|
||||
load_dic(json_str) {
|
||||
let obj = JSON.parse(json_str)
|
||||
for (let propName in obj) {
|
||||
this[propName] = obj[propName];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
load_data(data) {
|
||||
for (let propName in JSON.parse(this.to_dict())) {
|
||||
this[propName] = data[propName];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class VodDetail extends VodShort {
|
||||
constructor() {
|
||||
super();
|
||||
this.type_name = "" // 类别
|
||||
this.vod_year = "" // 年份
|
||||
this.vod_area = "" // 地区
|
||||
this.vod_actor = "" // 导演
|
||||
this.vod_director = "" // 演员
|
||||
this.vod_content = "" // 剧情
|
||||
this.vod_play_from = "" // 播放格式
|
||||
this.vod_play_url = "" // 播放连接
|
||||
}
|
||||
|
||||
to_short() {
|
||||
let vodShort = new VodShort()
|
||||
vodShort.load_dic(this.to_dict())
|
||||
return vodShort
|
||||
}
|
||||
|
||||
load_dic(json_str) {
|
||||
let obj = JSON.parse(json_str)
|
||||
for (let propName in JSON.parse(this.to_dict())) {
|
||||
this[propName] = obj[propName];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
56
cat/tjs/lib/x-user-defined.js
Normal file
56
cat/tjs/lib/x-user-defined.js
Normal file
@ -0,0 +1,56 @@
|
||||
import { inRange, encoderError, end_of_stream, finished, isASCIIByte, isASCIICodePoint } from './text_decoder_utils.js'
|
||||
|
||||
// 15.5 x-user-defined
|
||||
|
||||
// 15.5.1 x-user-defined decoder
|
||||
/**
|
||||
* @implements {Decoder}
|
||||
*/
|
||||
export class XUserDefinedDecoder {
|
||||
/**
|
||||
* @param {Stream} stream The stream of bytes being decoded.
|
||||
* @param {number} bite The next byte read from the stream.
|
||||
*/
|
||||
handler(stream, bite) {
|
||||
// 1. If byte is end-of-stream, return finished.
|
||||
if (bite === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If byte is an ASCII byte, return a code point whose value
|
||||
// is byte.
|
||||
if (isASCIIByte(bite))
|
||||
return bite
|
||||
|
||||
// 3. Return a code point whose value is 0xF780 + byte − 0x80.
|
||||
return 0xF780 + bite - 0x80
|
||||
}
|
||||
}
|
||||
|
||||
// 15.5.2 x-user-defined encoder
|
||||
/**
|
||||
* @implements {Encoder}
|
||||
*/
|
||||
export class XUserDefinedEncoder {
|
||||
/**
|
||||
* @param {Stream} stream Input stream.
|
||||
* @param {number} code_point Next code point read from the stream.
|
||||
*/
|
||||
handler(stream, code_point) {
|
||||
// 1.If code point is end-of-stream, return finished.
|
||||
if (code_point === end_of_stream)
|
||||
return finished
|
||||
|
||||
// 2. If code point is an ASCII code point, return a byte whose
|
||||
// value is code point.
|
||||
if (isASCIICodePoint(code_point))
|
||||
return code_point
|
||||
|
||||
// 3. If code point is in the range U+F780 to U+F7FF, inclusive,
|
||||
// return a byte whose value is code point − 0xF780 + 0x80.
|
||||
if (inRange(code_point, 0xF780, 0xF7FF))
|
||||
return code_point - 0xF780 + 0x80
|
||||
|
||||
// 4. Return error with code point.
|
||||
return encoderError(code_point)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user