[{"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}]