🍅番茄短剧·漫剧(APP)
https://api5-normal-sinfonlineb.fqnovel.com##短剧版
laizhiyu (6509)4天前
测试软件:legado-E,legado-team其他自测或者自己AI兼容
四个书源,参考开源项目项目制作,感谢大佬的开源开源项目:https://github.com/ch6vip/fqapp
本书源把开源项目移植成为书源,也是本地版,没有什么经过服务器转接之类的,所以且用且珍惜,什么时候接口被封或者换之类的,我也没办法呀
纯AI制作,无一点人工,所以会有各种bug,有bug自己反馈给AI修复,本人还要玩游戏。
{
"bookSourceComment": "基于 fqapp 开源项目逆向的番茄官方 App 接口(x-argus\/x-ladon 纯 JS 签名)\n【发现】漫剧\/短剧\/动漫 三频道(tab_type=32\/8\/24,两段式视频流)\n【搜索】suggest 通道(含视频结果,固定10条)\n【播放】shortplay 通道(aid=1967)+ CENC-AES-CTR 解密 + 本地文件播放\n【画质】书源菜单→登录 可切换 360\/480\/540\/720P(默认360P)\n【E版兼容】零第三方依赖:hex 用 BigInteger(4MB 330ms)、SM3 纯JS、Base64 三级降级\n【参考】github.com\/ch6vip\/fqapp",
"bookSourceGroup": "视频,番茄,AI",
"bookSourceName": "🍅番茄短剧·漫剧(APP)",
"bookSourceType": 4,
"bookSourceUrl": "https:\/\/api5-normal-sinfonlineb.fqnovel.com##短剧版",
"bookUrlPattern": "https?:\/\/api5-normal-sinfonlineb[.]fqnovel[.]com\/detail[?]series_id=[0-9]+",
"concurrentRate": "1\/1200",
"customButton": false,
"customOrder": 6,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "[{\"title\": \"🎬 漫剧热播\", \"url\": \"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/explore?kind=manju&page={{page}}\"}, {\"title\": \"🎭 短剧精选\", \"url\": \"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/explore?kind=zhaoju&page={{page}}\"}, {\"title\": \"✨ 动漫剧场\", \"url\": \"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/explore?kind=dongman&page={{page}}\"}]",
"header": "{\"User-Agent\": \"com.phoenix.read\/72932 (Linux; U; Android 12; zh_CN; V2284A; Build\/V417IR;tt-ok\/3.12.13.20)\"}",
"jsLib": "function HG_b64JS(u8){\nvar CH='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+\/';\nvar out='';var i=0,n=u8.length;\nwhile(i<n){\nvar c1=u8[i++]&255;\nvar c2=(i<n)?(u8[i++]&255):NaN;\nvar c3=(i<n)?(u8[i++]&255):NaN;\nvar e1=c1>>2;\nvar e2=((c1&3)<<4)|(isNaN(c2)?0:(c2>>4));\nvar e3=isNaN(c2)?64:(((c2&15)<<2)|(isNaN(c3)?0:(c3>>6)));\nvar e4=isNaN(c3)?64:(c3&63);\nout+=CH.charAt(e1)+CH.charAt(e2)+(e3===64?'=':CH.charAt(e3))+(e4===64?'=':CH.charAt(e4));\n}\nreturn out;\n}\nvar HG_CS=Packages.java.nio.charset.Charset.forName('ISO-8859-1');\nfunction HG_jb(u8){var s='';for(var i=0;i<u8.length;i++)s+=String.fromCharCode(u8[i]&0xff);return new Packages.java.lang.String(s).getBytes(HG_CS);}\nfunction HG_j2u(a){var o=new Uint8Array(a.length);for(var i=0;i<a.length;i++)o[i]=a[i]&0xff;return o;}\nfunction HG_hx(u8){var s='';for(var i=0;i<u8.length;i++){var b=u8[i]&0xff;s+=(b<16?'0':'')+b.toString(16);}return s;}\nfunction HG_h2b(h){var b=new Uint8Array(h.length\/2);for(var i=0;i<b.length;i++)b[i]=parseInt(h.substr(i*2,2),16);return b;}\nfunction HG_u8(s){var r=[];for(var i=0;i<s.length;i++){var c=s.charCodeAt(i);if(c<0x80)r.push(c);else if(c<0x800){r.push(0xc0|(c>>6),0x80|(c&0x3f));}else{r.push(0xe0|(c>>12),0x80|((c>>6)&0x3f),0x80|(c&0x3f));}}return new Uint8Array(r);}\nfunction HG_cat(){var ps=[].slice.call(arguments),n=0,i;for(i=0;i<ps.length;i++){var p=(ps[i] instanceof Uint8Array)?ps[i]:new Uint8Array(ps[i]||[]);n+=p.length;}\nvar o=new Uint8Array(n),o2=0;for(i=0;i<ps.length;i++){var p2=(ps[i] instanceof Uint8Array)?ps[i]:new Uint8Array(ps[i]||[]);o.set(p2,o2);o2+=p2.length;}return o;}\nfunction HG_rnd(l){var o=new Uint8Array(l);for(var i=0;i<l;i++)o[i]=Math.floor(Math.random()*256);return o;}\nfunction HG_rev(b){var o=new Uint8Array(b.length);for(var i=0;i<b.length;i++)o[i]=b[b.length-i-1];return o;}\nfunction HG_ev(v){var o=[],r=v;while(r>=0x80){o.push((r%0x80)|0x80);r=Math.floor(r\/0x80);}o.push(r);return new Uint8Array(o);}\nfunction HG_pbv(f,v){return HG_cat(HG_ev(f*8),HG_ev(v));}\nfunction HG_pbb(f,v){var e=(v instanceof Uint8Array)?v:HG_u8(String(v));return HG_cat(HG_ev(f*8+2),HG_ev(e.length),e);}\nfunction HG_w64(lo,hi){return {lo:lo>>>0,hi:hi>>>0};}\nfunction HG_xor(){var lo=0,hi=0;for(var i=0;i<arguments.length;i++){lo^=arguments[i].lo;hi^=arguments[i].hi;}return HG_w64(lo,hi);}\nfunction HG_a64(a,b){return HG_w64(a.lo&b.lo,a.hi&b.hi);}\nfunction HG_n64(v){return HG_w64(~v.lo,~v.hi);}\nfunction HG_add64(a,b){var lo=(a.lo+b.lo)>>>0;return HG_w64(lo,(a.hi+b.hi+(lo<a.lo?1:0))>>>0);}\nfunction HG_rol(v,c){var s=((c%64)+64)%64;if(s===0)return HG_w64(v.lo,v.hi);\nif(s<32)return HG_w64((v.lo<<s)|(v.hi>>>(32-s)),(v.hi<<s)|(v.lo>>>(32-s)));\nif(s===32)return HG_w64(v.hi,v.lo);var r=s-32;return HG_w64((v.hi<<r)|(v.lo>>>(32-r)),(v.lo<<r)|(v.hi>>>(32-r)));}\nfunction HG_ror(v,c){return HG_rol(v,64-(c%64));}\nfunction HG_r64(b,o){o=o||0;return HG_w64((b[o]|(b[o+1]<<8)|(b[o+2]<<16)|(b[o+3]<<24))>>>0,(b[o+4]|(b[o+5]<<8)|(b[o+6]<<16)|(b[o+7]<<24))>>>0);}\nfunction HG_w64le(o,off,v){for(var i=0;i<4;i++){o[off+i]=(v.lo>>>(i*8))&0xff;o[off+4+i]=(v.hi>>>(i*8))&0xff;}}\nfunction HG_r32(b){return (b[0]|(b[1]<<8)|(b[2]<<16)|(b[3]<<24))>>>0;}\nfunction HG_md5(u8){var d=Packages.java.security.MessageDigest.getInstance('MD5');d.update(HG_jb(u8));return HG_j2u(d.digest());}\nfunction HG_sm3(u8){\nvar rotl=function(x,n){n=n&31;return ((x<<n)|(x>>>(32-n)))>>>0;};\nvar s='';for(var q=0;q<u8.length;q++)s+=String.fromCharCode(u8[q]&255);\nvar len=s.length,bitLen=len*8,arr=[];\nfor(var i=0;i<len;i++)arr.push(s.charCodeAt(i)&255);\narr.push(0x80);\nwhile(arr.length%64!==56)arr.push(0);\nvar hi=Math.floor(bitLen\/4294967296),lo=bitLen>>>0;\narr.push((hi>>>24)&255,(hi>>>16)&255,(hi>>>8)&255,hi&255);\narr.push((lo>>>24)&255,(lo>>>16)&255,(lo>>>8)&255,lo&255);\nvar V=[0x7380166f,0x4914b2b9,0x172442d7,0xda8a0600,0xa96f30bc,0x163138aa,0xe38dee4d,0xb0fb0e4e];\nfor(var b=0;b<arr.length;b+=64){\nvar W=[];\nfor(var i=0;i<16;i++)W.push(((arr[b+i*4]<<24)|(arr[b+i*4+1]<<16)|(arr[b+i*4+2]<<8)|arr[b+i*4+3])>>>0);\nfor(var j=16;j<68;j++){var x=(W[j-16]^W[j-9]^rotl(W[j-3],15))>>>0;x=(x^rotl(x,15)^rotl(x,23))>>>0;W.push((x^rotl(W[j-13],7)^W[j-6])>>>0);}\nvar W1=[];for(var j=0;j<64;j++)W1.push((W[j]^W[j+4])>>>0);\nvar A=V[0],B=V[1],C=V[2],D=V[3],E=V[4],F=V[5],G=V[6],H=V[7];\nfor(var j=0;j<64;j++){\nvar Tj=(j<16)?0x79cc4519:0x7a879d8a;\nvar SS1=rotl((rotl(A,12)+E+rotl(Tj,j&31))>>>0,7);\nvar SS2=(SS1^rotl(A,12))>>>0;\nvar FFj=(j<16)?((A^B^C)>>>0):(((A&B)|(A&C)|(B&C))>>>0);\nvar GGj=(j<16)?((E^F^G)>>>0):(((E&F)|((~E)&G))>>>0);\nvar TT1=(FFj+D+SS2+W1[j])>>>0;\nvar TT2=(GGj+H+SS1+W[j])>>>0;\nD=C;C=rotl(B,9);B=A;A=TT1;H=G;G=rotl(F,19);F=E;E=(TT2^rotl(TT2,9)^rotl(TT2,17))>>>0;\n}\nV[0]=(V[0]^A)>>>0;V[1]=(V[1]^B)>>>0;V[2]=(V[2]^C)>>>0;V[3]=(V[3]^D)>>>0;\nV[4]=(V[4]^E)>>>0;V[5]=(V[5]^F)>>>0;V[6]=(V[6]^G)>>>0;V[7]=(V[7]^H)>>>0;\n}\nvar out=new Uint8Array(32);\nfor(var i=0;i<8;i++){\nvar v=V[i];\nout[i*4]=(v>>>24)&255;out[i*4+1]=(v>>>16)&255;out[i*4+2]=(v>>>8)&255;out[i*4+3]=v&255;\n}\nreturn out;\n}\nvar HG_SK=HG_h2b('ac1adaae95a7af94a5114ab3b3a97dd80050aa0a39314c40528caec95256c28c');\nvar HG_AH=HG_h2b('3ccc'),HG_AP=HG_h2b('a6e783ee7001100918'),HG_AS=HG_h2b('567b'),HG_AX=HG_h2b('d04ffdff');\nvar HG_LID=1611921764,HG_MAID=3019,HG_SVS=135135744,HG_CV=1388734;\nvar HG_SZ=HG_w64(0x046d678b,0x3dc94c3a);\nfunction HG_zbit(i){return HG_w64(i<32?((HG_SZ.lo>>>i)&1):((HG_SZ.hi>>>(i-32))&1),0);}\nfunction HG_srk(key){var w=[];for(var i=0;i<4;i++)w.push(HG_r64(key,i*8));\nfor(var x=4;x<72;x++){var m=HG_xor(HG_ror(w[x-1],3),w[x-3]);m=HG_xor(m,HG_ror(m,1));\nw.push(HG_xor(HG_n64(w[x-4]),m,HG_zbit((x-4)%62),HG_w64(3,0)));}return w;}\nfunction HG_sEnc(b,keys){var l=HG_r64(b,0),r=HG_r64(b,8);\nfor(var i=0;i<keys.length;i++){var o2=r;var nl=HG_a64(HG_rol(r,1),HG_rol(r,8));r=HG_xor(l,nl,HG_rol(r,2),keys[i]);l=o2;}\nvar o=new Uint8Array(16);HG_w64le(o,0,l);HG_w64le(o,8,r);return o;}\nfunction HG_pad(d){var p=16-(d.length%16);if(p===0)p=16;var o=new Uint8Array(d.length+p);o.set(d,0);for(var i=d.length;i<o.length;i++)o[i]=p;return o;}\nfunction HG_aProto(q,stub,ts,op){op=op||{};\nvar bi=(\/^[0-9a-f]{32}$\/i.test(stub))?HG_h2b(stub):new Uint8Array(16);\nvar rv=(op.argusRandom==null)?HG_r32(HG_rnd(4)):(op.argusRandom>>>0);\nvar sc=(op.argusSignCount==null)?2+(HG_rnd(1)[0]%49)*2:op.argusSignCount;\nvar nest=HG_cat(HG_pbv(1,sc),HG_pbv(2,HG_CV),HG_pbv(3,HG_CV),HG_pbv(4,HG_CV));\nreturn HG_cat(HG_pbv(1,0x20200929*2),HG_pbv(2,2),HG_pbv(3,rv),HG_pbb(4,HG_MAID),HG_pbb(6,HG_LID),\nHG_pbb(7,'6.8.1.32'),HG_pbb(8,'v04.07.01-ml-android'),HG_pbv(9,HG_SVS),HG_pbb(10,new Uint8Array(8)),\nHG_pbv(12,ts*2),HG_pbb(13,HG_sm3(bi).slice(0,6)),HG_pbb(14,HG_sm3(HG_u8(q)).slice(0,6)),\nHG_pbb(15,nest),HG_pbb(20,'none'),HG_pbv(21,738));}\nfunction HG_b64(u8){\n try{\n var B=Packages.android.util.Base64;\n if(B && typeof B.encodeToString==='function'){\n var r=String(B.encodeToString(HG_jb(u8),2));\n if(r && r.length>0) return r;\n }\n }catch(e){}\n try{\n var E=Packages.java.util.Base64.getEncoder();\n var r2=String(E.encodeToString(HG_jb(u8)));\n if(r2 && r2.length>0) return r2;\n }catch(e2){}\n return HG_b64JS(u8);\n}\nfunction HG_aesE(data,key,iv){var C=Packages.javax.crypto.Cipher;var c=C.getInstance('AES\/CBC\/PKCS5Padding');\nc.init(C.ENCRYPT_MODE,new Packages.javax.crypto.spec.SecretKeySpec(HG_jb(key),'AES'),new Packages.javax.crypto.spec.IvParameterSpec(HG_jb(iv)));\nreturn HG_j2u(c.doFinal(HG_jb(data)));}\nfunction HG_argus(q,stub,ts,op){op=op||{};\nvar pb=HG_pad(HG_aProto(q,stub,ts,op));\nvar sk=HG_sm3(HG_cat(HG_SK,HG_AH,HG_AS,HG_SK));var keys=HG_srk(sk);var enc=[];\nfor(var o1=0;o1<pb.length;o1+=16)enc.push(HG_sEnc(pb.slice(o1,o1+16),keys));\nvar encAll=HG_cat.apply(null,enc);var encoded=HG_cat(HG_AX,HG_AX,encAll);\nfor(var i=8;i<encoded.length;i++)encoded[i]^=encoded[i%8];\nvar cont=HG_cat(HG_AP,HG_rev(encoded),HG_AS);\nvar key=HG_h2b(HG_hx(HG_md5(HG_SK.slice(0,16))));var iv=HG_h2b(HG_hx(HG_md5(HG_SK.slice(16))));\nreturn HG_b64(HG_cat(HG_AH,HG_aesE(cont,key,iv)));}\nfunction HG_lrk(rp,aid){var dg=HG_u8(HG_hx(HG_md5(HG_cat(rp,HG_u8(String(aid))))));\nvar t=new Uint8Array(288);t.set(dg);var q=[];for(var i=0;i<4;i++)q.push(HG_r64(t,i*8));\nvar l=q.shift(),r=q.shift();\nfor(var x=0;x<0x22;x++){var m=HG_xor(HG_add64(HG_ror(r,8),l),HG_w64(x,0));q.push(m);\nl=HG_xor(m,HG_ror(l,61));HG_w64le(t,(x+1)*8,l);r=q.shift();}\nvar ks=[];for(var y=0;y<0x22;y++)ks.push(HG_r64(t,y*8));return ks;}\nfunction HG_lEnc(b,keys){var l=HG_r64(b,0),r=HG_r64(b,8);\nfor(var i=0;i<keys.length;i++){r=HG_xor(keys[i],HG_add64(l,HG_ror(r,8)));l=HG_xor(r,HG_ror(l,61));}\nvar o=new Uint8Array(16);HG_w64le(o,0,l);HG_w64le(o,8,r);return o;}\nfunction HG_ladon(ts,aid,op){op=op||{};\nvar rp=(op.ladonRandom==null)?HG_rnd(4):new Uint8Array(op.ladonRandom);\nvar data=HG_u8(ts+'-'+HG_LID+'-'+HG_MAID);\nvar ps=Math.ceil(data.length\/16)*16;var pd=new Uint8Array(ps);pd.set(data);\nvar pad2=16-(data.length%16);if(data.length+pad2<=ps)pd.fill(pad2,data.length);\nvar ks=HG_lrk(rp,aid);var ct=[];\nfor(var o1=0;o1<pd.length;o1+=16)ct.push(HG_lEnc(pd.slice(o1,o1+16),ks));\nreturn HG_b64(HG_cat(rp,HG_cat.apply(null,ct)));}\nfunction HG_md5p(v){var b=(v instanceof Uint8Array)?v:HG_u8(String(v));return HG_md5(b).slice(0,4);}\nfunction HG_qs(url){var t=String(url||'');var q=t.indexOf('?');if(q<0)return '';\nvar h=t.indexOf('#',q);return t.slice(q+1,h<0?undefined:h);}\nfunction HG_hdr(h,n){var e=String(n).toLowerCase();for(var k in (h||{})){if(k.toLowerCase()===e&&h[k]!=null)return String(h[k]);}return '';}\nfunction HG_gSeed(url,o){o=o||{};var kh=o.khronos;\nvar stub=String(HG_hdr(o.headers,'x-ss-stub')||'').trim();\nvar bp=new Uint8Array(4);\nif(\/^[0-9a-f]{32}$\/i.test(stub))bp=HG_h2b(stub.slice(0,8));\nelse if(o.body!=null){var bb=(o.body instanceof Uint8Array)?o.body:HG_u8(String(o.body));if(bb.length>0)bp=HG_md5p(bb);}\nvar ck=HG_hdr(o.headers,'cookie');\nvar ts=new Uint8Array([(kh>>>24)&0xff,(kh>>>16)&0xff,(kh>>>8)&0xff,kh&0xff]);\nreturn HG_cat(HG_md5p(HG_qs(url)),bp,ck?HG_md5p(ck):new Uint8Array(4),new Uint8Array([0,1,7,4]),ts);}\nfunction HG_rbits(v){var r=((v<<1)&0xaa)|((v>>>1)&0x55);r=((r<<2)&0xcc)|((r>>>2)&0x33);return ((r<<4)|(r>>>4))&0xff;}\nfunction HG_tSeed(seed,o){o=o||{};var inp=(seed instanceof Uint8Array)?seed:new Uint8Array(seed||[]);\nif(inp.length!==20)return new Uint8Array(0);\nvar key=new Uint8Array([0x4a,(o.parameter||0)&0xff,0x16,o.headerByte3,0x47,0x6c,((o.parameter||0)>>>8)&0xff,o.headerByte2]);\nvar sb=new Uint8Array(256);for(var i=0;i<256;i++)sb[i]=i;var j=0;\nfor(var i2=0;i2<256;i2++){j=(j+sb[i2]+key[i2&7])&0xff;sb[i2]=sb[j];}\nvar pl=new Uint8Array(inp);var i3=0;j=0;\nfor(var of2=0;of2<pl.length;of2++){i3=(i3+1)&0xff;j=(j+sb[i3])&0xff;sb[i3]=sb[j];pl[of2]^=sb[(sb[i3]+sb[j])&0xff];}\nvar lm=(~pl.length)&0xff;\nfor(var of3=0;of3<pl.length;of3++){var sn=((pl[of3]<<4)|(pl[of3]>>>4))&0xff;\npl[of3]=HG_rbits(sn^pl[(of3+1)%pl.length])^lm;}\nreturn HG_cat(new Uint8Array([0x84,0x04,o.headerByte2,o.headerByte3,(o.parameter||0)&0xff,((o.parameter||0)>>>8)&0xff]),pl);}\nfunction HG_qp(url,n){var e=String(n);var ps=HG_qs(url).split('&');\nfor(var i=0;i<ps.length;i++){if(!ps[i])continue;var s=ps[i].indexOf('=');\nvar k=s<0?ps[i]:ps[i].slice(0,s);if(decodeURIComponent(k)===e)return decodeURIComponent(s<0?'':ps[i].slice(s+1));}\nreturn null;}\nfunction HG_sign(url,op){op=op||{};\nvar ts=(op.timestamp==null)?Math.floor(new Date().getTime()\/1000):op.timestamp;\nvar aid=parseInt(HG_qp(url,'aid'),10);\nvar rb=HG_rnd(2);\nvar hb2=(op.headerByte2==null)?((rb[0]%7)+1)<<5:op.headerByte2;\nvar hb3=(op.headerByte3==null)?(0xe0|(rb[1]&0x0f)):op.headerByte3;\nvar hdrs=op.headers||{};\nvar sd=HG_gSeed(url,{headers:hdrs,body:op.body,khronos:ts});\nvar res={};\nres['X-Argus']=HG_argus(HG_qs(url),String(HG_hdr(hdrs,'x-ss-stub')||'').trim(),ts,op);\nres['X-Gorgon']=HG_hx(HG_tSeed(sd,{headerByte2:hb2,headerByte3:hb3,parameter:op.parameter||0}));\nres['X-Khronos']=String(ts);\nres['X-Ladon']=HG_ladon(ts,aid,op);\nreturn res;}\nfunction HG_SID(u){var s=String(u||'');var m=s.match(\/[?&]series_id=([0-9]+)\/);if(m)return m[1];\nvar m2=s.match(\/series_id=([0-9]+)\/);if(m2)return m2[1];\nvar m3=s.match(\/([0-9]{15,})\/);return m3?m3[1]:'';}\nfunction HG_VID(u){var s=String(u||'');var m=s.match(\/[?&]vid=([0-9]+)\/);if(m)return m[1];\nvar m2=s.match(\/vid=([0-9]+)\/);if(m2)return m2[1];return '';}\nfunction HG_BID(u){var s=String(u||'');var m=s.match(\/[?&]book_id=([0-9]+)\/);if(m)return m[1];\nvar m2=s.match(\/book_id=([0-9]+)\/);if(m2)return m2[1];return '';}\nfunction HG_API(JV, path, bodyObj, extra){\nvar java=JV;\nvar base='https:\/\/api5-normal-sinfonlineb.fqnovel.com';\nvar q={aid:'8662',app_name:'novelread',version_code:'72932',version_name:'7.2.9.32',\ndevice_platform:'android',os:'android',ssmix:'a',device_type:'V2284A',device_brand:'vivo',\nlanguage:'zh',os_api:'32',os_version:'12',manifest_version_code:'72932',\nresolution:'1080*1920',dpi:'280',update_version_code:'72932',host_abi:'arm64-v8a',\niid:'4439167111854618',device_id:'4439167111850522',channel:'huawei_8662_64',\nac:'wifi',dragon_device_type:'phone',pv_player:'72932',compliance_status:'0',\nneed_personal_recommend:'1',player_so_load:'1',is_android_pad_screen:'1',\nrom_version:'V417IR+release-keys',cdid:'6100e9f9-a1a0-4f65-ab47-94f5b41b8efb'};\nfor(var k in (extra||{})) q[k]=extra[k];\nvar arr=[]; for(var k2 in q) arr.push(k2+'='+encodeURIComponent(String(q[k2])));\narr.push('_rticket='+String(new Date().getTime()));\nvar url=base+path+'?'+arr.join('&');\nvar bs=bodyObj?JSON.stringify(bodyObj):null;\nvar h={'user-agent':'com.phoenix.read\/72932 (Linux; U; Android 12; zh_CN; V2284A; Build\/V417IR;tt-ok\/3.12.13.20)',\n'Accept':'application\/json; charset=utf-8,application\/x-protobuf',\n'content-type':'application\/json; charset=utf-8','cookie':'',\n'x-tt-token':'','x-tt-store-region':'cn-zj','x-tt-store-region-src':'did',\n'passport-sdk-version':'5051452','sdk-version':'2'};\nif(bs) h['x-ss-stub']=HG_hx(HG_md5(HG_u8(bs))).toUpperCase();\nvar sg=HG_sign(url,{headers:h,body:bs});\nfor(var k3 in sg) h[k3]=sg[k3];\ntry{\nvar Q=String.fromCharCode(34);\nvar opt={method:'POST',body:bs||'',headers:h};\nif(!bs) delete opt.body;\nvar jo=JSON.stringify(opt);\nvar r=java.ajax(url+','+jo);\nreturn String(r);\n}catch(e){ return 'ERR:'+String(e); }\n}\n\n\/\/ ================= CENC 解密核心(v5 新增)=================\n\n\/\/ ============ E 版兼容:hex 编解码(零第三方依赖)============\n\/\/ ① hex → bytes(零第三方依赖:Java 标准库 BigInteger)\nfunction RG_decodeHex(hex){\n var s=String(hex||'');\n if(s.length===0) return new Uint8Array(0);\n if(s.length%2===1) s='0'+s; \/\/ 奇数长度补前导零\n var bi=new Packages.java.math.BigInteger(s,16);\n var arr=bi.toByteArray();\n var n=s.length>>1;\n if(arr.length===n) return arr;\n if(arr.length===n+1) return Packages.java.util.Arrays.copyOfRange(arr,1,arr.length);\n if(arr.length<n){\n \/\/ 补前导零\n var bos=new Packages.java.io.ByteArrayOutputStream();\n for(var i=0;i<n-arr.length;i++) bos.write(0);\n bos.write(arr,0,arr.length);\n return bos.toByteArray();\n }\n return Packages.java.util.Arrays.copyOfRange(arr,arr.length-n,arr.length);\n}\n\/\/ ② bytes → hex(零第三方依赖:Java 标准库 BigInteger)\nfunction RG_encodeHex(b){\n if(!b || b.length===0) return '';\n var bi=new Packages.java.math.BigInteger(1,b);\n var h=String(bi.toString(16));\n var want=b.length*2;\n while(h.length<want) h='0'+h;\n return h;\n}\n\/\/ 纯 JS 兜底实现(慢,仅保底)\nfunction RG_hexToBytesJS(hex){\n var s=String(hex||'');\n var n=s.length>>1;\n var out=new Uint8Array(n);\n for(var i=0;i<n;i++){\n var hi=s.charCodeAt(i*2), lo=s.charCodeAt(i*2+1);\n var vh=(hi>=48&&hi<=57)?(hi-48):((hi>=97&&hi<=102)?(hi-87):((hi>=65&&hi<=70)?(hi-55):0));\n var vl=(lo>=48&&lo<=57)?(lo-48):((lo>=97&&lo<=102)?(lo-87):((lo>=65&&lo<=70)?(lo-55):0));\n out[i]=((vh<<4)|vl)&255;\n }\n return out;\n}\nfunction RG_bytesToHexJS(b){\n var CH='0123456789abcdef';\n var out='';\n for(var i=0;i<b.length;i++){var v=b[i]&255;out+=CH.charAt(v>>4)+CH.charAt(v&15);}\n return out;\n}\n\nvar RG_CS = Packages.java.nio.charset.Charset.forName('ISO-8859-1');\n\/\/ RG_HEX 已移除(E版兼容)\nvar RG_MD = Packages.java.security.MessageDigest;\nvar RG_ARR = Packages.java.util.Arrays;\nvar RG_SYS = Packages.java.lang.System;\nvar RG_CIPHER = Packages.javax.crypto.Cipher;\nvar RG_SK = Packages.javax.crypto.spec.SecretKeySpec;\nvar RG_IVP = Packages.javax.crypto.spec.IvParameterSpec;\nfunction RG_U8(b,p){ return b[p]&255; }\nfunction RG_U32(b,p){ return (b[p]&255)*16777216 + ((b[p+1]&255)<<16) + ((b[p+2]&255)<<8) + (b[p+3]&255); }\nfunction RG_STR(b,p,n){ var s=''; for(var i=0;i<n;i++) s+=String.fromCharCode(b[p+i]&255); return s; }\nfunction RG_S2B(s){ return new Packages.java.lang.String(String(s)).getBytes(RG_CS); }\nfunction RG_H2B(h){ var s=''; for(var i=0;i<h.length;i+=2) s+=String.fromCharCode(parseInt(h.substr(i,2),16)); return RG_S2B(s); }\nvar RG_CONST = '4dd4c2e6b83162090e52b3c7a6733ba41cb2462b829ab58a196b39db57177524f49baf7f08e8d68d26a72e37c1a95a2f1f05a51892aef2949732b62a38aadd58';\n\/\/ RG_B64 已移除(E版兼容)\nfunction RG_b64d(s){\n var str=String(s||'').replace(\/-\/g,'+').replace(\/_\/g,'\/');\n \/\/ ① android.util.Base64(LT版)\n try{\n var B=Packages.android.util.Base64;\n if(B && typeof B.decode==='function'){\n var r=B.decode(str,0);\n if(r && r.length>0) return r;\n }\n }catch(e){}\n \/\/ ② java.util.Base64(E版)\n try{\n var D=Packages.java.util.Base64.getDecoder();\n var r2=D.decode(str);\n if(r2 && r2.length>0) return r2;\n }catch(e2){}\n \/\/ ③ 纯 JS 兜底\n return RG_b64dJS(str);\n}\nfunction RG_b64dJS(str){\n var CH='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+\/';\n var out='';var buf=0,bits=0;\n var s=String(str);\n for(var i=0;i<s.length;i++){\n var c=s.charAt(i);\n if(c==='=')break;\n var v=CH.indexOf(c);\n if(v<0)continue;\n buf=(buf<<6)|v;bits+=6;\n if(bits>=8){bits-=8;out+=String.fromCharCode((buf>>bits)&255);}\n }\n var n=out.length;var arr=new Uint8Array(n);\n for(var k=0;k<n;k++)arr[k]=out.charCodeAt(k)&255;\n return arr;\n}\nfunction RG_spadeUrl(JV, b64, seedB64){\n var seedB = RG_b64d(seedB64);\n var raw = RG_b64d(b64);\n if (raw.length < 5) throw new Error('spade密文过短');\n if (RG_U8(raw,0)!==0xa8 || RG_U8(raw,2)!==0x01 || RG_U8(raw,3)!==0x00) throw new Error('spade头异常');\n var clen = Math.floor((raw.length-4)\/16)*16;\n if (!clen) throw new Error('spade无完整块');\n var md1 = RG_MD.getInstance('SHA-512'); md1.update(seedB); var h1 = md1.digest();\n var md2 = RG_MD.getInstance('SHA-512'); md2.update(h1); md2.update(RG_H2B(RG_CONST)); var h2 = md2.digest();\n var keyB = RG_ARR.copyOfRange(h2,0,16), ivB = RG_ARR.copyOfRange(h2,16,32);\n var ctB = RG_ARR.copyOfRange(raw,4,4+clen);\n var c = RG_CIPHER.getInstance('AES\/CBC\/NoPadding');\n c.init(RG_CIPHER.DECRYPT_MODE, new RG_SK(keyB,'AES'), new RG_IVP(ivB));\n var pt = c.doFinal(ctB);\n var plen = pt.length;\n if (plen) { var pad = pt[plen-1]&255; if (pad>=1 && pad<=16 && pad<=plen) plen-=pad; }\n while (plen && (pt[plen-1]&255)===0) plen--;\n var s=''; for (var i=0;i<plen;i++) s+=String.fromCharCode(pt[i]&255);\n return s;\n}\nfunction RG_cencKey(JV, spadeB64){\n var raw = RG_b64d(String(spadeB64).replace(\/-\/g,'+').replace(\/_\/g,'\/'));\n var len = raw.length;\n if (len < 3) throw new Error('spade_a过短');\n var v8 = len - (RG_U8(raw,0)^RG_U8(raw,1)^RG_U8(raw,2)) + 47;\n v8 = Math.min(v8, len-1);\n if (v8 < 33) throw new Error('spade_a长度无效');\n var work = [];\n for (var i=0;i<v8;i++) work.push(RG_U8(raw,1+i));\n var a=85,b=246;\n for (var i2=0;i2<v8;i2++){\n var old=work[i2], prev=(i2&1)?a:b;\n if (i2&1) a=old; else b=old;\n var pop=0,x=i2; while(x){pop+=x&1;x>>=1;}\n work[i2]=(-21-pop+(prev^old))&255;\n }\n var hs=''; for (var i3=1;i3<33;i3++) hs+=String.fromCharCode(work[i3]);\n return hs;\n}\nfunction RG_boxes(b,start,end){\n var o=[],p=start;\n while(p+8<=end){\n var sz=RG_U32(b,p), t=RG_STR(b,p+4,4), hdr=8;\n if(sz===1){ sz=RG_U32(b,p+8)*4294967296+RG_U32(b,p+12); hdr=16; }\n if(sz<hdr) break;\n o.push({t:t,p:p,s:sz,hdr:hdr}); p+=sz;\n }\n return o;\n}\nfunction RG_find(b,start,end,typ,acc){\n acc=acc||[];\n var bx=RG_boxes(b,start,end);\n for(var i=0;i<bx.length;i++){\n var x=bx[i];\n if(x.t===typ) acc.push(x);\n if('moov,trak,mdia,minf,stbl,dinf,edts,udta,stsd'.indexOf(x.t)>=0){\n RG_find(b,x.p+x.hdr,Math.min(x.p+x.s,b.length),typ,acc);\n }\n }\n return acc;\n}\nfunction RG_decrypt(bytes,keyHex){\n var BL = bytes.length;\n var sk = new RG_SK(RG_H2B(keyHex),'AES');\n var moovEnd = BL;\n var top = RG_boxes(bytes,0,Math.min(BL,4096));\n for (var i=0;i<top.length;i++){ if(top[i].t==='moov'){ moovEnd=top[i].p+top[i].s; break; } }\n var stsdL = RG_find(bytes,28,moovEnd,'stsd');\n \/\/ ★检测 ByteDance 私有编码:检查 stsd 里的 entry fourcc(bv2C = 私有格式,Android 解码器不支持)\n var hasBv2C = false;\n for (var si2 = 0; si2 < stsdL.length; si2++) {\n var entryKids = RG_boxes(bytes, stsdL[si2].p + 16, stsdL[si2].p + stsdL[si2].s);\n for (var ei2 = 0; ei2 < entryKids.length; ei2++) {\n if (entryKids[ei2].t === 'bv2C' || entryKids[ei2].t === 'bv2c') { hasBv2C = true; }\n }\n }\n var n1=0,n2=0,n3=0;\n for (var i=0;i<stsdL.length;i++){\n var kk = RG_boxes(bytes, stsdL[i].p+16, stsdL[i].p+stsdL[i].s);\n for (var k=0;k<kk.length;k++){\n var e=kk[k];\n if (e.t==='encv'||e.t==='enca'){\n if (e.t==='encv'){ bytes[e.p+4]=0x68;bytes[e.p+5]=0x65;bytes[e.p+6]=0x76;bytes[e.p+7]=0x31;n1++; }\n else { bytes[e.p+4]=0x6d;bytes[e.p+5]=0x70;bytes[e.p+6]=0x34;bytes[e.p+7]=0x61;n2++; }\n var eEnd=Math.min(e.p+e.s,BL);\n for (var p3=e.p+8;p3+8<=eEnd;p3++){\n if ((bytes[p3]&255)===0x73&&(bytes[p3+1]&255)===0x69&&(bytes[p3+2]&255)===0x6e&&(bytes[p3+3]&255)===0x66){\n var sz4=RG_U32(bytes,p3-4);\n if (sz4>=8 && p3-4+sz4<=BL){\n bytes[p3]=0x66;bytes[p3+1]=0x72;bytes[p3+2]=0x65;bytes[p3+3]=0x65;\n for (var j=p3+4;j<p3-4+sz4;j++) bytes[j]=0;\n n3++; p3=p3-4+sz4-1;\n }\n }\n }\n }\n }\n }\n var stbls = RG_find(bytes,28,moovEnd,'stbl');\n var total=0;\n for (var si=0;si<stbls.length;si++){\n var sp=stbls[si].p, ss=stbls[si].s;\n var kids=RG_boxes(bytes,sp+8,Math.min(sp+ss,BL));\n var senc=null,stsz=null,stco=null,stsc=null;\n for (var i2=0;i2<kids.length;i2++){\n if(kids[i2].t==='senc')senc=kids[i2];\n if(kids[i2].t==='stsz')stsz=kids[i2];\n if(kids[i2].t==='stco')stco=kids[i2];\n if(kids[i2].t==='stsc')stsc=kids[i2];\n }\n if(!senc||!stsz||!stco||!stsc) continue;\n var dflt=RG_U32(bytes,stsz.p+12), cnt=RG_U32(bytes,stsz.p+16);\n var nch=RG_U32(bytes,stco.p+12), nent=RG_U32(bytes,stsc.p+12);\n var ef=[],es=[];\n for (var j=0;j<nent;j++){ ef.push(RG_U32(bytes,stsc.p+16+j*12)); es.push(RG_U32(bytes,stsc.p+16+j*12+4)); }\n var sidx=0,eidx=0;\n for (var c=0;c<nch && sidx<cnt;c++){\n while(eidx+1<nent && ef[eidx+1]<=c+1) eidx++;\n var spc=es[eidx];\n var off=RG_U32(bytes,stco.p+16+c*4);\n for (var k2=0;k2<spc && sidx<cnt;k2++){\n var size=dflt?dflt:RG_U32(bytes,stsz.p+20+sidx*4);\n if(off+size>BL){c=nch;break;}\n var ivOff=senc.p+16+sidx*8;\n var iv16=RG_S2B(String.fromCharCode(\n bytes[ivOff]&255,bytes[ivOff+1]&255,bytes[ivOff+2]&255,bytes[ivOff+3]&255,\n bytes[ivOff+4]&255,bytes[ivOff+5]&255,bytes[ivOff+6]&255,bytes[ivOff+7]&255,\n 0,0,0,0,0,0,0,0));\n var cph=RG_CIPHER.getInstance('AES\/CTR\/NoPadding');\n cph.init(RG_CIPHER.DECRYPT_MODE, sk, new RG_IVP(iv16));\n var dec=cph.doFinal(RG_ARR.copyOfRange(bytes,off,off+size));\n RG_SYS.arraycopy(dec,0,bytes,off,size);\n off+=size; sidx++; total++;\n }\n }\n }\n \/\/ ★E版兼容:清除 CENC 样本加密信息(senc\/saiz\/saio)\n \/\/ Team版播放器会忽略它们,但 E版播放器见到 senc 就认为文件仍加密 → 拒绝播放\n var n4=0,n5=0,n6=0;\n for (var si3=0; si3<stbls.length; si3++){\n var sp2=stbls[si3].p, ss2=stbls[si3].s;\n var kids2=RG_boxes(bytes,sp2+8,Math.min(sp2+ss2,BL));\n for (var i3=0;i3<kids2.length;i3++){\n var e3=kids2[i3];\n if (e3.t==='senc'||e3.t==='saiz'||e3.t==='saio'){\n if (e3.t==='senc') n4++; else if (e3.t==='saiz') n5++; else n6++;\n \/\/ 改成 free(保留 box 大小与位置,只改 fourcc 并清零 payload)\n bytes[e3.p+4]=0x66; bytes[e3.p+5]=0x72; bytes[e3.p+6]=0x65; bytes[e3.p+7]=0x65;\n for (var j3=e3.p+8;j3<Math.min(e3.p+e3.s,BL);j3++) bytes[j3]=0;\n }\n }\n }\n return {samples:total, encv:n1, enca:n2, sinf:n3, bv2c:hasBv2C, senc:n4, saiz:n5, saio:n6};\n}\n\n\/\/ ---------- 分块处理(大文件用,避免 hex 通道 OOM)----------\n\/\/ 从头部(含 moov)提取样本索引为简单数组:[offset, size, iv8hex]\nfunction RG_buildIndex(headB){\n var BL = headB.length;\n var moovEnd = BL;\n var top = RG_boxes(headB, 0, Math.min(BL, 2000000));\n for (var i=0;i<top.length;i++){ if(top[i].t==='moov'){ moovEnd=top[i].p+top[i].s; break; } }\n \/\/ 总大小:★遍历找 mdat(moov 之后可能有 free\/skip 等 box)\n var total = moovEnd;\n var mdatAt = -1;\n var lim = Math.min(BL - 8, moovEnd + 200000);\n for (var q = moovEnd; q < lim; q++) {\n if (RG_STR(headB, q+4, 4) === 'mdat') {\n mdatAt = q;\n total = q + RG_U32(headB, q);\n break;\n }\n }\n \/\/ 兜底:用样本最大偏移 + size\n if (mdatAt < 0) {\n var maxEnd = moovEnd;\n \/\/ samples 在后面构建,此处先用 stco 粗估\n var stbls0 = RG_find(headB, 28, Math.min(moovEnd, BL), 'stbl');\n for (var z0=0; z0<stbls0.length; z0++){\n var kids0 = RG_boxes(headB, stbls0[z0].p+8, Math.min(stbls0[z0].p+stbls0[z0].s, BL));\n for (var z1=0; z1<kids0.length; z1++){\n if (kids0[z1].t === 'stco') {\n var nch0 = RG_U32(headB, kids0[z1].p+12);\n var lastOff = RG_U32(headB, kids0[z1].p+16+(nch0-1)*4);\n if (lastOff > maxEnd) maxEnd = lastOff;\n }\n }\n }\n if (maxEnd > total) total = maxEnd + 1000000; \/\/ 粗估最后一个 chunk 大小\n }\n \/\/ fourcc 修复位置(encv\/enca → hev1\/mp4a,sinf → free)\n var fixes = [];\n var stsdL = RG_find(headB, 28, Math.min(moovEnd, BL), 'stsd');\n for (var si=0; si<stsdL.length; si++){\n var kk = RG_boxes(headB, stsdL[si].p+16, stsdL[si].p+stsdL[si].s);\n for (var k=0;k<kk.length;k++){\n var e = kk[k];\n if (e.t==='encv'||e.t==='enca'){\n fixes.push({pos: e.p+4, to: (e.t==='encv') ? 'hev1' : 'mp4a'});\n \/\/ 找内部 sinf\n var eEnd = Math.min(e.p+e.s, BL);\n for (var p3=e.p+8; p3+8<=eEnd; p3++){\n if ((headB[p3]&255)===0x73&&(headB[p3+1]&255)===0x69&&(headB[p3+2]&255)===0x6e&&(headB[p3+3]&255)===0x66){\n var sz4 = RG_U32(headB, p3-4);\n if (sz4>=8 && p3-4+sz4<=BL){\n fixes.push({pos: p3, to: 'free', zeroFrom: p3+4, zeroTo: p3-4+sz4});\n p3 = p3-4+sz4-1;\n }\n }\n }\n }\n }\n }\n \/\/ 样本索引\n var samples = [];\n var stbls = RG_find(headB, 28, Math.min(moovEnd, BL), 'stbl');\n for (var si2=0; si2<stbls.length; si2++){\n var sp = stbls[si2].p, ss = stbls[si2].s;\n var kids = RG_boxes(headB, sp+8, Math.min(sp+ss, BL));\n var senc=null, stsz=null, stco=null, stsc=null;\n for (var i2=0;i2<kids.length;i2++){\n if(kids[i2].t==='senc') senc=kids[i2];\n if(kids[i2].t==='stsz') stsz=kids[i2];\n if(kids[i2].t==='stco') stco=kids[i2];\n if(kids[i2].t==='stsc') stsc=kids[i2];\n }\n if(!senc||!stsz||!stco||!stsc) continue;\n var dflt = RG_U32(headB, stsz.p+12), cnt = RG_U32(headB, stsz.p+16);\n var nch = RG_U32(headB, stco.p+12), nent = RG_U32(headB, stsc.p+12);\n var ef=[], es=[];\n for (var j=0;j<nent;j++){ ef.push(RG_U32(headB, stsc.p+16+j*12)); es.push(RG_U32(headB, stsc.p+16+j*12+4)); }\n var sidx=0, eidx=0;\n for (var c=0; c<nch && sidx<cnt; c++){\n while(eidx+1<nent && ef[eidx+1] <= c+1) eidx++;\n var spc = es[eidx];\n var off = RG_U32(headB, stco.p+16+c*4);\n for (var k2=0; k2<spc && sidx<cnt; k2++){\n var size = dflt ? dflt : RG_U32(headB, stsz.p+20+sidx*4);\n var ivOff = senc.p+16+sidx*8;\n samples.push({off: off, size: size, iv: [\n headB[ivOff]&255, headB[ivOff+1]&255, headB[ivOff+2]&255, headB[ivOff+3]&255,\n headB[ivOff+4]&255, headB[ivOff+5]&255, headB[ivOff+6]&255, headB[ivOff+7]&255\n ]});\n off += size; sidx++;\n }\n }\n }\n return {total: total, moovEnd: moovEnd, fixes: fixes, samples: samples};\n}\n\n\/\/ 对单个块做解密(blockStart = 块在文件中的起始偏移)\nfunction RG_decryptBlock(block, blockStart, keyHex, samples){\n var BL = block.length;\n var sk = new RG_SK(RG_H2B(keyHex), 'AES');\n var cnt = 0;\n \/\/ 二分找到第一个可能重叠的样本\n var lo = 0, hi = samples.length - 1, start = samples.length;\n while (lo <= hi) {\n var mid = (lo + hi) >> 1;\n if (samples[mid].off + samples[mid].size > blockStart) { start = mid; hi = mid - 1; }\n else lo = mid + 1;\n }\n for (var i = start; i < samples.length; i++){\n var s = samples[i];\n if (s.off >= blockStart + BL) break;\n var gEnd = s.off + s.size;\n if (gEnd <= blockStart) continue;\n var os = Math.max(s.off, blockStart) - blockStart;\n var oe = Math.min(gEnd, blockStart + BL) - blockStart;\n if (oe <= os) continue;\n var iv16 = RG_S2B(String.fromCharCode(s.iv[0], s.iv[1], s.iv[2], s.iv[3], s.iv[4], s.iv[5], s.iv[6], s.iv[7], 0,0,0,0,0,0,0,0));\n var cph = RG_CIPHER.getInstance('AES\/CTR\/NoPadding');\n cph.init(RG_CIPHER.DECRYPT_MODE, sk, new RG_IVP(iv16));\n var dec = cph.doFinal(RG_ARR.copyOfRange(block, os, oe));\n RG_SYS.arraycopy(dec, 0, block, os, oe - os);\n cnt++;\n }\n return cnt;\n}\n\n\/\/ 应用 fourcc 修复到块(如果块包含修复位置)\nfunction RG_applyFixes(block, blockStart, fixes){\n for (var i=0; i<fixes.length; i++){\n var f = fixes[i];\n if (f.pos >= blockStart && f.pos + 4 <= blockStart + block.length) {\n var p = f.pos - blockStart;\n block[p] = f.to.charCodeAt(0); block[p+1] = f.to.charCodeAt(1);\n block[p+2] = f.to.charCodeAt(2); block[p+3] = f.to.charCodeAt(3);\n if (f.zeroFrom !== undefined) {\n var zs = Math.max(f.zeroFrom, blockStart) - blockStart;\n var ze = Math.min(f.zeroTo, blockStart + block.length) - blockStart;\n for (var z = zs; z < ze; z++) block[z] = 0;\n }\n }\n }\n}\n\n\/\/ 播放数据(preload + fallback)\n\/\/ ★ 关键:video_model.video_list[].main_url 是明文 URL;\n\/\/ fallback_api 响应里的 video_list.video_N.main_url 才是 spade 密文(需解密)\nfunction HG_playData(JV, vid, sid){\n var BP = {detail_page_version:0,device_level:3,disable_digg_stat:false,need_all_video_definition:true,\n need_mp4_align:false,use_os_player:false,use_server_dns:false,video_platform:1024};\n var raw = String(HG_API(JV,'\/novel\/player\/multi_video_model\/preload\/v1',{biz_param:BP,dr_scene:'preload',mixed_video_id_map:{'1004':[vid]}}));\n var j = null; try { j = JSON.parse(raw); } catch(e) {}\n if (!j || j.code !== 0 || !j.data) return null;\n var entry = j.data[vid] || j.data[Object.keys(j.data)[0]];\n if (!entry || !entry.video_model) return null;\n var vm = typeof entry.video_model === 'string' ? JSON.parse(entry.video_model) : entry.video_model;\n \/\/ 画质偏好:默认 720p,可用书源变量 hgq 覆盖(360\/480\/540\/720\/1080)\n var prefQ = '';\n try { prefQ = String(JV.get ? (JV.get('hgq') || '') : ''); } catch(e0) {}\n if (!prefQ) { try { prefQ = String(source.get('hgq') || ''); } catch(e1) {} }\n if (!prefQ) prefQ = '480'; \/\/ 默认 480p:平衡内存与清晰度(720p+ 在校验器连续执行时可能 OOM)\n var wantDef = prefQ + 'p';\n \/\/ ★只选 bytevc1(标准 H.265 \/ hvcC):bytevc2 是 ByteDance 私有 bv2C 编码,Android 解码器不支持\n function isStdCodec(v){ return String((v.video_meta||{}).codec_type||'') === 'bytevc1'; }\n \/\/ 第一优先:目标清晰度 + bytevc1\n var pick = null, pickSpadeA = '', pickDef = '';\n for (var i = 0; i < vm.video_list.length; i++) {\n var v = vm.video_list[i];\n var ei = v.encrypt_info || {};\n if (!ei.spade_a) continue;\n var def = String((v.video_meta||{}).definition||'');\n if (def === wantDef && isStdCodec(v)) { pick = v; pickSpadeA = String(ei.spade_a); pickDef = def; break; }\n }\n \/\/ 第二优先:任意 bytevc1(选 size 最大)\n if (!pick) {\n var bestSize = -1;\n for (var i2 = 0; i2 < vm.video_list.length; i2++) {\n var v2 = vm.video_list[i2];\n var ei2 = v2.encrypt_info || {};\n if (!ei2.spade_a || !isStdCodec(v2)) continue;\n var sz = parseInt((v2.video_meta||{}).size || '0', 10) || 0;\n if (sz > bestSize) { bestSize = sz; pick = v2; pickSpadeA = String(ei2.spade_a); pickDef = String((v2.video_meta||{}).definition||''); }\n }\n }\n \/\/ 第三优先(兜底):目标清晰度,任意 codec(可能会遇到 bv2C)\n if (!pick) {\n for (var i3 = 0; i3 < vm.video_list.length; i3++) {\n var v3 = vm.video_list[i3];\n var ei3 = v3.encrypt_info || {};\n if (!ei3.spade_a) continue;\n var def3 = String((v3.video_meta||{}).definition||'');\n if (def3 === wantDef) { pick = v3; pickSpadeA = String(ei3.spade_a); pickDef = def3; break; }\n }\n }\n if (!pick) return null;\n \/\/ ★体积上限:hex 通道内存峰值约为文件的 2 倍,超过 24MB 会 OOM\n \/\/ 优先在选中档位中找 ≤ 上限的;找不到则用最小的(并标记)\n var maxMB = 20;\n try { var mv = String(source.get('hgmax') || ''); if (mv) maxMB = parseInt(mv, 10) || 24; } catch(e5) {}\n var maxBytes = maxMB * 1048576;\n var pickSize = parseInt((pick.video_meta||{}).size || '0', 10) || 0;\n \/\/ ★体积上限:写文件需 hex 字符串(约 4×文件字节),20MB 文件约需 80MB 峰值;再大必然 OOM\n if (pickSize > maxBytes) {\n var wantStd = isStdCodec(pick);\n var alt = null, altSize = -1, altSpadeA = '', altDef = '';\n for (var i4 = 0; i4 < vm.video_list.length; i4++) {\n var v4 = vm.video_list[i4];\n var ei4 = v4.encrypt_info || {};\n if (!ei4.spade_a) continue;\n if (isStdCodec(v4) !== wantStd) continue;\n var sz4 = parseInt((v4.video_meta||{}).size || '0', 10) || 0;\n if (sz4 <= maxBytes && sz4 > altSize) { altSize = sz4; alt = v4; altSpadeA = String(ei4.spade_a); altDef = String((v4.video_meta||{}).definition||''); }\n }\n if (alt) { pick = alt; pickSpadeA = altSpadeA; pickDef = altDef; pickSize = altSize; }\n else { return { spade: null, spadeA: null, seed: null, def: pickDef, tooBig: true, sizeMB: (pickSize\/1048576).toFixed(0) }; }\n }\n var bestSpadeA = pickSpadeA, bestDef = pickDef;\n \/\/ fallback 响应里才有 spade 密文 URL + key_seed\n var fb = vm.fallback_api && vm.fallback_api.fallback_api;\n if (!fb) return null;\n var fbr = String(JV.ajax(fb + ','+String.fromCharCode(123,34,104,101,97,100,101,114,115,34,58,123,34,85,115,101,114,45,65,103,101,110,116,34,58,34,99,111,109,46,112,104,111,101,110,105,120,46,114,101,97,100,47,55,50,57,51,50,34,125,125)));\n var fj = null; try { fj = JSON.parse(fbr); } catch(e) {}\n if (!fj || !fj.video_info || !fj.video_info.data) return null;\n var fd = fj.video_info.data;\n var seed = String(fd.key_seed || '');\n if (!seed) return null;\n var fvl = fd.video_list || {};\n \/\/ 优先按 definition 匹配;否则取 size 最大的\n var bestUrl = '', bestFs = -1, exactUrl = '';\n for (var k in fvl) {\n var fv = fvl[k] || {};\n var mu = String(fv.main_url || '');\n if (!mu || mu.indexOf('http') === 0) continue; \/\/ 只要 spade 密文\n var fdef = String(fv.definition || '');\n if (fdef === bestDef) exactUrl = mu;\n var fsz = parseInt(fv.size || '0', 10) || 0;\n if (fsz > bestFs) { bestFs = fsz; bestUrl = mu; }\n }\n if (exactUrl) bestUrl = exactUrl;\n if (!bestUrl) return null;\n return { spade: bestUrl, spadeA: bestSpadeA, seed: seed, def: bestDef, sizeMB: ((parseInt((pick.video_meta||{}).size||'0',10)||0)\/1048576).toFixed(1) };\n}\n\n\/\/ ===== 红果短剧 API 版规则函数(供书源各字段调用)=====\n\/\/ --- 搜索:网页版 \/search\/{kw},解析内嵌 searchList ---\n\n\/\/ 括号配平提取 JSON 数组\/对象\nfunction HG_jsonArray(text, marker){\nvar i=String(text).indexOf(marker);\nif(i<0) return null;\nvar j=i+marker.length;\nwhile(j<text.length && (text[j]===' '||text[j]===String.fromCharCode(10)||text[j]===String.fromCharCode(9))) j++;\nvar open=text[j];\nif(open!=='[' && open!=='{') return null;\nvar close=open==='['?']':'}';\nvar depth=0,quoted=false,esc=false,end=j;\nfor(;end<text.length;end++){\nvar ch=text[end];\nif(quoted){ if(esc)esc=false; else if(ch===String.fromCharCode(92))esc=true; else if(ch===String.fromCharCode(34))quoted=false; continue; }\nif(ch===String.fromCharCode(34))quoted=true;\nelse if(ch===open)depth++;\nelse if(ch===close && --depth===0) break;\n}\ntry{ return JSON.parse(text.substring(j,end+1)); }catch(e){ return null; }\n}\n\/\/ --- 详情:API multi_video_detail\/v1 ---\nfunction HG_detail(JV, sid){\nvar BP={detail_page_version:0,disable_digg_stat:false,disable_video_relate_book:false,\nneed_all_video_definition:false,need_mp4_align:false,screen_width_px:'900',source:7,\nuse_os_player:false,use_server_dns:false};\nvar r=HG_API(JV,'\/novel\/player\/multi_video_detail\/v1\/',{biz_param:BP,series_id:sid});\nvar j=null; try{ j=JSON.parse(String(r)); }catch(e){}\nif(!j || j.code!==0 || !j.data) return null;\nvar d=j.data[sid]||j.data[Object.keys(j.data)[0]];\nif(!d) return null;\nvar vd=d.video_data||{};\nreturn {sid:sid,\nname:String(vd.series_title||''),\nauthor:'红果短剧',\nkind:String(vd.episode_cnt||0)+'集',\nintro:String(vd.series_intro||''),\ncoverUrl:String(vd.series_cover||''),\nepisodeCnt:vd.episode_cnt||0,\nvideoList:vd.video_list||[]};\n}\n\/\/ --- 目录:从 video_list 生成章节 ---\nfunction HG_chapters(JV, sid){\nvar d=HG_detail(JV,sid);\nif(!d) throw new Error('获取剧集失败:接口未返回数据,请检查网络或稍后重试');\nvar out=[];\nvar vl=d.videoList||[];\nfor(var i=0;i<vl.length;i++){\nvar v=vl[i]||{};\nvar idx=v.vid_index||(i+1);\nvar vid=String(v.vid||'');\nif(!vid) continue;\nout.push(JSON.stringify({name:'第'+idx+'集',url:'https:\/\/api5-normal-sinfonlineb.fqnovel.com\/play?vid='+vid+'&series_id='+sid+'&idx='+idx}));\n}\nif(!out.length) throw new Error('该剧暂无可用剧集');\nreturn out;\n}\n\/\/ --- 正文:取播放直链 ---\nfunction HG_play(JV, vid, sid){\nvar BP={detail_page_version:0,device_level:3,disable_digg_stat:false,need_all_video_definition:true,\nneed_mp4_align:false,use_os_player:false,use_server_dns:false,video_platform:1024};\nvar r=HG_API(JV,'\/novel\/player\/multi_video_model\/preload\/v1',{biz_param:BP,dr_scene:'preload',mixed_video_id_map:{'1004':[vid]}});\nvar j=null; try{ j=JSON.parse(String(r)); }catch(e){}\nif(!j||j.code!==0||!j.data) return null;\nvar item=j.data[vid];\nif(!item) return null;\nvar vm=null; try{ vm=JSON.parse(item.video_model); }catch(e){}\nif(!vm||!vm.video_list||!vm.video_list.length) return null;\n\/\/ 选最高清晰度\nvar best=null,bestScore=-1;\nfor(var i=0;i<vm.video_list.length;i++){\nvar v=vm.video_list[i];\nif(!v.main_url) continue;\nvar meta=v.video_meta||{};\nvar def=parseInt(String(meta.definition||'0'),10)||0;\nif(def<100){ var mm=String(meta.definition||'').match(\/([0-9]+)\/); def=mm?parseInt(mm[1],10):0; }\nvar sc=def*1000000+(parseInt(meta.vwidth||0,10)*parseInt(meta.vheight||0,10));\nif(sc>bestScore){ bestScore=sc; best=v; }\n}\nif(!best) return null;\nreturn {url:String(best.main_url),backup:String(best.backup_url||''),def:String((best.video_meta||{}).definition||''),expire:vm.url_expire||item.expire_time||0};\n}\n\/\/ --- 关键词反解(bookList 上下文无 key 变量)---\n\n\/\/ --- 站点状态自检(登录界面按钮用)---\nfunction HG_check(JV, SV){\nvar out=[];\n\/\/ 1. 网页可达性\ntry{\nvar t0=new Date().getTime();\nvar r=String(JV.ajax('https:\/\/hongguoduanju.com\/'));\nvar t1=new Date().getTime();\nout.push(r.length>10000 ? ('✅ 网页版可达 ('+(t1-t0)+'ms, '+r.length+'字节)') : ('⚠️ 网页版响应异常 ('+r.length+'字节)'));\n}catch(e){ out.push('❌ 网页版不可达: '+String(e).substring(0,60)); }\n\/\/ 2. API 可达性 + 签名自检\ntry{\nvar t2=new Date().getTime();\nvar r2=HG_API(JV,'\/novel\/player\/multi_video_detail\/v1\/',{biz_param:{detail_page_version:0,disable_digg_stat:false,disable_video_relate_book:false,need_all_video_definition:false,need_mp4_align:false,screen_width_px:'900',source:7,use_os_player:false,use_server_dns:false},series_id:'7503745441910017049'});\nvar t3=new Date().getTime();\nvar j=null; try{ j=JSON.parse(String(r2)); }catch(e){}\nif(j && j.code===0){ out.push('✅ API 签名有效 ('+(t3-t2)+'ms)'); }\nelse if(j){ out.push('⚠️ API 返回异常 code='+j.code+' '+(j.message||'')); }\nelse { out.push('⚠️ API 响应无法解析 ('+String(r2).length+'字节)'); }\n}catch(e){ out.push('❌ API 不可达: '+String(e).substring(0,60)); }\nout.push('【说明】 本剧集无需登录,游客即可观看全集');\nreturn out.join(String.fromCharCode(10));\n}\n\/\/ --- 发现页:榜单\/分类(网页版 HTML 解析)---\n\n\/\/ --- 发现页参数反解 ---\n\n\n\n\/\/ 清理上次生成的解密视频文件(避免缓存堆积)\nfunction RG_cleanOld(JV, SV){\n try {\n var oldPath = String(SV.get('rg_last') || '');\n if (oldPath) {\n try { JV.deleteFile(oldPath); } catch(e1) {}\n SV.put('rg_last', '');\n }\n } catch(e2) {}\n}\n\n\/\/ 裁剪解密缓存:只保留最近 N 个,其余删除(避免缓存无限堆积)\nfunction RG_trimCache(JV, SV){\n try {\n var keys = String(SV.get('rg_keys') || '');\n var arr = keys ? keys.split(',') : [];\n \/\/ 新键已在规则里 put,这里把当前键加入列表\n \/\/ 保留最近 8 个\n var MAXK = 8;\n while (arr.length > MAXK) {\n var oldKey = arr.shift();\n try {\n var p = String(SV.get(oldKey) || '');\n if (p) { try { JV.deleteFile(p); } catch(e1) {} }\n SV.put(oldKey, '');\n } catch(e2) {}\n }\n SV.put('rg_keys', arr.join(','));\n } catch(e3) {}\n}\n\n\/\/ ============================================================\n\/\/ 以下为「视频书源」新增\/覆盖函数(番茄短剧·漫剧)\n\/\/ 所有函数都把 java\/source 作为显式参数传入,避免 jsLib 作用域限制\n\/\/ ============================================================\n\n\/\/ —— 通用请求(GET,带 phoenix 头 + 签名)——\nfunction FV_get(JV, path, extra, host){\n var q={aid:'8662',app_name:'novelread',version_code:'72932',version_name:'7.2.9.32',\n device_platform:'android',os:'android',ssmix:'a',device_type:'V2284A',device_brand:'vivo',\n language:'zh',os_api:'32',os_version:'12',manifest_version_code:'72932',\n resolution:'1080*1920',dpi:'280',update_version_code:'72932',host_abi:'arm64-v8a',\n iid:'4439167111854618',device_id:'4439167111850522',channel:'huawei_8662_64',\n ac:'wifi',dragon_device_type:'phone',pv_player:'72932',compliance_status:'0',\n need_personal_recommend:'1',player_so_load:'1',is_android_pad_screen:'1',\n rom_version:'V417IR+release-keys',cdid:'6100e9f9-a1a0-4f65-ab47-94f5b41b8efb'};\n for(var k in (extra||{})) q[k]=extra[k];\n var arr=[]; for(var k2 in q) arr.push(k2+'='+encodeURIComponent(String(q[k2])));\n arr.push('_rticket='+String(new Date().getTime()));\n var url=(host||'https:\/\/api5-normal-sinfonlineb.fqnovel.com')+path+'?'+arr.join('&');\n var h={'user-agent':'com.phoenix.read\/72932 (Linux; U; Android 12; zh_CN; V2284A; Build\/V417IR;tt-ok\/3.12.13.20)',\n 'Accept':'application\/json; charset=utf-8,application\/x-protobuf',\n 'content-type':'application\/json; charset=utf-8','cookie':'',\n 'x-tt-token':'','x-tt-store-region':'cn-zj','x-tt-store-region-src':'did',\n 'passport-sdk-version':'5051452','sdk-version':'2'};\n var sg=HG_sign(url,{headers:h});\n for(var k3 in sg) h[k3]=sg[k3];\n return String(JV.ajax(url+','+JSON.stringify({headers:h})));\n}\n\n\/\/ —— 漫剧\/短剧 发现:第一段(取 cell_id_str + session_id)——\nfunction FV_tab(JV, tabType){\n var r=FV_get(JV,'\/reading\/bookapi\/bookmall\/tab\/v',{tab_type:String(tabType),client_template:'0',\n offset:'0',last_tab_index:'0',screen_width_px:'1281',classic_tab_style:'v3',\n bottom_tab_type:'0',client_req_type:'1'});\n var j=null; try{ j=JSON.parse(r); }catch(e){ return null; }\n if(!j.data||!j.data.tab_item) return null;\n var ti=j.data.tab_item;\n for(var i=0;i<ti.length;i++){\n if(String(ti[i].tab_type)===String(tabType)) return ti[i];\n }\n return null;\n}\n\n\/\/ —— 漫剧\/短剧 发现:第二段(取视频流,支持翻页)——\n\/\/ off=0 首屏(change_type=2),off>0 翻页(change_type=1);sid 必须回传(服务端绑设备)\nfunction FV_feed(JV, tabType, cellId, off, sid){\n var ex={cell_id:cellId,tab_type:String(tabType),client_template:'2',client_req_type:'2',\n offset:String(off),unlimited_selector_change_type:(off>0?'1':'2')};\n if(sid) ex.session_id=sid;\n var r=FV_get(JV,'\/reading\/bookapi\/bookmall\/cell\/change\/v1\/',ex);\n var j=null; try{ j=JSON.parse(r); }catch(e){ return null; }\n var d=j.data||{};\n return d;\n}\n\n\/\/ —— 从任意 cell 树里递归收集 video_data ——\nfunction FV_collect(node, out){\n if(!node) return out;\n if(Array.isArray(node)){\n for(var i=0;i<node.length;i++) FV_collect(node[i],out);\n return out;\n }\n var vd=node.video_data;\n if(vd){\n if(Array.isArray(vd)){ for(var m=0;m<vd.length;m++) out.push(vd[m]); }\n else out.push(vd);\n }\n var sub=node.cell_data;\n if(sub) FV_collect(sub,out);\n return out;\n}\n\n\/\/ —— video_data → 书源条目 ——\nfunction FV_item(v){\n if(!v) return null;\n var sid=String(v.series_id||'');\n if(!sid) return null;\n var t=String(v.title||v.series_title||'').trim();\n if(!t) return null;\n var k=[];\n if(v.episode_cnt) k.push(String(v.episode_cnt)+'集');\n if(v.vertical===false) k.push('横屏');\n if(v.score) k.push(String(v.score)+'分');\n if(v.play_cnt) k.push(String(Math.round(Number(v.play_cnt)\/10000))+'万播放');\n return JSON.stringify({name:t,author:'番茄短剧',kind:k.join(' · '),\n intro:String(v.video_desc||v.series_intro||''),\n coverUrl:String(v.cover||v.series_cover||''),\n bookUrl:'https:\/\/api5-normal-sinfonlineb.fqnovel.com\/detail?series_id='+sid});\n}\n\n\/\/ —— 发现页入口:解析出 kind 与 page 后调对应通道 ——\n\/\/ kind: manju(漫剧32) \/ zhaoju(找剧8) \/ dongman(动漫24)\nfunction FV_explore(JV, SV, kind, page){\n var ttMap={manju:32,zhaoju:8,dongman:24};\n var tt=ttMap[kind]; if(!tt) return [];\n var pg=parseInt(page||'1',10)||1;\n \/\/ cell 缓存(同一书源会话内复用;服务端 cell_id 数天不变)\n var ck='fv_cell_'+tt;\n var cellId='', sid='';\n try{ cellId=String(SV.get(ck)||''); }catch(e){}\n if(!cellId){\n var t=FV_tab(JV,tt);\n if(!t) throw new Error('发现页加载失败:无法获取频道数据('+tt+')');\n var c=t.cell_data&&t.cell_data[0]?t.cell_data[0].cell_id_str:'';\n if(!c) throw new Error('发现页加载失败:频道无 cell');\n cellId=String(c);\n try{ SV.put(ck,cellId); }catch(e2){}\n }\n \/\/ 翻页需要 session:首屏先取一次\n var sk='fv_sid_'+tt;\n try{ sid=String(SV.get(sk)||''); }catch(e3){}\n var off=(pg-1)*6;\n var d=FV_feed(JV,tt,cellId,off,sid);\n if(!d){\n \/\/ session 失效 → 重新开一次首屏拿新 session 后重试\n var t2=FV_tab(JV,tt);\n if(t2&&t2.session_id){ sid=String(t2.session_id); try{ SV.put(sk,sid); }catch(e4){} }\n d=FV_feed(JV,tt,cellId,off,sid);\n }\n if(!d) throw new Error('发现页加载失败:视频流为空');\n if(d.session_id){ try{ SV.put(sk,String(d.session_id)); }catch(e5){} }\n var arr=FV_collect((d.cell_view&&d.cell_view.cell_data)||[],[]);\n var out=[]; var seen={};\n for(var i=0;i<arr.length;i++){\n var it=FV_item(arr[i]);\n if(!it) continue;\n var o=JSON.parse(it);\n if(seen[o.bookUrl]) continue;\n seen[o.bookUrl]=1;\n out.push(it);\n }\n \/\/ has_more=false 且是最后一页 → 返回空数组让 Legado 停止翻页\n if(d.has_more===false && out.length===0) return [];\n return out;\n}\n\n\/\/ —— 搜索:suggest 通道(含视频结果)——\n\/\/ 注意:该接口固定 10 条、无翻页参数;视频条目在 query_result_v2[].video_data\nfunction FV_search(JV, kw){\n var r=FV_get(JV,'\/reading\/bookapi\/search\/suggest\/v',{query:kw,keyword:kw,q:kw});\n var j=null; try{ j=JSON.parse(r); }catch(e){ return []; }\n if(!j.data) return [];\n var v2=j.data.query_result_v2||[];\n var out=[];\n for(var i=0;i<v2.length;i++){\n var v=v2[i].video_data;\n if(!v) continue;\n var it=FV_item(v);\n if(it) out.push(it);\n }\n return out;\n}\n\n\/\/ —— 详情:multi_video_detail ——\nfunction FV_detail(JV, sid){\n var BP={detail_page_version:0,disable_digg_stat:false,disable_video_relate_book:false,\n need_all_video_definition:false,need_mp4_align:false,screen_width_px:'900',source:7,\n use_os_player:false,use_server_dns:false};\n var body={biz_param:BP,series_id:String(sid)};\n var bs=JSON.stringify(body);\n var u='https:\/\/api5-normal-sinfonlineb.fqnovel.com\/novel\/player\/multi_video_detail\/v1\/';\n var arr=[]; \n var q={aid:'8662',app_name:'novelread',version_code:'72932',version_name:'7.2.9.32',\n device_platform:'android',os:'android',ssmix:'a',device_type:'V2284A',device_brand:'vivo',\n language:'zh',os_api:'32',os_version:'12',manifest_version_code:'72932',\n resolution:'1080*1920',dpi:'280',update_version_code:'72932',host_abi:'arm64-v8a',\n iid:'4439167111854618',device_id:'4439167111850522',channel:'huawei_8662_64',\n ac:'wifi',dragon_device_type:'phone',pv_player:'72932',compliance_status:'0',\n need_personal_recommend:'1',player_so_load:'1',is_android_pad_screen:'1',\n rom_version:'V417IR+release-keys',cdid:'6100e9f9-a1a0-4f65-ab47-94f5b41b8efb'};\n for(var k in q) arr.push(k+'='+encodeURIComponent(String(q[k])));\n var url=u+'?'+arr.join('&');\n var h={'user-agent':'com.phoenix.read\/72932 (Linux; U; Android 12; zh_CN; V2284A; Build\/V417IR;tt-ok\/3.12.13.20)',\n 'Accept':'application\/json; charset=utf-8,application\/x-protobuf',\n 'content-type':'application\/json; charset=utf-8','cookie':'',\n 'x-tt-token':'','x-tt-store-region':'cn-zj','x-tt-store-region-src':'did',\n 'passport-sdk-version':'5051452','sdk-version':'2'};\n h['x-ss-stub']=HG_hx(HG_md5(HG_u8(bs))).toUpperCase();\n var sg=HG_sign(url,{headers:h,body:bs});\n for(var k3 in sg) h[k3]=sg[k3];\n var r=String(JV.ajax(url+','+JSON.stringify({method:'POST',headers:h,body:bs})));\n var j=null; try{ j=JSON.parse(r); }catch(e){ return null; }\n if(j.code!==0||!j.data) return null;\n var d=j.data[String(sid)]||j.data[Object.keys(j.data)[0]];\n if(!d) return null;\n var vd=d.video_data||{};\n return {sid:String(sid),name:String(vd.series_title||''),author:'番茄短剧',\n kind:String(vd.episode_cnt||0)+'集',\n intro:String(vd.series_intro||''),\n coverUrl:String(vd.series_cover||''),\n episodeCnt:Number(vd.episode_cnt||0),\n videoList:vd.video_list||[]};\n}\n\n\/\/ —— 播放:shortplay 通道(aid=1967,各档位 bytevc1 可播,URL 为 base64 明文)——\nfunction FV_playData(JV, vid){\n var u='https:\/\/api3-normal-sinfonlinea.fqnovel.com\/novel\/player\/video_model\/v1\/'\n +'?iid=4439167111854618&device_id=4439167111850522&aid=1967&app_name=novelapp'\n +'&version_code=72132&version_name=7.2.1.32&device_platform=android&device_brand=Xiaomi'\n +'&os_version=13&cdid=6100e9f9-a1a0-4f65-ab47-94f5b41b8efb';\n var BP={detail_page_version:0,device_level:3,disable_digg_stat:false,disable_video_relate_book:false,\n from_video_id:'',need_all_video_definition:true,need_mp4_align:false,source:4,\n use_os_player:false,use_server_dns:false,video_platform:3};\n var body={video_id:String(vid),content_type:1004,biz_param:BP};\n var bs=JSON.stringify(body);\n var h={'user-agent':'com.dragon.read','Content-Type':'application\/json'};\n h['x-ss-stub']=HG_hx(HG_md5(HG_u8(bs))).toUpperCase();\n var sg=HG_sign(u,{headers:h,body:bs});\n for(var k in sg) h[k]=sg[k];\n var r=String(JV.ajax(u+','+JSON.stringify({method:'POST',headers:h,body:bs})));\n var j=null; try{ j=JSON.parse(r); }catch(e){ return null; }\n if(j.code!==0||!j.data||!j.data.video_model) return null;\n var vm=null;\n try{ vm=(typeof j.data.video_model==='string')?JSON.parse(j.data.video_model):j.data.video_model; }catch(e2){ return null; }\n var vl=vm.video_list;\n if(!vl) return null;\n \/\/ 画质偏好:source.get('hgq') 覆盖(360\/480\/540\/720),默认 360(体积小、解密快)\n var prefQ='';\n try{ prefQ=String(JV.get?JV.get('hgq'):''); }catch(e3){}\n if(!prefQ){ try{ prefQ=String(source.get('hgq')||''); }catch(e4){} }\n if(!prefQ) prefQ='360';\n var want=prefQ+'p';\n var pick=null;\n for(var kk in vl){\n var v=vl[kk];\n if(String(v.definition||'')===want && String(v.codec_type||'')==='bytevc1'){ pick=v; break; }\n }\n if(!pick){\n var best=-1;\n for(var k2 in vl){\n var v2=vl[k2];\n if(String(v2.codec_type||'')!=='bytevc1') continue;\n var sz=parseInt(v2.size||'0',10)||0;\n if(sz>best){ best=sz; pick=v2; }\n }\n }\n if(!pick){\n for(var k3 in vl){ var v3=vl[k3]; if(String(v3.definition||'')===want){ pick=v3; break; } }\n }\n if(!pick) return null;\n var url64=String(pick.main_url||'');\n if(!url64) return null;\n var realUrl='';\n try{ realUrl=String(new Packages.java.lang.String(RG_b64d(url64),'UTF-8')); }catch(e5){\n try{ realUrl=String(new Packages.java.lang.String(Packages.java.util.Base64.getDecoder().decode(url64),'UTF-8')); }catch(e6){ return null; }\n }\n if(realUrl.indexOf('http')!==0) return null;\n var size=parseInt(pick.size||'0',10)||0;\n return {url:realUrl,def:String(pick.definition||''),size:size,\n sizeMB:Math.round(size\/1048576),spadeA:String(pick.spade_a||''),\n bv2c:(String(pick.codec_type||'').indexOf('bytevc2')>=0)};\n}\n\nfunction FV_epk(u){var s=String(u||'');var m=s.match(\/[?&]kind=([a-z]+)\/);return m?m[1]:'';}\nfunction FV_epp(u){var s=String(u||'');var m=s.match(\/[?&]page=([0-9]+)\/);return m?m[1]:'1';}\nfunction HG_SW(u){var s=String(u||'');var i=s.indexOf('suggest=');if(i<0)return '';var r=s.substring(i+8);try{return decodeURIComponent(r);}catch(e){return r;}}\nfunction FV_trimCache(JV,SV){\n try{\n var keys=String(SV.get('fv_keys')||'');\n var arr=keys?keys.split(','):[];\n var MAXK=6;\n while(arr.length>MAXK){\n var oldKey=arr.shift();\n try{\n var p=String(SV.get(oldKey)||'');\n if(p){ try{ JV.deleteFile(p); }catch(e1){} }\n SV.put(oldKey,'');\n }catch(e2){}\n }\n SV.put('fv_keys',arr.join(','));\n }catch(e3){}\n}\n\n\/\/ ★动态探测缓存目录(跨版本兼容:不同 App 包名不同)\nfunction FV_cacheDir(JV){\n var jv = JV || java; \/\/ 优先用传入的 JV(规则上下文),回退全局 java(eval_js 上下文)\n try{ jv.readFile('\/'); }catch(e){\n var m=String(e).match(\/([\/]storage[\/]emulated[\/]0[\/]Android[\/]data[\/][^\/]+[\/]cache)\/);\n if(m) return m[1];\n var m2=String(e).match(\/([\/]data[\/]data[\/][^\/]+[\/]cache)\/);\n if(m2) return m2[1];\n }\n return '';\n}\n",
"lastUpdateTime": "1791896067277",
"loginUi": "[{\"name\": \"📺 番茄短剧·漫剧\", \"type\": \"button\", \"action\": \"FV_check(java)\"}, {\"name\": \"🎚 画质:360P(默认,最快)\", \"type\": \"button\", \"action\": \"FV_qset(java,source,'360')\"}, {\"name\": \"🎚 画质:480P\", \"type\": \"button\", \"action\": \"FV_qset(java,source,'480')\"}, {\"name\": \"🎚 画质:540P\", \"type\": \"button\", \"action\": \"FV_qset(java,source,'540')\"}, {\"name\": \"🎚 画质:720P(较慢)\", \"type\": \"button\", \"action\": \"FV_qset(java,source,'720')\"}, {\"name\": \"🔍 检查站点连通\", \"type\": \"button\", \"action\": \"FV_check(java)\"}, {\"name\": \"🧹 清空视频缓存\", \"type\": \"button\", \"action\": \"FV_clearc(java,source)\"}]",
"loginUrl": "\nfunction FV_qset(JV,SV,q){\n try{ SV.put('hgq',String(q)); }catch(e){ try{ JV.put('hgq',String(q)); }catch(e2){} }\n try{ JV.toast('画质已切换:'+q+'P'); }catch(e3){}\n try{ JV.longToast('🎚 画质已切换:'+q+'P\\n(下一集生效)'); }catch(e4){}\n return 'ok';\n}\nfunction FV_clearc(JV,SV){\n var n=0;\n try{\n var kl=String(SV.get('fv_keys')||'');\n var ka=kl?kl.split(','):[];\n for(var i=0;i<ka.length;i++){\n var p=String(SV.get(ka[i])||'');\n if(p){ try{ JV.deleteFile(p); n++; }catch(e1){} }\n SV.put(ka[i],'');\n }\n SV.put('fv_keys','');\n }catch(e2){}\n try{ JV.longToast('🧹 已清空 '+n+' 个视频缓存文件'); }catch(e3){}\n return 'ok';\n}\nfunction FV_check(JV){\n var out=[];\n try{\n var t0=new Date().getTime();\n var r=HG_API(JV,'\/novel\/player\/multi_video_detail\/v1\/',{biz_param:{detail_page_version:0,disable_digg_stat:false,disable_video_relate_book:false,need_all_video_definition:false,need_mp4_align:false,screen_width_px:'900',source:7,use_os_player:false,use_server_dns:false},series_id:'7503745441910017049'});\n var t1=new Date().getTime();\n var j=null; try{ j=JSON.parse(String(r)); }catch(e){}\n if(j&&j.code===0){ out.push('✅ 接口签名有效 ('+(t1-t0)+'ms)'); }\n else if(j){ out.push('⚠️ 接口返回异常 code='+j.code); }\n else { out.push('⚠️ 接口响应无法解析'); }\n }catch(e){ out.push('❌ 接口不可达:'+String(e).substring(0,60)); }\n try{\n var q='';\n try{ q=String(source.get('hgq')||''); }catch(e2){}\n out.push('🎚 当前画质:'+(q||'360')+'P(默认 360P)');\n }catch(e3){}\n out.push('本剧集无需登录,游客即可观看全集');\n return out.join(String.fromCharCode(10));\n}\nfunction login(){ return 'ok'; }\n",
"respondTime": 24824,
"ruleBookInfo": {
"author": "$.author",
"coverUrl": "$.coverUrl",
"init": "@js:(function(){var sid=HG_SID(String(baseUrl));if(!sid)throw new Error('无效剧集ID');var d=FV_detail(java,sid);if(!d)throw new Error('获取剧集详情失败,请检查网络或稍后重试');var out={name:d.name,author:d.author,kind:d.kind,intro:d.intro,coverUrl:d.coverUrl,tocUrl:'https:\/\/api5-normal-sinfonlineb.fqnovel.com\/detail?series_id='+sid};return JSON.stringify(out);})()",
"intro": "$.intro",
"kind": "$.kind",
"name": "$.name",
"tocUrl": "$.tocUrl"
},
"ruleContent": {
"content": "@js:\n(function(){\ntry{\nvar FVCD='';\ntry{ java.readFile('\/'); }catch(e0){\n var mm=String(e0).match(\/([\/]storage[\/]emulated[\/]0[\/]Android[\/]data[\/][^\/]+[\/]cache)\/);\n if(mm) FVCD=mm[1];\n if(!FVCD){\n var m2=String(e0).match(\/([\/]data[\/]data[\/][^\/]+[\/]cache)\/);\n if(m2) FVCD=m2[1];\n }\n}\nif(!FVCD) FVCD='\/storage\/emulated\/0\/Android\/data\/com.legado.app.release\/cache';\n\nvar vid=HG_VID(String(baseUrl));\nif(!vid)throw new Error('章节参数缺失');\nvar d=FV_playData(java,vid);\nif(!d)throw new Error('获取播放地址失败:该集可能已下架或接口异常');\nif(d.bv2c)throw new Error('该清晰度使用了字节私有编码,安卓解码器不支持。请在书源菜单→登录 调整画质');\nvar defName=d.def;\nvar cacheKey='fv_'+vid+'_'+defName.replace('p','');\nvar cachedPath='';\ntry{ cachedPath=String(source.get(cacheKey)||''); }catch(e0){}\nif(cachedPath){\n try{\n var cb=java.readFile(cachedPath);\n if(cb&&cb.length>100000){ cb=null; try{Packages.java.lang.System.gc();}catch(e01){} java.toast('使用本地缓存,秒播'); return 'file:\/\/'+FVCD+cachedPath; }\n cb=null;\n }catch(e02){}\n}\njava.toast('正在解密视频('+defName+')…');\nvar keyHex=RG_cencKey(java,d.spadeA);\nvar hexStr=String(java.ajax(d.url+',{'+String.fromCharCode(34)+'type'+String.fromCharCode(34)+':'+String.fromCharCode(34)+'hex'+String.fromCharCode(34)+','+String.fromCharCode(34)+'retry'+String.fromCharCode(34)+':2,'+String.fromCharCode(34)+'headers'+String.fromCharCode(34)+':{'+String.fromCharCode(34)+'User-Agent'+String.fromCharCode(34)+':'+String.fromCharCode(34)+'com.phoenix.read\/72932'+String.fromCharCode(34)+'}}'));\nif(hexStr.length<1000)throw new Error('视频下载失败(响应过短)');\nvar bytes=RG_decodeHex(hexStr);\nhexStr=null;\ntry{ Packages.java.lang.System.gc(); }catch(g0){}\nvar r=RG_decrypt(bytes,keyHex);\nif(r.samples<100)throw new Error('解密异常:样本数 '+r.samples);\nif(r.bv2c)throw new Error('该清晰度使用了字节私有编码,安卓解码器不支持');\nvar outHex=RG_encodeHex(bytes);\nbytes=null;\ntry{ Packages.java.lang.System.gc(); }catch(g1){}\nvar path=String(java.downloadFile(outHex,'https:\/\/x\/'+cacheKey+'.mp4,{'+String.fromCharCode(34)+'type'+String.fromCharCode(34)+':'+String.fromCharCode(34)+'mp4'+String.fromCharCode(34)+'}'));\noutHex=null;\ntry{ Packages.java.lang.System.gc(); }catch(g2){}\ntry{\n source.put(cacheKey,path);\n var kl=String(source.get('fv_keys')||'');\n var ka=kl?kl.split(','):[];\n if(ka.indexOf(cacheKey)<0)ka.push(cacheKey);\n source.put('fv_keys',ka.join(','));\n}catch(e9){}\nFV_trimCache(java,source);\nreturn 'file:\/\/'+FVCD+path;\n}catch(e){ return 'DIAG_ERR: '+String(e.message||e); }\n})()"
},
"ruleExplore": {
"author": "$.author",
"bookList": "@js:(function(){var k=FV_epk(String(baseUrl));var pg=FV_epp(String(baseUrl));if(!k)throw new Error('发现参数缺失');return FV_explore(java,source,k,pg);})()",
"bookUrl": "$.bookUrl",
"coverUrl": "$.coverUrl",
"intro": "$.intro",
"kind": "$.kind",
"name": "$.name"
},
"ruleSearch": {
"author": "$.author",
"bookList": "@js:(function(){var kw=HG_SW(String(baseUrl));if(!kw)throw new Error('缺少搜索关键词');var l=FV_search(java,kw);if(!l.length)throw new Error('未搜索到短剧\/漫剧:'+kw);return l;})()",
"bookUrl": "$.bookUrl",
"checkKeyWord": "都市",
"coverUrl": "$.coverUrl",
"intro": "$.intro",
"kind": "$.kind",
"name": "$.name"
},
"ruleToc": {
"chapterList": "@js:(function(){var sid=HG_SID(String(baseUrl));if(!sid)throw new Error('无效剧集ID');var d=FV_detail(java,sid);if(!d)throw new Error('获取剧集失败:接口未返回数据,请检查网络或稍后重试');var out=[];var vl=d.videoList||[];for(var i=0;i<vl.length;i++){var v=vl[i]||{};var vid=String(v.vid||'');if(!vid)continue;var idx=parseInt(v.vid_index||'0',10)||(i+1);out.push(JSON.stringify({name:'第'+idx+'集',url:'https:\/\/api5-normal-sinfonlineb.fqnovel.com\/play?vid='+vid+'&series_id='+sid+'&idx='+idx}));}if(!out.length)throw new Error('该剧暂无可用剧集');return out;})()",
"chapterName": "$.name",
"chapterUrl": "$.url"
},
"searchUrl": "https:\/\/api5-normal-sinfonlineb.fqnovel.com\/search?suggest={{java.encodeURI(key)}}",
"weight": 0
}