haijiao-pro-video-crack

适用于 haijiao.pro 的付费视频解锁脚本

// ==UserScript==
// @name        haijiao-pro-video-crack
// @description 适用于 haijiao.pro 的付费视频解锁脚本
// @version     0.0.2
// @author      JoyofFire
// @match       https://www.haijiao.pro/post/details?pid=*
// @match       https://haijiao.pro/post/details?pid=*
// @grant       none
// @iconURL     https://www.haijiao.pro/favicon.ico
// @license     gplv3
// @namespace   http://tampermonkey.net/
// @run-at      document-start
// ==/UserScript==

/*
 * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
 * This devtool is neither made for production nor for readable output files.
 * It uses "eval()" calls to create a separate source file in the browser devtools.
 * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
 * or disable the default devtool with "devtool: false".
 * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
 */
/******/ (() => { // webpackBootstrap
/******/ 	"use strict";
/******/ 	var __webpack_modules__ = ({

/***/ "./src/core.ts":
/*!*********************!*\
  !*** ./src/core.ts ***!
  \*********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   create_full_m3u8: () => (/* binding */ create_full_m3u8),\n/* harmony export */   is_m3u8_preview_request: () => (/* binding */ is_m3u8_preview_request)\n/* harmony export */ });\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ \"./src/utils.ts\");\n\r\nconst TS_URL_PATTERN = /_i([0-9]+)[.]ts$/;\r\nconst KEY_URI_PATTERN = /#EXT-X-KEY:METHOD=AES-128,URI=\"(.+?)\",IV=/;\r\nconst END_TAG = \"#EXT-X-ENDLIST\";\r\nconst EXTINF = \"#EXTINF:1.25,\";\r\nfunction is_m3u8_preview_request(method, url) {\r\n    if (method !== \"GET\") {\r\n        return false;\r\n    }\r\n    const is_preview = url.endsWith(\"_i_preview.m3u8\");\r\n    if (is_preview) {\r\n        _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(\"Preview request detected:\", url);\r\n    }\r\n    return is_preview;\r\n}\r\nfunction is_ts_url(url) {\r\n    return /_i[0-9]+[.]ts$/.test(url);\r\n}\r\nfunction extract_index(ts_url) {\r\n    // ['_i7.ts', '7']\r\n    const matches = ts_url.match(TS_URL_PATTERN);\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.assert(matches != null, `无法从 ts url 中提取序号: ${ts_url}`);\r\n    const index = parseInt(matches[1]);\r\n    return index;\r\n}\r\nasync function find_max_index(test, high) {\r\n    let low = 0;\r\n    while (high - low > 1) {\r\n        const mid = Math.floor((low + high) / 2);\r\n        if (await test(mid)) {\r\n            low = mid;\r\n        }\r\n        else {\r\n            high = mid;\r\n        }\r\n    }\r\n    return low;\r\n}\r\nfunction get_common_prefix(ts_url) {\r\n    const sep = \"_i\";\r\n    return ts_url.split(sep).slice(0, -1).join(sep);\r\n}\r\nfunction create_video_record(common_prefix, index) {\r\n    return `${EXTINF}\\n${common_prefix}_i${index}.ts`;\r\n}\r\nfunction is_not_end_tag(line) {\r\n    return !line.includes(END_TAG);\r\n}\r\nfunction full_key_uri(m3u8, base_url) {\r\n    const matches = m3u8.match(KEY_URI_PATTERN);\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.assert(matches !== null, \"无法从 m3u8 文件中提取 key uri\");\r\n    const key_uri = matches[1];\r\n    return `${base_url}/${key_uri}`;\r\n}\r\nasync function create_full_m3u8(create_test, m3u8, base_url) {\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(`原始 m3u8 内容:\\n${m3u8}`);\r\n    const lines = m3u8\r\n        .split(\"\\n\")\r\n        .filter(_utils__WEBPACK_IMPORTED_MODULE_0__.Utils.is_not_empty)\r\n        .filter(is_not_end_tag);\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(\"过滤后的 m3u8 内容:\\n\" + lines.join(\"\\n\"));\r\n    const ts_url = _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.find_last(lines, is_ts_url);\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.assert(ts_url != null, \"无法在 m3u8 文件中找到 ts 文件的 URL\");\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(\"最后一个 ts 文件的 URL:\", ts_url);\r\n    const upper_limit = 10000;\r\n    const test = create_test(ts_url.replace(TS_URL_PATTERN, \"_i{index}.ts\"));\r\n    const end = await find_max_index(test, upper_limit);\r\n    const begin = extract_index(ts_url) + 1;\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(\"找到的最大 ts 序号:\", end);\r\n    const prefix = get_common_prefix(ts_url);\r\n    const video_records = Array\r\n        .from(_utils__WEBPACK_IMPORTED_MODULE_0__.Utils.range(begin, end + 1))\r\n        .map(create_video_record.bind(null, prefix));\r\n    const new_lines = [\r\n        ...lines,\r\n        ...video_records,\r\n        END_TAG\r\n    ];\r\n    const full_m3u8 = new_lines.join(\"\\n\");\r\n    const full_uri = full_key_uri(m3u8, base_url);\r\n    _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(\"完整的 KEY URI: \", full_uri);\r\n    const full_m3u8_with_key = full_m3u8.replace(KEY_URI_PATTERN, `#EXT-X-KEY:METHOD=AES-128,URI=\"${full_uri}\",IV=`);\r\n    return full_m3u8_with_key;\r\n}\r\n\n\n//# sourceURL=webpack://haijiao-helper/./src/core.ts?");

/***/ }),

/***/ "./src/main.ts":
/*!*********************!*\
  !*** ./src/main.ts ***!
  \*********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./core */ \"./src/core.ts\");\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./utils */ \"./src/utils.ts\");\n/* harmony import */ var _xhr_hook__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./xhr_hook */ \"./src/xhr_hook.ts\");\n\r\n\r\n\r\nfunction create_test(url_without_index) {\r\n    async function test(index) {\r\n        const url = url_without_index.replace(\"{index}\", `${index}`);\r\n        try {\r\n            const response = await fetch(url, { method: \"HEAD\" });\r\n            return response.ok;\r\n        }\r\n        catch (error) {\r\n            // Utils.error(error as Error);\r\n            return false;\r\n        }\r\n    }\r\n    return test;\r\n}\r\nfunction run_at_start() {\r\n    _utils__WEBPACK_IMPORTED_MODULE_1__.Utils.info(\"【haijiao-pro-video-crack】已启动\");\r\n    _xhr_hook__WEBPACK_IMPORTED_MODULE_2__.XHRHook.hook(_core__WEBPACK_IMPORTED_MODULE_0__.is_m3u8_preview_request, _core__WEBPACK_IMPORTED_MODULE_0__.create_full_m3u8.bind(null, create_test));\r\n}\r\nfunction run_at_idle() {\r\n}\r\nfunction main() {\r\n    document.addEventListener(\"DOMContentLoaded\", run_at_idle, true);\r\n    run_at_start();\r\n}\r\nmain();\r\n\n\n//# sourceURL=webpack://haijiao-helper/./src/main.ts?");

/***/ }),

/***/ "./src/utils.ts":
/*!**********************!*\
  !*** ./src/utils.ts ***!
  \**********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   Utils: () => (/* binding */ Utils)\n/* harmony export */ });\nconst BTN_STYLE = `\n    position: fixed;\n    left: 1em;\n    width: 4em;\n    top: 50%;\n    height: 3em;\n    border-radius: 50%;\n    text-align: center;\n    font-size: 1.5em;\n    background-color: white;\n`;\r\nclass Utils {\r\n    constructor() { }\r\n    static now() {\r\n        const _now = new Date()\r\n            .toISOString()\r\n            .slice(11, 23)\r\n            .replace('T', ' ')\r\n            .replace('Z', '');\r\n        return _now;\r\n    }\r\n    static info(...args) {\r\n        console.info(`[${Utils.now()}]`, ...args);\r\n    }\r\n    static error(error) {\r\n        console.info(`[${Utils.now()}]`, error);\r\n    }\r\n    static access_xhr_resp_text(xhr, get_text) {\r\n        Object.defineProperty(xhr, \"response\", {\r\n            enumerable: true,\r\n            configurable: false,\r\n            get: get_text\r\n        });\r\n    }\r\n    static *reversed(iterable) {\r\n        const arr = Array.from(iterable);\r\n        for (let i = arr.length - 1; i >= 0; i--) {\r\n            yield arr[i];\r\n        }\r\n    }\r\n    static find(iterable, predicate) {\r\n        for (const item of iterable) {\r\n            if (predicate(item)) {\r\n                return item;\r\n            }\r\n        }\r\n        return null;\r\n    }\r\n    static find_last(iterable, predicate) {\r\n        const reversed = Utils.reversed(iterable);\r\n        return Utils.find(reversed, predicate);\r\n    }\r\n    static assert(condition, message) {\r\n        if (!condition) {\r\n            throw new Error(message || 'Assertion failed');\r\n        }\r\n    }\r\n    static is_not_empty(text) {\r\n        return text !== null\r\n            && text !== undefined\r\n            && text.trim().length > 0;\r\n    }\r\n    static any(iterable, predicate) {\r\n        for (const item of iterable) {\r\n            if (predicate(item)) {\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n    static str_arr(arr) {\r\n        const body = arr\r\n            .map((item) => `\"${item}\"`)\r\n            .join(\", \");\r\n        return `[${body}]`;\r\n    }\r\n    static *range(start, end, step = 0) {\r\n        const _step = (step === 0 ? 1 : step);\r\n        const nums = [start, end, _step];\r\n        const nums_are_valid = nums.every(Number.isFinite);\r\n        Utils.assert(nums_are_valid, `每个数字都必须是有限的! ${Utils.str_arr(nums)}`);\r\n        for (let i = start; i < end; i += _step) {\r\n            yield i;\r\n        }\r\n    }\r\n    static combine_onload(prev, post) {\r\n        Utils.info('combine_onload', prev, post);\r\n        return async function (ev) {\r\n            await prev.call(this, ev);\r\n            await post.call(this, ev);\r\n        };\r\n    }\r\n    static get_video_title() {\r\n        var _a, _b, _c;\r\n        return (_c = (_b = (_a = document\r\n            .querySelector(\"#details-page h2\")) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.slice(0, 6)) !== null && _c !== void 0 ? _c : \"video\";\r\n    }\r\n    static add_save_m3u8_btn(m3u8, fname) {\r\n        const blob = new Blob([m3u8], { type: 'application/x-mpegURL' });\r\n        const url = URL.createObjectURL(blob);\r\n        Utils.info('完整 m3u8 blob url:', url);\r\n        const a = document.createElement('a');\r\n        a.href = url;\r\n        a.download = `${fname}.m3u8`;\r\n        a.textContent = \"保存 m3u8\";\r\n        a.setAttribute(\"style\", BTN_STYLE);\r\n        document.body.insertAdjacentElement(\"afterbegin\", a);\r\n    }\r\n    static get_parent_path(url) {\r\n        return url\r\n            .split(\"/\")\r\n            .slice(0, -1)\r\n            .join(\"/\");\r\n    }\r\n}\r\n\n\n//# sourceURL=webpack://haijiao-helper/./src/utils.ts?");

/***/ }),

/***/ "./src/xhr_hook.ts":
/*!*************************!*\
  !*** ./src/xhr_hook.ts ***!
  \*************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   XHRHook: () => (/* binding */ XHRHook)\n/* harmony export */ });\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ \"./src/utils.ts\");\n\r\nclass XHRHook {\r\n    constructor() { }\r\n    static hook(is_target, change_result) {\r\n        const xhr_proto = XMLHttpRequest.prototype;\r\n        const _open = xhr_proto.open;\r\n        xhr_proto.open = function (method, url) {\r\n            const xhr = this;\r\n            if (!is_target(method, url)) {\r\n                return _open.call(xhr, method, url, true);\r\n            }\r\n            const base_url = _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.get_parent_path(url);\r\n            _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(\"提取的 base_url:\", url);\r\n            async function change_video_src(_) {\r\n                _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.info(\"响应文本:\", xhr.responseText);\r\n                const new_m3u8 = await change_result(xhr.responseText, base_url);\r\n                _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.add_save_m3u8_btn(new_m3u8, _utils__WEBPACK_IMPORTED_MODULE_0__.Utils.get_video_title());\r\n            }\r\n            ;\r\n            xhr.addEventListener(\"load\", change_video_src, true);\r\n            return _open.call(xhr, method, url, true);\r\n        };\r\n    }\r\n}\r\nXHRHook.text = \"\";\r\n\n\n//# sourceURL=webpack://haijiao-helper/./src/xhr_hook.ts?");

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/make namespace object */
/******/ 	(() => {
/******/ 		// define __esModule on exports
/******/ 		__webpack_require__.r = (exports) => {
/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 			}
/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
/******/ 		};
/******/ 	})();
/******/ 	
/************************************************************************/
/******/ 	
/******/ 	// startup
/******/ 	// Load entry module and return exports
/******/ 	// This entry module can't be inlined because the eval devtool is used.
/******/ 	var __webpack_exports__ = __webpack_require__("./src/main.ts");
/******/ 	
/******/ })()
;