diff --git a/.env.development b/.env.development
index 6edf51d2..6aba1651 100644
--- a/.env.development
+++ b/.env.development
@@ -34,6 +34,7 @@ QQ_SMTP_AUTH_CODE =
# 调试猫源-推荐开启
CAT_DEBUG=1
+PHP_PATH=
PYTHON_PATH=
VIRTUAL_ENV=
daemonMode=0
diff --git a/.gitignore b/.gitignore
index 78c5d859..7bbca429 100644
--- a/.gitignore
+++ b/.gitignore
@@ -159,3 +159,6 @@ dist
/scripts/mjs/index.db
/scripts/test/rsa-test.json
/apps/salary/
+/jx/_30wmv.js
+.DS_Store
+/spider/catvod/mtv60w[差].js
diff --git a/.plugins.example.js b/.plugins.example.js
index c61674e6..6b429d01 100644
--- a/.plugins.example.js
+++ b/.plugins.example.js
@@ -35,14 +35,14 @@ const plugins = [
path: 'plugins/pup-sniffer', // 插件路径
params: '-port 57573', // 启动参数:端口57573
desc: 'drplayer嗅探服务', // 插件描述:提供视频适配代理功能
- active: true // 是否激活:true表示启用此插件
+ active: false // 是否激活:true表示启用此插件
},
{
name: 'mediaProxy', // 插件名称
path: 'plugins/mediaProxy', // 插件路径
params: '-port 57574', // 启动参数:端口57574
desc: 'go媒体代理服务', // 插件描述:提供视频适配代理功能
- active: true // 是否激活:true表示启用此插件
+ active: false // 是否激活:true表示启用此插件
},
]
diff --git a/Dockerfile b/Dockerfile
index 0377fc19..63bb1162 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -43,13 +43,28 @@ RUN cp /app/.env.development /app/.env && \
# 但是,我们仍然需要安装Node.js运行时本身(除非drpys项目是一个纯静态资源服务,不需要Node.js运行时)
RUN apk add --no-cache nodejs
+# 安装php8.3及其扩展
+RUN apk add --no-cache \
+ php83 \
+ php83-cli \
+ php83-curl \
+ php83-mbstring \
+ php83-xml \
+ php83-pdo \
+ php83-pdo_mysql \
+ php83-pdo_sqlite \
+ php83-openssl \
+ php83-sqlite3 \
+ php83-json
+RUN ln -sf /usr/bin/php83 /usr/bin/php
+
# 安装python3依赖
RUN apk add --no-cache python3 \
py3-pip \
py3-setuptools \
py3-wheel
-# 激活python3虚拟环境并安装依pip3赖
+# 激活python3虚拟环境并安装requirements依赖
RUN python3 -m venv /app/.venv && \
. /app/.venv/bin/activate && \
pip3 install -r /app/spider/py/base/requirements.txt
diff --git a/README.md b/README.md
index c122d8cf..558b3c0e 100644
--- a/README.md
+++ b/README.md
@@ -11,15 +11,20 @@ nodejs作为服务端的drpy实现。全面升级异步写法
### 常用超链接
* [本项目主页-免翻](https://github.com/hjdhnx/drpy-node)
-* [接口文档](docs/apidoc.md) | [接口列表如定时任务](docs/apiList.md) | [小猫影视-待对接T4](https://github.com/waifu-project/movie/pull/135)
+* ~~[最新DS本地包-适配皮卡丘](/gh/release)~~
+* [DS本地包下载中心](/admin/download)
+* [接口文档](docs/apidoc.md) | [接口列表如定时任务](docs/apiList.md) |
+ ~~[小猫影视-待对接T4](https://github.com/waifu-project/movie/pull/135)~~
* [代码质量评估工具说明](docs/codeCheck.md) | [DS项目代码评估报告](docs/codeCheckReport.md)
* [本地配置接口-动态本地](/config?healthy=1&pwd=$pwd)
* [本地配置接口-动态外网/局域网](/config/1?healthy=1&pwd=$pwd)
* [其他配置接口-订阅过滤](/docs/sub.md)
* [python环境](/docs/pyenv.md) | [DS项目环境变量说明](/docs/envdoc.md)
+* php环境(详见 spider/php/readme.md) 不在这里赘述
* [猫源调试教程](/docs/catDebug.md)
* [接口压测教程](/docs/httpTest.md)
* [AI编程工具 trae](https://www.trae.ai/account-setting#subscription) | 邮编ZIP输入: 518000
+* [推荐使用AI模型-GLM4.7](https://www.bigmodel.cn/glm-coding?ic=DRV3C8M5NX) | [GLM配置文档](https://docs.bigmodel.cn/cn/coding-plan/tool/trae)
* [免费AI-360纳米](https://bot.n.cn/)|[免费AI-当贝AI](https://ai.dangbei.com/chat)|[国外聚合全模型](https://lmarena.ai/)
* [本站防止爬虫协议](/robots.txt)
* [油猴脚本-反切屏检测](/public/monkey/check_screen_leave.user.js)
@@ -42,6 +47,7 @@ nodejs作为服务端的drpy实现。全面升级异步写法
* [DS时钟插件-白色时钟](/apps/clock/white_clock.html)|[日历时钟](/apps/clock/index.html)
* [DS庆祝页面-完结撒花](/apps/happy/index.html)
* [bookReader](/apps/book-reader)
+* [系统备份与恢复](/apps/backup-restore/index.html)
* [代码加解密工具](/admin/encoder)
* [央视点播解析工具](/proxy/央视大全[官]/index.html)
* [在线猫ds源主页](/cat/index.html)
@@ -60,32 +66,25 @@ nodejs作为服务端的drpy实现。全面升级异步写法
* [酷9](https://wwbty.lanzouv.com/iGoUV3d3hxuf)
* [千寻](https://wwbty.lanzouv.com/iSSN93d3hyzg)
+* [皮卡丘](https://github.com/ingriddaleusag-dotcom/PeekPiliRelease)
## 更新记录
-### 20260112
+### 20260131
-更新至V1.3.15
+更新至V1.3.21
-### 20251017
+### 20260127
-更新至V1.3.14
+更新至V1.3.20
-### 20251015
+### 20260125
-更新至V1.3.13
+更新至V1.3.19
-### 20251014
+### 20260118
-更新至V1.3.12
-
-### 20251013
-
-更新至V1.3.11
-
-### 20251012
-
-更新至V1.3.10
+更新至V1.3.18
[点此查看完整更新记录](docs/updateRecord.md)
@@ -189,6 +188,7 @@ pm2 restart drpys
* [源动力-老](https://sourcepower.top/index)
* [电竞专业反应测试](https://www.arealme.com/brain-memory-game/zh/)
* [桌面启动器](https://wwbty.lanzouv.com/iDZaP3d3i5ud)
+* [不知名获取网盘CK工具](http://sspa8.top:8100/pan/admin/index.php)
## AI接入
diff --git a/apps/backup-restore/index.html b/apps/backup-restore/index.html
new file mode 100644
index 00000000..98a03231
--- /dev/null
+++ b/apps/backup-restore/index.html
@@ -0,0 +1,373 @@
+
+
+
+
@@ -19,10 +20,13 @@
@@ -93,28 +97,67 @@
const textValue = document.getElementById("cookie-res").value || '';
const active_name = activeLi.textContent.trim();
const active_key = activeLi.getAttribute('data-platform').trim();
- const save_key = active_key === 'ali' ? active_key + '_token' : active_key + '_cookie';
+ let save_key = ''
+ if (/ali|pikpak/.test(active_key)) {
+ save_key = active_key + '_token'
+ } else {
+ save_key = active_key + '_cookie';
+ }
+
console.log(`准备入库cookie:${active_name} ${save_key},值为:${textValue}`);
const cookie_auth_code = prompt('cookie入库功能需要管理员授权码,请你正确输入后继续');
if (cookie_auth_code) {
- // 使用 axios 发送 POST 请求
- axios.post('/admin/cookie-set', {
- cookie_auth_code: cookie_auth_code,
- key: save_key,
- value: textValue.trim().replaceAll('\n', '')
- })
- .then(response => {
+ if (save_key.includes('pikpak')) {
+ // 使用 axios 发送 POST 请求
+ axios.post('/admin/cookie-set', {
+ cookie_auth_code: cookie_auth_code,
+ key: save_key,
+ value: textValue.split(';')[0].trim().replaceAll('\n', '')
+ }).then(response => {
if (response.data.success) {
alert(`Cookie 入库成功:${active_name} (${save_key})`);
} else {
alert(`入库失败:${response.data.message}`);
}
- })
- .catch(error => {
+ }).catch(error => {
+ console.error('请求失败:', error);
+ alert(`入库失败,服务器出现问题,请稍后再试。\n${error.response.data.message}`);
+ });
+ axios.post('/admin/cookie-set', {
+ cookie_auth_code: cookie_auth_code,
+ key: save_key.replace('token', 'refresh_token'),
+ value: textValue.split(';')[1].trim().replaceAll('\n', '')
+ }).then(response => {
+ if (response.data.success) {
+ alert(`Cookie 入库成功:${active_name} (${save_key.replace('token', 'refresh_token')})`);
+ } else {
+ alert(`入库失败:${response.data.message}`);
+ }
+ }).catch(error => {
console.error('请求失败:', error);
alert(`入库失败,服务器出现问题,请稍后再试。\n${error.response.data.message}`);
});
+ } else {
+ // 使用 axios 发送 POST 请求
+ axios.post('/admin/cookie-set', {
+ cookie_auth_code: cookie_auth_code,
+ key: save_key,
+ value: textValue.trim().replaceAll('\n', '')
+ })
+ .then(response => {
+ if (response.data.success) {
+ alert(`Cookie 入库成功:${active_name} (${save_key})`);
+ } else {
+ alert(`入库失败:${response.data.message}`);
+ }
+ })
+ .catch(error => {
+ console.error('请求失败:', error);
+ alert(`入库失败,服务器出现问题,请稍后再试。\n${error.response.data.message}`);
+ });
+ }
+
}
} else {
alert('至少选中一个cookie入库项目');
diff --git a/apps/cookie-butler/static/js/cookie.js b/apps/cookie-butler/static/js/cookie.js
index bff8f94a..f01be452 100644
--- a/apps/cookie-butler/static/js/cookie.js
+++ b/apps/cookie-butler/static/js/cookie.js
@@ -20,7 +20,7 @@ function showToast(message, type = 'success') {
// 初始化页面
async function initializePage() {
// 加载cookie
- const platforms = ['ali', 'quark', 'uc', 'uc_token', 'bili', 'baidu'];
+ const platforms = ['ali', 'quark', 'quark_token', 'uc', 'uc_token', 'bili', 'yun', 'baidu', 'pikpak'];
// 绑定按钮事件
platforms.forEach(platform => {
diff --git a/apps/cookie-butler/static/js/jsencrypt.min.js b/apps/cookie-butler/static/js/jsencrypt.min.js
new file mode 100644
index 00000000..27c29d5e
--- /dev/null
+++ b/apps/cookie-butler/static/js/jsencrypt.min.js
@@ -0,0 +1 @@
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.JSEncrypt={})}(this,function(t){"use strict";var e="0123456789abcdefghijklmnopqrstuvwxyz";function a(t){return e.charAt(t)}function i(t,e){return t&e}function u(t,e){return t|e}function r(t,e){return t^e}function n(t,e){return t&~e}function s(t){if(0==t)return-1;var e=0;return 0==(65535&t)&&(t>>=16,e+=16),0==(255&t)&&(t>>=8,e+=8),0==(15&t)&&(t>>=4,e+=4),0==(3&t)&&(t>>=2,e+=2),0==(1&t)&&++e,e}function o(t){for(var e=0;0!=t;)t&=t-1,++e;return e}var h="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";function c(t){var e,i,r="";for(e=0;e+3<=t.length;e+=3)i=parseInt(t.substring(e,e+3),16),r+=h.charAt(i>>6)+h.charAt(63&i);for(e+1==t.length?(i=parseInt(t.substring(e,e+1),16),r+=h.charAt(i<<2)):e+2==t.length&&(i=parseInt(t.substring(e,e+2),16),r+=h.charAt(i>>2)+h.charAt((3&i)<<4));0<(3&r.length);)r+="=";return r}function f(t){var e,i="",r=0,n=0;for(e=0;e
>2),n=3&s,r=1):1==r?(i+=a(n<<2|s>>4),n=15&s,r=2):2==r?(i+=a(n),i+=a(s>>2),n=3&s,r=3):(i+=a(n<<2|s>>4),i+=a(15&s),r=0))}return 1==r&&(i+=a(n<<2)),i}var l,p=function(t,e){return(p=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var i in e)e.hasOwnProperty(i)&&(t[i]=e[i])})(t,e)};var g,d=function(t){var e;if(void 0===l){var i="0123456789ABCDEF",r=" \f\n\r\t \u2028\u2029";for(l={},e=0;e<16;++e)l[i.charAt(e)]=e;for(i=i.toLowerCase(),e=10;e<16;++e)l[i.charAt(e)]=e;for(e=0;e>16,r[r.length]=n>>8&255,r[r.length]=255&n,s=n=0):n<<=6}}switch(s){case 1:throw new Error("Base64 encoding incomplete: at least 2 bits missing");case 2:r[r.length]=n>>10;break;case 3:r[r.length]=n>>16,r[r.length]=n>>8&255}return r},re:/-----BEGIN [^-]+-----([A-Za-z0-9+\/=\s]+)-----END [^-]+-----|begin-base64[^\n]+\n([A-Za-z0-9+\/=\s]+)====/,unarmor:function(t){var e=v.re.exec(t);if(e)if(e[1])t=e[1];else{if(!e[2])throw new Error("RegExp out of sync");t=e[2]}return v.decode(t)}},m=1e13,y=function(){function t(t){this.buf=[+t||0]}return t.prototype.mulAdd=function(t,e){var i,r,n=this.buf,s=n.length;for(i=0;ie&&(t=t.substring(0,e)+b),t}var w,D=function(){function i(t,e){this.hexDigits="0123456789ABCDEF",t instanceof i?(this.enc=t.enc,this.pos=t.pos):(this.enc=t,this.pos=e)}return i.prototype.get=function(t){if(void 0===t&&(t=this.pos++),t>=this.enc.length)throw new Error("Requesting byte offset "+t+" on a stream of length "+this.enc.length);return"string"==typeof this.enc?this.enc.charCodeAt(t):this.enc[t]},i.prototype.hexByte=function(t){return this.hexDigits.charAt(t>>4&15)+this.hexDigits.charAt(15&t)},i.prototype.hexDump=function(t,e,i){for(var r="",n=t;n>u&1?"1":"0";if(s.length>i)return n+E(s,i)}return n+s},i.prototype.parseOctetString=function(t,e,i){if(this.isASCII(t,e))return E(this.parseStringISO(t,e),i);var r=e-t,n="("+r+" byte)\n";(i/=2)i)return E(r,i);n=new y,s=0}}return 0>6,this.tagConstructed=0!=(32&e),this.tagNumber=31&e,31==this.tagNumber){for(var i=new y;e=t.get(),i.mulAdd(128,127&e),128&e;);this.tagNumber=i.simplify()}}return t.prototype.isUniversal=function(){return 0===this.tagClass},t.prototype.isEOC=function(){return 0===this.tagClass&&0===this.tagNumber},t}(),B=[2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997],A=(1<<26)/B[B.length-1],O=function(){function b(t,e,i){null!=t&&("number"==typeof t?this.fromNumber(t,e,i):null==e&&"string"!=typeof t?this.fromString(t,256):this.fromString(t,e))}return b.prototype.toString=function(t){if(this.s<0)return"-"+this.negate().toString(t);var e;if(16==t)e=4;else if(8==t)e=3;else if(2==t)e=1;else if(32==t)e=5;else{if(4!=t)return this.toRadix(t);e=2}var i,r=(1<>h)&&(n=!0,s=a(i));0<=o;)h>(h+=this.DB-e)):(i=this[o]>>(h-=e)&r,h<=0&&(h+=this.DB,--o)),0>24},b.prototype.shortValue=function(){return 0==this.t?this.s:this[0]<<16>>16},b.prototype.signum=function(){return this.s<0?-1:this.t<=0||1==this.t&&this[0]<=0?0:1},b.prototype.toByteArray=function(){var t=this.t,e=[];e[0]=this.s;var i,r=this.DB-t*this.DB%8,n=0;if(0>r)!=(this.s&this.DM)>>r&&(e[n++]=i|this.s<>(r+=this.DB-8)):(i=this[t]>>(r-=8)&255,r<=0&&(r+=this.DB,--t)),0!=(128&i)&&(i|=-256),0==n&&(128&this.s)!=(128&i)&&++n,(0=this.t?0!=this.s:0!=(this[e]&1<>n-a&u:(f=(t[p]&(1<>this.DB+n-a)),h=i;0==(1&f);)f>>=1,--h;if((n-=h)<0&&(n+=this.DB,--p),g)o[f].copyTo(s),g=!1;else{for(;1this.DB?(this[this.t-1]|=(o&(1<>this.DB-s):this[this.t-1]|=o<=this.DB&&(s-=this.DB))}8==i&&0!=(128&+t[0])&&(this.s=-1,0>r|o,o=(this[h]&n)<=this.t)e.t=0;else{var r=t%this.DB,n=this.DB-r,s=(1<>r;for(var o=i+1;o>r;0>=this.DB;if(t.t>=this.DB;r+=this.s}else{for(r+=this.s;i>=this.DB;r-=t.s}e.s=r<0?-1:0,r<-1?e[i++]=this.DV+r:0=e.DV&&(t[i+e.t]-=e.DV,t[i+e.t+1]=1)}0>this.F2:0),l=this.FV/f,p=(1<=i&&(this.dMultiply(r),this.dAddOffset(o,0),o=s=0))}0t&&this.subTo(b.ONE.shiftLeft(t-1),this);else{var r=[],n=7&t;r.length=1+(t>>3),e.nextBytes(r),0>=this.DB;if(t.t>=this.DB;r+=this.s}else{for(r+=this.s;i>=this.DB;r+=t.s}e.s=r<0?-1:0,0=this.DV;)this[e]-=this.DV,++e>=this.t&&(this[this.t++]=0),++this[e]}},b.prototype.multiplyLowerTo=function(t,e,i){var r=Math.min(this.t+t.t,e);for(i.s=0,i.t=r;0>1)&&(t=B.length);for(var n=M(),s=0;st&&n.subTo(b.ONE.shiftLeft(t-1),n),n.isProbablePrime(e)?setTimeout(function(){r()},0):setTimeout(s,0)};setTimeout(s,0)}else{var o=[],h=7&t;o.length=1+(t>>3),e.nextBytes(o),0>15,this.um=(1<>15)*this.mpl&this.um)<<15)&t.DM;for(t[i=e+this.m.t]+=this.m.am(0,r,t,e,0,this.m.t);t[i]>=t.DV;)t[i]-=t.DV,t[++i]++}t.clamp(),t.drShiftTo(this.m.t,t),0<=t.compareTo(this.m)&&t.subTo(this.m,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}(),P=function(){function t(t){this.m=t,this.r2=M(),this.q3=M(),O.ONE.dlShiftTo(2*t.t,this.r2),this.mu=this.r2.divide(t)}return t.prototype.convert=function(t){if(t.s<0||t.t>2*this.m.t)return t.mod(this.m);if(t.compareTo(this.m)<0)return t;var e=M();return t.copyTo(e),this.reduce(e),e},t.prototype.revert=function(t){return t},t.prototype.reduce=function(t){for(t.drShiftTo(this.m.t-1,this.r2),t.t>this.m.t+1&&(t.t=this.m.t+1,t.clamp()),this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3),this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2);t.compareTo(this.r2)<0;)t.dAddOffset(1,this.m.t+1);for(t.subTo(this.r2,t);0<=t.compareTo(this.m);)t.subTo(this.m,t)},t.prototype.mulTo=function(t,e,i){t.multiplyTo(e,i),this.reduce(i)},t.prototype.sqrTo=function(t,e){t.squareTo(e),this.reduce(e)},t}();function M(){return new O(null)}function q(t,e){return new O(t,e)}"Microsoft Internet Explorer"==navigator.appName?(O.prototype.am=function(t,e,i,r,n,s){for(var o=32767&e,h=e>>15;0<=--s;){var a=32767&this[t],u=this[t++]>>15,c=h*a+u*o;n=((a=o*a+((32767&c)<<15)+i[r]+(1073741823&n))>>>30)+(c>>>15)+h*u+(n>>>30),i[r++]=1073741823&a}return n},w=30):"Netscape"!=navigator.appName?(O.prototype.am=function(t,e,i,r,n,s){for(;0<=--s;){var o=e*this[t++]+i[r]+n;n=Math.floor(o/67108864),i[r++]=67108863&o}return n},w=26):(O.prototype.am=function(t,e,i,r,n,s){for(var o=16383&e,h=e>>14;0<=--s;){var a=16383&this[t],u=this[t++]>>14,c=h*a+u*o;n=((a=o*a+((16383&c)<<14)+i[r]+n)>>28)+(c>>14)+h*u,i[r++]=268435455&a}return n},w=28),O.prototype.DB=w,O.prototype.DM=(1<>>16)&&(t=e,i+=16),0!=(e=t>>8)&&(t=e,i+=8),0!=(e=t>>4)&&(t=e,i+=4),0!=(e=t>>2)&&(t=e,i+=2),0!=(e=t>>1)&&(t=e,i+=1),i}O.ZERO=F(0),O.ONE=F(1);var K=function(){function t(){this.i=0,this.j=0,this.S=[]}return t.prototype.init=function(t){var e,i,r;for(e=0;e<256;++e)this.S[e]=e;for(e=i=0;e<256;++e)i=i+this.S[e]+t[e%t.length]&255,r=this.S[e],this.S[e]=this.S[i],this.S[i]=r;this.i=0,this.j=0},t.prototype.next=function(){var t;return this.i=this.i+1&255,this.j=this.j+this.S[this.i]&255,t=this.S[this.i],this.S[this.i]=this.S[this.j],this.S[this.j]=t,this.S[t+this.S[this.i]&255]},t}();var k,_,z=256,Z=null;if(null==Z){Z=[];var G=void(_=0);if(window.crypto&&window.crypto.getRandomValues){var $=new Uint32Array(256);for(window.crypto.getRandomValues($),G=0;G<$.length;++G)Z[_++]=255&$[G]}var Y=function(t){if(this.count=this.count||0,256<=this.count||z<=_)window.removeEventListener?window.removeEventListener("mousemove",Y,!1):window.detachEvent&&window.detachEvent("onmousemove",Y);else try{var e=t.x+t.y;Z[_++]=255&e,this.count+=1}catch(t){}};window.addEventListener?window.addEventListener("mousemove",Y,!1):window.attachEvent&&window.attachEvent("onmousemove",Y)}function J(){if(null==k){for(k=new K;_>6|192):(i[--e]=63&n|128,i[--e]=n>>6&63|128,i[--e]=n>>12|224)}i[--e]=0;for(var s=new X,o=[];2>3);if(null==e)return null;var i=this.doPublic(e);if(null==i)return null;var r=i.toString(16);return 0==(1&r.length)?r:"0"+r},t.prototype.setPrivate=function(t,e,i){null!=t&&null!=e&&0>1;this.e=parseInt(e,16);for(var n=new O(e,16);;){for(;this.p=new O(t-r,1,i),0!=this.p.subtract(O.ONE).gcd(n).compareTo(O.ONE)||!this.p.isProbablePrime(10););for(;this.q=new O(r,1,i),0!=this.q.subtract(O.ONE).gcd(n).compareTo(O.ONE)||!this.q.isProbablePrime(10););if(this.p.compareTo(this.q)<=0){var s=this.p;this.p=this.q,this.q=s}var o=this.p.subtract(O.ONE),h=this.q.subtract(O.ONE),a=o.multiply(h);if(0==a.gcd(n).compareTo(O.ONE)){this.n=this.p.multiply(this.q),this.d=n.modInverse(a),this.dmp1=this.d.mod(o),this.dmq1=this.d.mod(h),this.coeff=this.q.modInverse(this.p);break}}},t.prototype.decrypt=function(t){var e=q(t,16),i=this.doPrivate(e);return null==i?null:function(t,e){var i=t.toByteArray(),r=0;for(;r=i.length)return null;var n="";for(;++r>3)},t.prototype.generateAsync=function(t,e,n){var s=new X,o=t>>1;this.e=parseInt(e,16);var h=new O(e,16),a=this,u=function(){var e=function(){if(a.p.compareTo(a.q)<=0){var t=a.p;a.p=a.q,a.q=t}var e=a.p.subtract(O.ONE),i=a.q.subtract(O.ONE),r=e.multiply(i);0==r.gcd(h).compareTo(O.ONE)?(a.n=a.p.multiply(a.q),a.d=h.modInverse(r),a.dmp1=a.d.mod(e),a.dmq1=a.d.mod(i),a.coeff=a.q.modInverse(a.p),setTimeout(function(){n()},0)):setTimeout(u,0)},i=function(){a.q=M(),a.q.fromNumberAsync(o,1,s,function(){a.q.subtract(O.ONE).gcda(h,function(t){0==t.compareTo(O.ONE)&&a.q.isProbablePrime(10)?setTimeout(e,0):setTimeout(i,0)})})},r=function(){a.p=M(),a.p.fromNumberAsync(t-o,1,s,function(){a.p.subtract(O.ONE).gcda(h,function(t){0==t.compareTo(O.ONE)&&a.p.isProbablePrime(10)?setTimeout(i,0):setTimeout(r,0)})})};setTimeout(r,0)};setTimeout(u,0)},t.prototype.sign=function(t,e,i){var r=function(t,e){if(e=e?t:new Array(e-t.length+1).join("0")+t},this.getString=function(){return this.s},this.setString=function(t){this.hTLV=null,this.isModified=!0,this.s=t,this.hV=stohex(t)},this.setByDateValue=function(t,e,i,r,n,s){var o=new Date(Date.UTC(t,e-1,i,r,n,s,0));this.setByDate(o)},this.getFreshValueHex=function(){return this.hV}},tt.lang.extend(et.asn1.DERAbstractTime,et.asn1.ASN1Object),et.asn1.DERAbstractStructured=function(t){et.asn1.DERAbstractString.superclass.constructor.call(this),this.setByASN1ObjectArray=function(t){this.hTLV=null,this.isModified=!0,this.asn1Array=t},this.appendASN1Object=function(t){this.hTLV=null,this.isModified=!0,this.asn1Array.push(t)},this.asn1Array=new Array,void 0!==t&&void 0!==t.array&&(this.asn1Array=t.array)},tt.lang.extend(et.asn1.DERAbstractStructured,et.asn1.ASN1Object),et.asn1.DERBoolean=function(){et.asn1.DERBoolean.superclass.constructor.call(this),this.hT="01",this.hTLV="0101ff"},tt.lang.extend(et.asn1.DERBoolean,et.asn1.ASN1Object),et.asn1.DERInteger=function(t){et.asn1.DERInteger.superclass.constructor.call(this),this.hT="02",this.setByBigInteger=function(t){this.hTLV=null,this.isModified=!0,this.hV=et.asn1.ASN1Util.bigIntToMinTwosComplementsHex(t)},this.setByInteger=function(t){var e=new O(String(t),10);this.setByBigInteger(e)},this.setValueHex=function(t){this.hV=t},this.getFreshValueHex=function(){return this.hV},void 0!==t&&(void 0!==t.bigint?this.setByBigInteger(t.bigint):void 0!==t.int?this.setByInteger(t.int):"number"==typeof t?this.setByInteger(t):void 0!==t.hex&&this.setValueHex(t.hex))},tt.lang.extend(et.asn1.DERInteger,et.asn1.ASN1Object),et.asn1.DERBitString=function(t){if(void 0!==t&&void 0!==t.obj){var e=et.asn1.ASN1Util.newObject(t.obj);t.hex="00"+e.getEncodedHex()}et.asn1.DERBitString.superclass.constructor.call(this),this.hT="03",this.setHexValueIncludingUnusedBits=function(t){this.hTLV=null,this.isModified=!0,this.hV=t},this.setUnusedBitsAndHexValue=function(t,e){if(t<0||7 {
- if (enable_dr2 === '1') {
+ if (enable_dr2 === '1' || enable_dr2 === '2') {
// dr2ApiType=0 使用接口drpy2 dr2ApiType=1 使用壳子内置的drpy2
let api = dr2ApiType ? `assets://js/lib/drpy2.js` : `${requestHost}/public/drpy/drpy2.min.js`;
+ if (enable_dr2 === '2') {
+ api = `${requestHost}/public/drpy/drpy2-fast.min.js`;
+ }
let ext = `${requestHost}/js/${file}`;
if (pwd) {
ext += `?pwd=${pwd}`;
@@ -348,31 +353,33 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
ext: ext || "", // 固定为空字符串
};
sites.push(site);
- } else if (enable_dr2 === '2') {
- // 模式2:只启用T3脚本的T4风格API配置
- const t4site = {
- key: fileSite.key,
- name: fileSite.name,
- type: 4, // 固定值
- api: `${requestHost}/api/${baseName}`,
- ...ruleMeta,
- ext: "", // 固定为空字符串
- };
- // 添加isdr2参数到API URL
- if (pwd) {
- t4site.api += `?pwd=${pwd}&do=dr`;
- } else {
- t4site.api += `?do=dr`;
- }
-
- // 处理传参源的API参数
- if (fileSite.queryStr) {
- const separator = t4site.api.includes('?') ? '&' : '?';
- site.api += `${separator}extend=${encodeURIComponent(fileSite.queryStr)}`;
- }
-
- sites.push(t4site);
}
+ // else if (enable_dr2 === '2') {
+ //
+ // // 模式2:只启用T3脚本的T4风格API配置
+ // const t4site = {
+ // key: fileSite.key,
+ // name: fileSite.name,
+ // type: 4, // 固定值
+ // api: `${requestHost}/api/${baseName}`,
+ // ...ruleMeta,
+ // ext: "", // 固定为空字符串
+ // };
+ // // 添加isdr2参数到API URL
+ // if (pwd) {
+ // t4site.api += `?pwd=${pwd}&do=dr`;
+ // } else {
+ // t4site.api += `?do=dr`;
+ // }
+ //
+ // // 处理传参源的API参数
+ // if (fileSite.queryStr) {
+ // const separator = t4site.api.includes('?') ? '&' : '?';
+ // site.api += `${separator}extend=${encodeURIComponent(fileSite.queryStr)}`;
+ // }
+ //
+ // sites.push(t4site);
+ // }
});
},
param: {file, dr2Dir, requestHost, pwd, drpyS, SitesMap},
@@ -412,6 +419,11 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
filterable: 1, // 固定值
quickSearch: 1, // 固定值
};
+ if (baseName.includes('[画]')) {
+ ruleObject.类型 = '漫画'
+ } else if (baseName.includes('[书]')) {
+ ruleObject.类型 = '小说'
+ }
let ruleMeta = {...ruleObject};
const filePath = path.join(pyDir, file);
const header = await FileHeaderManager.readHeader(filePath);
@@ -485,6 +497,68 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
await batchExecute(py_tasks, listener);
}
+
+ // 根据用户是否启用php源去生成对应配置
+ const enable_php = ENV.get('enable_php', '1');
+ console.log('isPhpAvailable:', isPhpAvailable);
+ if ((enable_php === '1' && isPhpAvailable) || enable_php === '2') {
+ const php_files = readdirSync(phpDir);
+ const api_type = enable_php === '2' ? 3 : 4;
+ let php_valid_files = php_files.filter((file) => file.endsWith('.php') && !file.startsWith('_') && !['config.php', 'index.php', 'test_runner.php'].includes(file));
+ log(`开始生成php的T${api_type}配置,phpDir:${phpDir},源数量: ${php_valid_files.length}`);
+
+ const php_tasks = php_valid_files.map((file) => {
+ return {
+ func: async ({file, phpDir, requestHost, pwd, SitesMap}) => {
+ const baseName = path.basename(file, '.php');
+ let api = enable_php === '2' ? `${requestHost}/php/${file}` : `${requestHost}/api/${baseName}?do=php`;
+ let ext = '';
+ if (pwd) {
+ api += enable_php === '2' ? `?pwd=${pwd}` : `&pwd=${pwd}`;
+ }
+ let ruleObject = {
+ searchable: 1,
+ filterable: 1,
+ quickSearch: 1,
+ };
+ if (baseName.includes('[画]')) {
+ ruleObject.类型 = '漫画'
+ } else if (baseName.includes('[书]')) {
+ ruleObject.类型 = '小说'
+ }
+ let ruleMeta = {...ruleObject};
+ const filePath = path.join(phpDir, file);
+
+ Object.assign(ruleMeta, {
+ title: baseName,
+ lang: 'php',
+ });
+ ruleMeta.title = enableRuleName ? ruleMeta.title || baseName : baseName;
+
+ let fileSites = [];
+ let key = `php_${ruleMeta.title}`;
+ let name = `${ruleMeta.title}(PHP)`;
+ fileSites.push({key, name, ext});
+
+ fileSites.forEach((fileSite) => {
+ const site = {
+ key: fileSite.key,
+ name: fileSite.name,
+ type: api_type,
+ api,
+ ...ruleMeta,
+ ext: fileSite.ext || "",
+ };
+ sites.push(site);
+ });
+ },
+ param: {file, phpDir, requestHost, pwd, SitesMap},
+ id: file,
+ };
+ });
+ await batchExecute(php_tasks, listener);
+ }
+
const enable_cat = ENV.get('enable_cat', '1');
// 根据用户是否启用cat源去生成对应配置
if (enable_cat === '1' || enable_cat === '2') {
@@ -515,6 +589,11 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
filterable: 1, // 固定值
quickSearch: 1, // 固定值
};
+ if (baseName.includes('[画]')) {
+ ruleObject.类型 = '漫画'
+ } else if (baseName.includes('[书]')) {
+ ruleObject.类型 = '小说'
+ }
let ruleMeta = {...ruleObject};
const filePath = path.join(catDir, file);
const header = await FileHeaderManager.readHeader(filePath);
@@ -655,78 +734,82 @@ async function generateSiteJSON(options, requestHost, sub, pwd) {
* @returns {Promise ', '状态');
+ let kid = cutStr(it, 'href="', '"', 'Id');
+ kvods.push({
+ vod_name: kname,
+ vod_pic: kpic,
+ vod_remarks: kremarks,
+ vod_id: `${kid}@${kname}@${kpic}@${kremarks}`
+ });
+ }
+ return kvods;
+ } catch (e) {
+ console.error(`生成视频列表失败:`, e.message);
+ return [];
+ }
+}
+
+async function detail(ids) {
+ try {
+ let [id, kname, kpic, kremarks] = ids.split('@');
+ let detailUrl = !/^http/.test(id) ? `${HOST}${id}` : id;
+ let resHtml = await request(detailUrl);
+ if (!resHtml) {throw new Error('源码为空');}
+ let intros = cutStr(resHtml, 'search-show', '', '', false);
+ let ktabs = pdfa(resHtml, '.anthology-tab&&a').map((it,idx) => cutStr(it, '', '<', `线路${idx+1}`));
+ let kurls = pdfa(resHtml, '.anthology-list-play').map(item => {
+ return pdfa(item, 'a').map(it => { return `${cutStr(it, '>', '<', 'noEpi')}$${cutStr(it, 'href="', '"', 'noUrl')}` }).join('#');
+ });
+ let VOD = {
+ vod_id: detailUrl,
+ vod_name: kname,
+ vod_pic: kpic,
+ type_name: cutStr(intros, '类型:', '', '类型'),
+ vod_remarks: `${cutStr(intros, '状态:', '', '状态')}|${cutStr(intros, '更新:', '', '更新')}`,
+ vod_year: cutStr(intros, '年份:', '', '1000'),
+ vod_area: cutStr(intros, '地区:', '', '地区'),
+ vod_lang: cutStr(intros, '语言:', '', '语言'),
+ vod_director: cutStr(intros, '导演:', '', '').replace(/,$/, '') || '导演',
+ vod_actor: cutStr(intros, '主演:', '', '').replace(/,$/, '') || '主演',
+ vod_content: cutStr(intros, '简介:', '', '') || kname,
+ vod_play_from: ktabs.join('$$$'),
+ vod_play_url: kurls.join('$$$')
+ };
+ return JSON.stringify({list: [VOD]});
+ } catch (e) {
+ console.error('详情页获取失败:', e.message);
+ return JSON.stringify({list: []});
+ }
+}
+
+async function play(flag, ids, flags) {
+ try {
+ let playUrl = !/^http/.test(ids) ? `${HOST}${ids}` : ids;
+ let kp = 0, kurl = '';
+ let resHtml = await request(playUrl);
+ let codeObj = safeParseJSON(cutStr(resHtml, 'var player_£=', '<', '', false));
+ let jurl = codeObj?.url ?? '';
+ jurl = safeUrlDecode(safeB64Decode(jurl));
+ if (jurl) {
+ jurl = `${HOST}/player/?url=${jurl}&next=`;
+ resHtml = await request(jurl);
+ let encryptedUrl = cutStr(resHtml, 'const encryptedUrl = "', '"', '');
+ let sessionKey = cutStr(resHtml, 'const sessionKey = "', '"', '');
+ kurl = urlAesDecrypt(encryptedUrl, sessionKey);
+ }
+ if (!/^http/.test(kurl)) {
+ kurl = playUrl;
+ kp = 1;
+ }
+ return JSON.stringify({jx: 0, parse: kp, url: kurl, header: DefHeader});
+ } catch (e) {
+ console.error('播放失败:', e.message);
+ return JSON.stringify({jx: 0, parse: 0, url: '', header: {}});
+ }
+}
+
+function urlAesDecrypt(ciphertext, key) {
+ try {
+ const rawData = Crypto.enc.Base64.parse(ciphertext);
+ const keyWordArr = Crypto.enc.Utf8.parse(key);
+ const ivWordArr = Crypto.lib.WordArray.create(rawData.words.slice(0, 4));
+ const encrypted = Crypto.lib.WordArray.create(rawData.words.slice(4));
+ const decrypted = Crypto.AES.decrypt( { ciphertext: encrypted }, keyWordArr,
+ {
+ iv: ivWordArr,
+ mode: Crypto.mode.CBC,
+ padding: Crypto.pad.Pkcs7
+ }
+ );
+ return decrypted.toString(Crypto.enc.Utf8);
+ } catch (e) {
+ return '';
+ }
+}
+
+function safeB64Decode(b64Str) {
+ try {return Crypto.enc.Utf8.stringify(Crypto.enc.Base64.parse(b64Str));} catch (e) {return '';}
+}
+
+function safeUrlDecode(urlStr) {
+ try {return decodeURIComponent(urlStr);} catch (e) {return '';}
+}
+
+function safeParseJSON(jStr) {
+ try {return JSON.parse(jStr);} catch (e) {return null;}
+}
+
+function cutStr(str, prefix = '', suffix = '', defaultVal = 'cutFaile', clean = true, i = 1, all = false) {
+ try {
+ if (typeof str !== 'string' || !str) {throw new Error('被截取对象需为非空字符串');}
+ const cleanStr = cs => String(cs).replace(/<[^>]*?>/g, ' ').replace(/( |\u00A0|\s)+/g, ' ').trim().replace(/\s+/g, ' ');
+ const esc = s => String(s).replace(/[.*+?${}()|[\]\\/^]/g, '\\$&');
+ let pre = esc(prefix).replace(/£/g, '[^]*?'), end = esc(suffix);
+ let regex = new RegExp(`${pre ? pre : '^'}([^]*?)${end ? end : '$'}`, 'g');
+ let matchIterator = str.matchAll(regex);
+ if (all) {
+ let matchArr = [...matchIterator];
+ return matchArr.length ? matchArr.map(it => {
+ const val = it[1] ?? defaultVal;
+ return clean && val !== defaultVal ? cleanStr(val) : val;
+ }) : [defaultVal];
+ }
+ i = parseInt(i, 10);
+ if (isNaN(i) || i < 1) {throw new Error('序号必须为正整数');}
+ let tgIdx = i - 1,matchIdx = 0;
+ for (const match of matchIterator) {
+ if (matchIdx++ === tgIdx) {
+ const result = match[1] ?? defaultVal;
+ return clean && result !== defaultVal ? cleanStr(result) : result;
+ }
+ }
+ return defaultVal;
+ } catch (e) {
+ console.error(`字符串截取失败:`, e.message);
+ return all ? ['cutErr'] : 'cutErr';
+ }
+}
+
+async function request(reqUrl, options = {}) {
+ try {
+ if (typeof reqUrl !== 'string' || !reqUrl.trim()) { throw new Error('reqUrl需为字符串且非空'); }
+ if (typeof options !== 'object' || Array.isArray(options) || options === null) { throw new Error('options类型需为非null对象'); }
+ options.method = options.method?.toUpperCase() || 'GET';
+ if (['GET', 'HEAD'].includes(options.method)) {
+ delete options.body;
+ delete options.data;
+ delete options.postType;
+ }
+ let {headers, timeout, buffer, ...restOpts} = options;
+ const optObj = {
+ headers: (typeof headers === 'object' && !Array.isArray(headers) && headers) ? headers : KParams.headers,
+ timeout: parseInt(timeout, 10) > 0 ? parseInt(timeout, 10) : KParams.timeout,
+ buffer: buffer ?? 0,
+ ...restOpts
+ };
+ const res = await req(reqUrl, optObj);
+ if (options.withHeaders) {
+ const resHeaders = typeof res.headers === 'object' && !Array.isArray(res.headers) && res.headers ? res.headers : {};
+ const resWithHeaders = { ...resHeaders, body: res?.content ?? '' };
+ return JSON.stringify(resWithHeaders);
+ }
+ return res?.content ?? '';
+ } catch (e) {
+ console.error(`${reqUrl}→请求失败:`, e.message);
+ return options?.withHeaders ? JSON.stringify({ body: '' }) : '';
+ }
+}
+
+export function __jsEvalReturn() {
+ return {
+ init,
+ home,
+ homeVod,
+ category,
+ search,
+ detail,
+ play,
+ proxy: null
+ };
+}
\ No newline at end of file
diff --git "a/spider/catvod/\345\244\256\345\244\256[\345\256\230].js" "b/spider/catvod/\345\244\256\345\244\256[\345\256\230].js"
new file mode 100644
index 00000000..478e4b10
--- /dev/null
+++ "b/spider/catvod/\345\244\256\345\244\256[\345\256\230].js"
@@ -0,0 +1,290 @@
+/**
+ * 央视大全 - 猫影视/TVBox JS爬虫格式
+ * 继承BaseSpider类
+ @header({
+ searchable: 1,
+ filterable: 1,
+ quickSearch: 1,
+ title: '央央[官]',
+ lang: 'cat'
+ })
+ */
+
+class Spider extends BaseSpider {
+
+ constructor() {
+ super();
+ this.host = 'https://api.cntv.cn';
+ this.siteName = '央视大全';
+ this.sessionStore = {};
+ this.videoCache = {};
+
+ this.headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
+ "Referer": "https://tv.cctv.com",
+ "Accept": "application/json, text/plain, */*",
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
+ };
+ }
+
+ init(extend = "") {
+ return "";
+ }
+
+ getName() {
+ return this.siteName;
+ }
+
+ isVideoFormat(url) {
+ return url.includes('.m3u8') || url.includes('.mp4');
+ }
+
+ manualVideoCheck() {
+ return false;
+ }
+
+ destroy() {
+ this.sessionStore = {};
+ this.videoCache = {};
+ }
+
+ homeContent(filter) {
+ const categories = [
+ {type_id: "栏目大全", type_name: "栏目大全"},
+ {type_id: "特别节目", type_name: "特别节目"},
+ {type_id: "纪录片", type_name: "纪录片"},
+ {type_id: "电视剧", type_name: "电视剧"},
+ {type_id: "动画片", type_name: "动画片"}
+ ];
+
+ return {class: categories};
+ }
+
+ async homeVideoContent() {
+ // 央视首页推荐
+ return {list: []};
+ }
+
+ async categoryContent(tid, pg, filter, extend) {
+ try {
+ const page = parseInt(pg) || 1;
+ const videos = [];
+
+ const channelMap = {
+ "特别节目": "CHAL1460955953877151",
+ "纪录片": "CHAL1460955924871139",
+ "电视剧": "CHAL1460955853485115",
+ "动画片": "CHAL1460955899450127",
+ };
+
+ let filterObj = {};
+ if (extend && typeof extend === 'object') {
+ filterObj = extend;
+ }
+
+ if (tid === '栏目大全') {
+ const url = `${this.host}/lanmu/columnSearch?&fl=&fc=&cid=&p=${page}&n=20&serviceId=tvcctv&t=json`;
+ const response = await this.fetch(url, {}, this.headers);
+ const data = response.data;
+
+ if (data && data.response && data.response.docs) {
+ const docs = data.response.docs;
+ docs.forEach(it => {
+ videos.push({
+ vod_id: `${it.lastVIDE.videoSharedCode}|${it.column_firstclass}|${it.column_name}|${it.channel_name}|${it.column_brief}|${it.column_logo}|${it.lastVIDE.videoTitle}|栏目大全`,
+ vod_name: it.column_name,
+ vod_pic: it.column_logo,
+ vod_remarks: it.channel_name,
+ vod_content: ''
+ });
+ });
+ }
+ } else {
+ // 处理筛选参数
+ let fl_url = `&channelid=${channelMap[tid] || ''}&fc=${encodeURIComponent(tid)}`;
+ if (filterObj.channel) fl_url += `&channel=${encodeURIComponent(filterObj.channel)}`;
+ if (filterObj.sc) fl_url += `&sc=${encodeURIComponent(filterObj.sc)}`;
+ if (filterObj.year) fl_url += `&year=${filterObj.year}`;
+
+ const url = `${this.host}/list/getVideoAlbumList?${fl_url}&area=&letter=&n=24&serviceId=tvcctv&t=json&p=${page}`;
+ const response = await this.fetch(url, {}, this.headers);
+ const data = response.data;
+
+ if (data && data.data && data.data.list) {
+ const dataList = data.data.list;
+ dataList.forEach(it => {
+ videos.push({
+ vod_id: `${it.id}|${it.sc}|${it.title}|${it.channel}|${it.brief}|${it.image}|${it.count}|${tid}`,
+ vod_name: it.title,
+ vod_pic: it.image,
+ vod_remarks: `${it.sc}${it.year ? '·' + it.year : ''}`,
+ vod_content: it.brief || ''
+ });
+ });
+ }
+ }
+
+ return {
+ list: videos,
+ page: page,
+ pagecount: 9999,
+ limit: 20,
+ total: 999999
+ };
+
+ } catch (error) {
+ console.error(`categoryContent error: ${error.message}`);
+ return {
+ list: [],
+ page: pg,
+ pagecount: 0,
+ limit: 20,
+ total: 0
+ };
+ }
+ }
+
+ async detailContent(ids) {
+ try {
+ const id = ids[0];
+ if (!id) return {list: []};
+
+ // 检查缓存
+ const cacheKey = `detail_${id}`;
+ if (this.videoCache[cacheKey]) {
+ return {list: [this.videoCache[cacheKey]]};
+ }
+
+ const info = id.split("|");
+ // ID 结构: 0:id, 1:sc, 2:title, 3:channel, 4:brief, 5:image, 6:count/remark, 7:cate
+
+ const cate = info[7];
+ const ctid = info[0];
+ const modeMap = {
+ "特别节目": "0",
+ "纪录片": "0",
+ "电视剧": "0",
+ "动画片": "1"
+ };
+
+ // 获取选集列表
+ let playUrls = [];
+ const mode = modeMap[cate] || '0';
+ const albumUrl = `${this.host}/NewVideo/getVideoListByAlbumIdNew?id=${ctid}&serviceId=tvcctv&p=1&n=100&mode=${mode}&pub=1`;
+
+ const response = await this.fetch(albumUrl, {}, this.headers);
+ const data = response.data;
+
+ if (data.errcode === '1001') {
+ // 需要获取真实的ctid
+ const videoInfoUrl = `${this.host}/video/videoinfoByGuid?guid=${ctid}&serviceId=tvcctv`;
+ const vInfoRes = await this.fetch(videoInfoUrl, {}, this.headers);
+ const vInfoData = vInfoRes.data;
+ const realCtid = vInfoData.ctid;
+
+ const columnUrl = `${this.host}/NewVideo/getVideoListByColumn?id=${realCtid}&d=&p=1&n=100&sort=desc&mode=0&serviceId=tvcctv&t=json`;
+ const colRes = await this.fetch(columnUrl, {}, this.headers);
+ const colData = colRes.data;
+ playUrls = colData.data?.list || [];
+ } else {
+ playUrls = data.data?.list || [];
+ }
+
+ // 构建播放列表
+ const playList = [];
+ if (playUrls.length > 0) {
+ for (const item of playUrls) {
+ const title = item.title || `第${item.index || '?'}集`;
+ const cleanTitle = title.replace(/\$/g, '');
+ const guid = item.guid || '';
+ playList.push(`${cleanTitle}$${guid}`);
+ }
+ }
+
+ const vod = {
+ vod_id: id,
+ vod_name: info[2] || '',
+ vod_pic: info[5] || '',
+ type_name: info[1] || '',
+ vod_year: '',
+ vod_area: '',
+ vod_remarks: info[6] ? `共${info[6]}集` : '',
+ vod_actor: '',
+ vod_director: '',
+ vod_content: info[4] || '',
+ vod_play_from: playList.length > 0 ? '央视频' : '',
+ vod_play_url: playList.length > 0 ? playList.join('#') : ''
+ };
+
+ // 缓存结果
+ this.videoCache[cacheKey] = vod;
+
+ return {list: [vod]};
+
+ } catch (error) {
+ console.error(`detailContent error: ${error.message}`);
+ return {list: []};
+ }
+ }
+
+ async searchContent(key, quick, pg = "1") {
+ // CCTV搜索接口较复杂,这里返回空结果
+ return {
+ list: [],
+ page: pg,
+ pagecount: 0,
+ limit: 20,
+ total: 0
+ };
+ }
+
+ async playerContent(flag, id, vipFlags) {
+ try {
+ // 央视视频采用直接播放的方式
+ // 根据GUID拼接m3u8地址
+ let playUrl = `https://cntv.playdreamer.cn/proxy/asp/hls/2000/0303000a/3/default/${id}/2000.m3u8`;
+
+ // 也可以尝试其他格式
+ // playUrl = `https://hls.cntv.myalicdn.com/asp/hls/2000/0303000a/3/default/${id}/2000.m3u8`;
+
+ return {
+ parse: 0, // 0表示直接播放,不需要解析
+ jx: 0, // 0表示不解析
+ url: playUrl,
+ header: JSON.stringify({
+ 'User-Agent': this.headers['User-Agent'],
+ 'Referer': 'https://tv.cctv.com',
+ 'Origin': 'https://tv.cctv.com'
+ })
+ };
+
+ } catch (error) {
+ console.error(`playerContent error: ${error.message}`);
+ return {
+ parse: 0,
+ jx: 0,
+ url: id,
+ header: JSON.stringify(this.headers)
+ };
+ }
+ }
+
+ localProxy(param) {
+ return null;
+ }
+
+ // 辅助方法:安全获取对象属性
+ getSafe(obj, path, defaultValue = '') {
+ if (!obj || typeof obj !== 'object') return defaultValue;
+ try {
+ return path.split('.').reduce((o, key) => {
+ if (o == null) return defaultValue;
+ return o[key];
+ }, obj) ?? defaultValue;
+ } catch {
+ return defaultValue;
+ }
+ }
+}
+
+export default new Spider();
\ No newline at end of file
diff --git "a/spider/catvod/\345\245\207\345\245\207[\345\256\230].js" "b/spider/catvod/\345\245\207\345\245\207[\345\256\230].js"
new file mode 100644
index 00000000..89ce8472
--- /dev/null
+++ "b/spider/catvod/\345\245\207\345\245\207[\345\256\230].js"
@@ -0,0 +1,403 @@
+/**
+ * 爱奇艺视频 - 猫影视/TVBox JS爬虫格式
+ * 调用壳子超级解析功能(壳子会自动读取json配置)
+ @header({
+ searchable: 1,
+ filterable: 1,
+ quickSearch: 1,
+ title: '奇奇[官]',
+ lang: 'cat'
+ })
+ */
+
+class Spider extends BaseSpider {
+
+ constructor() {
+ super();
+ this.host = 'https://www.iqiyi.com';
+ this.sessionStore = {};
+
+ this.headers = {
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
+ 'Referer': 'https://www.iqiyi.com',
+ 'Accept': 'application/json, text/plain, */*',
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
+ 'Accept-Encoding': 'gzip, deflate, br',
+ 'Connection': 'keep-alive'
+ };
+
+ // 分类配置
+ this.classes = [
+ {type_id: '1', type_name: '电影'},
+ {type_id: '2', type_name: '电视剧'},
+ {type_id: '6', type_name: '综艺'},
+ {type_id: '4', type_name: '动漫'},
+ {type_id: '3', type_name: '纪录片'},
+ {type_id: '5', type_name: '音乐'},
+ {type_id: '16', type_name: '网络电影'}
+ ];
+
+ // 筛选配置
+ this.filters = {
+ '1': [{
+ key: 'year',
+ name: '年代',
+ value: [{n: '全部', v: ''}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'}, {n: '2023', v: '2023'}]
+ }],
+ '2': [{
+ key: 'year',
+ name: '年代',
+ value: [{n: '全部', v: ''}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'}, {n: '2023', v: '2023'}]
+ }]
+ };
+ }
+
+ init(extend = '') {
+ return '';
+ }
+
+ getName() {
+ return '爱奇艺视频';
+ }
+
+ isVideoFormat(url) {
+ return true;
+ }
+
+ manualVideoCheck() {
+ return false;
+ }
+
+ destroy() {
+ // 清理资源
+ }
+
+ homeContent(filter) {
+ const result = {
+ class: this.classes,
+ filters: this.filters
+ };
+
+ return result;
+ }
+
+ homeVideoContent() {
+ return {list: []};
+ }
+
+ async categoryContent(tid, pg, filter, extend) {
+ try {
+ let channelId = tid;
+ let dataType = 1;
+ let extraParams = "";
+ const page = parseInt(pg) || 1;
+
+ if (tid === "16") {
+ channelId = "1";
+ extraParams = "&three_category_id=27401";
+ } else if (tid === "5") {
+ dataType = 2;
+ }
+
+ // 处理筛选条件
+ if (extend) {
+ let extendObj = {};
+ if (typeof extend === 'string') {
+ try {
+ extendObj = JSON.parse(extend);
+ } catch (e) {
+ // 如果不是JSON,尝试解析为key=value格式
+ extend.split('&').forEach(item => {
+ const [key, value] = item.split('=');
+ if (key && value) {
+ extendObj[key] = value;
+ }
+ });
+ }
+ } else if (typeof extend === 'object') {
+ extendObj = extend;
+ }
+
+ if (extendObj.year) {
+ extraParams += `&market_release_date_level=${extendObj.year}`;
+ }
+ }
+
+ const url = `https://pcw-api.iqiyi.com/search/recommend/list?channel_id=${channelId}&data_type=${dataType}&page_id=${page}&ret_num=20${extraParams}`;
+
+ const response = await this.fetch(url, {}, this.headers);
+ const jsonData = response.data;
+
+ const videos = [];
+ if (jsonData.data && jsonData.data.list) {
+ for (const item of jsonData.data.list) {
+ const vid = `${item.channelId}$${item.albumId}`;
+ let remarks = "";
+
+ if (item.channelId === 1) {
+ remarks = item.score ? `${item.score}分` : "";
+ } else if (item.channelId === 2 || item.channelId === 4) {
+ if (item.latestOrder && item.videoCount) {
+ remarks = item.latestOrder === item.videoCount ?
+ `${item.latestOrder}集全` :
+ `更新至${item.latestOrder}集`;
+ } else {
+ remarks = item.focus || "";
+ }
+ } else {
+ remarks = item.period || item.focus || "";
+ }
+
+ videos.push({
+ vod_id: vid,
+ vod_name: item.name,
+ vod_pic: item.imageUrl ? item.imageUrl.replace(".jpg", "_390_520.jpg") : "",
+ vod_remarks: remarks
+ });
+ }
+ }
+
+ return {
+ list: videos,
+ page: page,
+ pagecount: 9999,
+ limit: 20,
+ total: 999999
+ };
+
+ } catch (error) {
+ console.error(`categoryContent error: ${error.message}`);
+ return {
+ list: [],
+ page: pg,
+ pagecount: 0,
+ limit: 20,
+ total: 0
+ };
+ }
+ }
+
+ async getPlaylists(channelId, albumId, data) {
+ let playlists = [];
+ const cid = parseInt(channelId || data.channelId || 0);
+
+ try {
+ if (cid === 1 || cid === 5) {
+ // 电影或音乐
+ if (data.playUrl) {
+ playlists.push({title: data.name || '正片', url: data.playUrl});
+ }
+ } else if (cid === 6 && data.period) {
+ // 综艺
+ let qs = data.period.toString().split("-")[0];
+ let listUrl = `https://pcw-api.iqiyi.com/album/source/svlistinfo?cid=6&sourceid=${albumId}&timelist=${qs}`;
+ try {
+ const listResp = await this.fetch(listUrl, {}, this.headers);
+ const listJson = listResp.data;
+ if (listJson.data && listJson.data[qs]) {
+ listJson.data[qs].forEach(it => {
+ playlists.push({
+ title: it.shortTitle || it.period || it.focus || `期${it.order}`,
+ url: it.playUrl
+ });
+ });
+ }
+ } catch (e) {
+ console.error(`综艺列表获取失败: ${e.message}`);
+ }
+ } else {
+ // 电视剧、动漫等
+ let listUrl = `https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid=${albumId}&size=100&page=1`;
+ try {
+ const listResp = await this.fetch(listUrl, {}, this.headers);
+ const listJson = listResp.data;
+
+ if (listJson.data && listJson.data.epsodelist) {
+ playlists = listJson.data.epsodelist.map(item => ({
+ title: item.shortTitle || item.title ||
+ (item.order ? `第${item.order}集` : `集${item.timelist}`),
+ url: item.playUrl || item.url || ''
+ }));
+
+ // 处理分页
+ const total = listJson.data.total;
+ if (total > 100) {
+ const totalPages = Math.ceil(total / 100);
+ for (let i = 2; i <= totalPages; i++) {
+ let nextUrl = `https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid=${albumId}&size=100&page=${i}`;
+ try {
+ const nextResp = await this.fetch(nextUrl, {}, this.headers);
+ const nextJson = nextResp.data;
+ if (nextJson.data && nextJson.data.epsodelist) {
+ playlists = playlists.concat(nextJson.data.epsodelist.map(item => ({
+ title: item.shortTitle || item.title ||
+ (item.order ? `第${item.order}集` : `集${item.timelist}`),
+ url: item.playUrl || item.url || ''
+ })));
+ }
+ } catch (e) {
+ break;
+ }
+ }
+ }
+ }
+ } catch (e) {
+ console.error(`剧集列表获取失败: ${e.message}`);
+ }
+ }
+ } catch (error) {
+ console.error(`getPlaylists error: ${error.message}`);
+ }
+
+ return playlists;
+ }
+
+ async detailContent(ids) {
+ try {
+ const id = ids[0];
+ let channelId = "";
+ let albumId = id;
+
+ if (id.includes('$')) {
+ const parts = id.split('$');
+ channelId = parts[0];
+ albumId = parts[1];
+ }
+
+ // 获取视频基本信息
+ const infoUrl = `https://pcw-api.iqiyi.com/video/video/videoinfowithuser/${albumId}?agent_type=1&authcookie=&subkey=${albumId}&subscribe=1`;
+ const infoResp = await this.fetch(infoUrl, {}, this.headers);
+ const infoJson = infoResp.data;
+ const data = infoJson.data || {};
+
+ // 获取播放列表
+ const playlists = await this.getPlaylists(channelId, albumId, data);
+
+ // 构建播放地址
+ const playUrls = [];
+ if (playlists.length > 0) {
+ for (const item of playlists) {
+ if (item.url) {
+ playUrls.push(`${item.title}$${item.url}`);
+ }
+ }
+ }
+
+ const vod = {
+ vod_id: id,
+ vod_name: data.name || '未知标题',
+ type_name: data.categories ? data.categories.map(it => it.name).join(',') : '',
+ vod_year: data.formatPeriod || '',
+ vod_area: data.areas ? data.areas.map(it => it.name).join(',') : '',
+ vod_remarks: data.latestOrder ?
+ `更新至${data.latestOrder}集` :
+ (data.period || playlists.length > 0 ? `${playlists.length}集` : ''),
+ vod_actor: data.people && data.people.main_charactor ?
+ data.people.main_charactor.map(it => it.name).join(',') : '',
+ vod_director: data.people && data.people.director ?
+ data.people.director.map(it => it.name).join(',') : '',
+ vod_content: data.description || '暂无简介',
+ vod_pic: data.imageUrl ? data.imageUrl.replace(".jpg", "_480_270.jpg") : '',
+ vod_play_from: playUrls.length > 0 ? '爱奇艺视频' : '',
+ vod_play_url: playUrls.length > 0 ? playUrls.join('#') : ''
+ };
+
+ return {list: [vod]};
+
+ } catch (error) {
+ console.error(`detailContent error: ${error.message}`);
+ return {list: []};
+ }
+ }
+
+ async searchContent(key, quick, pg = '1') {
+ try {
+ const page = parseInt(pg) || 1;
+ const url = `https://search.video.iqiyi.com/o?if=html5&key=${encodeURIComponent(key)}&pageNum=${page}&pos=1&pageSize=20&site=iqiyi`;
+
+ const response = await this.fetch(url, {}, this.headers);
+ const jsonData = response.data;
+
+ const videos = [];
+
+ if (jsonData.data && jsonData.data.docinfos) {
+ for (const item of jsonData.data.docinfos) {
+ if (item.albumDocInfo) {
+ const doc = item.albumDocInfo;
+ const channelId = doc.channel ? doc.channel.split(',')[0] : '0';
+ videos.push({
+ vod_id: `${channelId}$${doc.albumId}`,
+ vod_name: doc.albumTitle || '',
+ vod_pic: doc.albumVImage || '',
+ vod_remarks: doc.tvFocus || doc.year || ''
+ });
+ }
+ }
+ }
+
+ return {
+ list: videos,
+ page: page,
+ pagecount: 10,
+ limit: 20,
+ total: videos.length
+ };
+
+ } catch (error) {
+ console.error(`searchContent error: ${error.message}`);
+ return {
+ list: [],
+ page: pg,
+ pagecount: 0,
+ limit: 20,
+ total: 0
+ };
+ }
+ }
+
+ async playerContent(flag, id, vipFlags) {
+ try {
+ // 解析播放地址
+ let playUrl = id;
+ if (id.includes('$')) {
+ playUrl = id.split('$')[1];
+ }
+
+ // 关键:调用壳子超级解析
+ const playData = {
+ parse: 1, // 必须为1,表示需要解析
+ jx: 1, // 必须为1,启用解析
+ play_parse: true, // 启用播放解析
+ parse_type: '壳子超级解析',
+ parse_source: '爱奇艺视频',
+ url: playUrl, // 原始爱奇艺链接
+ header: JSON.stringify({
+ 'User-Agent': this.headers['User-Agent'],
+ 'Referer': 'https://www.iqiyi.com',
+ 'Origin': 'https://www.iqiyi.com'
+ })
+ };
+
+ return playData;
+
+ } catch (error) {
+ console.error(`playerContent error: ${error.message}`);
+ // 即使出错也返回超级解析参数,让壳子处理
+ return {
+ parse: 1,
+ jx: 1,
+ play_parse: true,
+ parse_type: '壳子超级解析',
+ parse_source: '爱奇艺视频',
+ url: id,
+ header: JSON.stringify(this.headers)
+ };
+ }
+ }
+
+ localProxy(param) {
+ return null;
+ }
+}
+
+export default new Spider();
\ No newline at end of file
diff --git "a/spider/catvod/\346\236\234\346\236\234[\345\256\230].js" "b/spider/catvod/\346\236\234\346\236\234[\345\256\230].js"
new file mode 100644
index 00000000..ea5846fc
--- /dev/null
+++ "b/spider/catvod/\346\236\234\346\236\234[\345\256\230].js"
@@ -0,0 +1,365 @@
+/**
+ * 芒果TV - 猫影视JS爬虫格式(第二个版本)
+ * 调用壳子超级解析功能
+ @header({
+ searchable: 1,
+ filterable: 1,
+ quickSearch: 1,
+ title: '果果[官]',
+ lang: 'cat'
+ })
+ */
+
+class Spider extends BaseSpider {
+
+ constructor() {
+ super();
+ this.host = 'https://www.mgtv.com';
+ this.headers = {
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
+ 'Referer': 'https://www.mgtv.com/',
+ 'Accept': 'application/json, text/plain, */*',
+ 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
+ 'Accept-Encoding': 'gzip, deflate, br',
+ 'Connection': 'keep-alive'
+ };
+ }
+
+ init(extend = '') {
+ return '';
+ }
+
+ getName() {
+ return '芒果TV2';
+ }
+
+ isVideoFormat(url) {
+ return true;
+ }
+
+ manualVideoCheck() {
+ return false;
+ }
+
+ destroy() {
+ // 清理资源
+ }
+
+ homeContent(filter) {
+ const classes = [
+ {type_id: '3', type_name: '电影'},
+ {type_id: '2', type_name: '电视剧'},
+ {type_id: '1', type_name: '综艺'},
+ {type_id: '50', type_name: '动漫'},
+ {type_id: '51', type_name: '纪录片'},
+ {type_id: '115', type_name: '教育'},
+ {type_id: '10', type_name: '少儿'}
+ ];
+
+ const filters = {
+ '3': [
+ {
+ key: 'year', name: '年份', value: [
+ {n: '全部', v: 'all'}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'},
+ {n: '2023', v: '2023'}, {n: '2022', v: '2022'}, {n: '2021', v: '2021'},
+ {n: '2020', v: '2020'}, {n: '2019', v: '2019'}, {n: '2010-2019', v: '2010-2019'},
+ {n: '2000-2009', v: '2000-2009'}
+ ]
+ },
+ {
+ key: 'sort', name: '排序', value: [
+ {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'}
+ ]
+ }
+ ],
+ '2': [
+ {
+ key: 'year', name: '年份', value: [
+ {n: '全部', v: 'all'}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'},
+ {n: '2023', v: '2023'}, {n: '2022', v: '2022'}, {n: '2021', v: '2021'},
+ {n: '2020', v: '2020'}
+ ]
+ },
+ {
+ key: 'sort', name: '排序', value: [
+ {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'}
+ ]
+ }
+ ],
+ '1': [
+ {
+ key: 'sort', name: '排序', value: [
+ {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'}
+ ]
+ }
+ ],
+ '50': [
+ {
+ key: 'sort', name: '排序', value: [
+ {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'}
+ ]
+ }
+ ]
+ };
+
+ return {
+ class: classes,
+ filters: filters
+ };
+ }
+
+ homeVideoContent() {
+ return {list: []};
+ }
+
+ async categoryContent(tid, pg, filter, extend) {
+ try {
+ const page = parseInt(pg) || 1;
+ const baseUrl = 'https://pianku.api.mgtv.com/rider/list/pcweb/v3';
+
+ // 构建查询参数
+ const params = {
+ platform: 'pcweb',
+ channelId: tid,
+ pn: page,
+ pc: '20',
+ hudong: '1',
+ _support: '10000000',
+ kind: 'a1',
+ area: 'a1'
+ };
+
+ // 处理筛选条件
+ if (extend) {
+ if (extend.year && extend.year !== 'all') {
+ params.year = extend.year;
+ }
+ if (extend.sort) {
+ params.sort = extend.sort;
+ }
+ if (extend.chargeInfo) {
+ params.chargeInfo = extend.chargeInfo;
+ }
+ }
+
+ const queryString = new URLSearchParams(params).toString();
+ const url = `${baseUrl}?${queryString}`;
+
+ const response = await this.fetch(url, {}, this.headers);
+ const json = response.data || {};
+
+ const videos = [];
+ if (json.data?.hitDocs && Array.isArray(json.data.hitDocs)) {
+ for (const item of json.data.hitDocs) {
+ videos.push({
+ vod_id: item.playPartId || '',
+ vod_name: item.title || '',
+ vod_pic: item.img || '',
+ vod_remarks: item.updateInfo || item.rightCorner?.text || ''
+ });
+ }
+ }
+
+ return {
+ list: videos,
+ page: page,
+ pagecount: json.data?.totalPage || 999,
+ limit: 20,
+ total: json.data?.totalHit || 9999
+ };
+
+ } catch (error) {
+ console.error(`categoryContent error: ${error.message}`);
+ return {
+ list: [],
+ page: pg,
+ pagecount: 0,
+ limit: 20,
+ total: 0
+ };
+ }
+ }
+
+ async detailContent(ids) {
+ try {
+ const videoId = ids[0];
+
+ // 获取视频基本信息
+ const infoUrl = `https://pcweb.api.mgtv.com/video/info?video_id=${videoId}`;
+ const infoResponse = await this.fetch(infoUrl, {}, this.headers);
+ const infoData = infoResponse.data?.data?.info || {};
+
+ const vod = {
+ vod_id: videoId,
+ vod_name: infoData.title || '',
+ type_name: infoData.root_kind || '',
+ vod_actor: '',
+ vod_year: infoData.release_time || '',
+ vod_content: infoData.desc || '',
+ vod_remarks: infoData.time || '',
+ vod_pic: infoData.img || '',
+ vod_play_from: '芒果TV',
+ vod_play_url: ''
+ };
+
+ // 分页获取所有剧集
+ const pageSize = 50;
+ let allEpisodes = [];
+
+ try {
+ // 获取第一页,同时获取总页数
+ const firstPageUrl = `https://pcweb.api.mgtv.com/episode/list?video_id=${videoId}&page=1&size=${pageSize}`;
+ const firstResponse = await this.fetch(firstPageUrl, {}, this.headers);
+ const firstData = firstResponse.data?.data || {};
+
+ if (firstData.list && Array.isArray(firstData.list)) {
+ allEpisodes = allEpisodes.concat(firstData.list);
+ const totalPages = firstData.total_page || 1;
+
+ // 如果有多页,获取剩余页面
+ if (totalPages > 1) {
+ const pagePromises = [];
+ for (let i = 2; i <= totalPages; i++) {
+ const pageUrl = `https://pcweb.api.mgtv.com/episode/list?video_id=${videoId}&page=${i}&size=${pageSize}`;
+ pagePromises.push(this.fetch(pageUrl, {}, this.headers));
+ }
+
+ const responses = await Promise.all(pagePromises);
+ for (const response of responses) {
+ const data = response.data?.data || {};
+ if (data.list && Array.isArray(data.list)) {
+ allEpisodes = allEpisodes.concat(data.list);
+ }
+ }
+ }
+ }
+ } catch (episodeError) {
+ console.error(`获取剧集列表失败: ${episodeError.message}`);
+ }
+
+ // 构建播放列表
+ const playUrls = [];
+ if (allEpisodes.length > 0) {
+ // 过滤可播放的剧集(isIntact = 1)
+ const validEpisodes = allEpisodes.filter(item =>
+ item.isIntact === "1" || item.isIntact === 1
+ );
+
+ // 按集数排序
+ validEpisodes.sort((a, b) => {
+ const orderA = parseInt(a.order) || 0;
+ const orderB = parseInt(b.order) || 0;
+ return orderA - orderB;
+ });
+
+ // 构建播放链接
+ for (const item of validEpisodes) {
+ const name = item.t4 || item.t3 || item.title || `第${item.order || '?'}集`;
+ const playLink = item.url ? `https://www.mgtv.com${item.url}` : '';
+
+ if (playLink) {
+ playUrls.push(`${name}$${playLink}`);
+ }
+ }
+ }
+
+ vod.vod_play_url = playUrls.join('#');
+
+ return {list: [vod]};
+
+ } catch (error) {
+ console.error(`detailContent error: ${error.message}`);
+ return {list: []};
+ }
+ }
+
+ async searchContent(key, quick, pg = '1') {
+ try {
+ const page = parseInt(pg) || 1;
+ const searchUrl = `https://mobileso.bz.mgtv.com/msite/search/v2?q=${encodeURIComponent(key)}&pn=${page}&pc=20`;
+
+ const response = await this.fetch(searchUrl, {}, this.headers);
+ const json = response.data?.data || {};
+
+ const videos = [];
+
+ if (json.contents && Array.isArray(json.contents)) {
+ for (const group of json.contents) {
+ if (group.type === 'media' && group.data && Array.isArray(group.data)) {
+ for (const item of group.data) {
+ if (item.source === 'imgo') {
+ // 提取视频ID
+ const match = item.url.match(/\/(\d+)\.html/);
+ if (match) {
+ videos.push({
+ vod_id: match[1],
+ vod_name: item.title ? item.title.replace(/