diff --git a/dianshi.json b/dianshi.json
index 64ca7b83..0e49980e 100644
--- a/dianshi.json
+++ b/dianshi.json
@@ -1,5 +1,5 @@
{
- "spider": "./jar/spider.jar;md5;dc774ec05ddb2be146f3fee027d5b51a",
+ "spider": "./jar/spider.jar;md5;20b61ea1e8772fd2f8e013c5c2fab86f",
"lives": [
{"name": "live","boot": false,"type": 0,"url": "./tv.txt","playerType": 2,"ua": "okhttp/3.8.1","timeout": 20,"epg": "https://epg.cdn.loc.cc/?ch={name}&date={date}","logo": "https://logo.wyfc.qzz.io/{name}.png"}
],
@@ -17,6 +17,7 @@
{"key": "qiletv","name": "奇乐影视","type": 3,"api": "csp_XBPQ","searchable": 1,"quickSearch": 1,"filterable": 1,"changeable": 1,"ext": "./json/奇乐.json","header": {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key": "热播影视","name": "热播 • APP","type": 3,"api": "csp_AppRJ","searchable": 1,"quickSearch": 1,"filterable": 0,"ext": {"url": "http://v.rbotv.cn"}},
{"key": "三秋影视","name": "三秋 • APP","type": 3,"api": "csp_App3Q"},
+ {"key": "马猴影视","name": "马猴 • APP","type": 3,"api": "csp_AppMH"},
{"key": "韩圈","name": "韩圈 • APP","type": 3,"api": "csp_Hxq","ext": "http://www.小不点.com/api/2026/fishhxq.php"},
{"key": "无极99","name": "无极 • APP","type": 3,"api": "csp_App99","ext": {"host": "http://111.170.140.70:19987/app/bn","LoginPath": "/app/log","version": "","appkey": "c403317c2a2946659b0bcbabdd5de43a","versionName": "1.0.1","name": "无极影视","package": "com.wujiys.app","buildNumber": "2001","buildSignature": "A0859BDC35AD3E81D4051E2A4678C77C62773EDB3423DDF1DEFF94CB331D398E"}},
{"key": "橙子99","name": "橙子 • APP","type": 3,"api": "csp_App99","ext": {"host": "http://113.31.180.218:19987/app/bn","LoginPath": "/app/log","appkey": "6e8f3764a0644f3686ab5cdd57bcdf48","versionName": "4.3.1","name": "小橙子","package": "com.xchengzix.app","buildNumber": "2001","buildSignature": "99DA983CF878CEC016616175464A91A94775E20A489F07F76FCDEA1A0F2B6B45"}},
@@ -28,8 +29,6 @@
{"key": "华谊","name": "华谊 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "华谊","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp9Ek4wIlQAtwFnuBRlsFiow2tr+4UOciGeNKbY7nL74etUqUb6fvpOSOHhFEfaWlfwUpOB17x3JEL3No19nfjCeVYrYPjlJcgoqUWH/tfIfFAQWvtxBIBlKazkhw8d3ChysWmeWRikKqkBsVRY4oqNPuj4sjm6Zult0U4I4prRQIDAQAB","dataKey": "NDBYSZR1DMRRZ05NSUCWEJNIYWLBPT0=","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.muyue.tool","host": "","site": "https://vip.123pan.cn/1851089669/oss/az5.txt","version": "1.0.0.4","decrypt": "1"}},
{"key": "苹果","name": "苹果 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "小苹果","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCduNEnfxGaLuQRk5ABzXHhPV43zi00sCHjLo8BYc+Wi6xXm2b4v0i28Sq4WlNCKhseft9fz8kO/qLr6/022o1RcuOU7e4GFL3U9WnNODwRBYSYWd+K8nqpI/tAUDmZEBGRWqjrc7x6aMl3A+xpnWkLbPCLsuhbuuUE3tv09oeOpwIDAQAB","dataKey": "S0VNDTJHOFHHCNMRAW5IV2TOS2PBPQ==","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.juechufsh.android.xpg1","host": "","site": "https://xpgandroid03-1372779881.cos.ap-beijing.myqcloud.com/app_xpg_android.txt","version": "1.0.0.3","decrypt": "0"}},
{"key": "薯条","name": "薯条 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "薯条影视","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCduNEnfxGaLuQRk5ABzXHhPV43zi00sCHjLo8BYc+Wi6xXm2b4v0i28Sq4WlNCKhseft9fz8kO/qLr6/022o1RcuOU7e4GFL3U9WnNODwRBYSYWd+K8nqpI/tAUDmZEBGRWqjrc7x6aMl3A+xpnWkLbPCLsuhbuuUE3tv09oeOpwIDAQAB","dataKey": "A1VACZJWDKRZY1P3MFV0DDRAZ3F3PT0=","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.st.standroid","host": "","site": "https://dyttandroid-1372779881.cos.ap-guangzhou.myqcloud.com/app_dyttandroid.txt","version": "5.0.0.1","decrypt": "0"}},
- {"key": "久久","name": "久久 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "http://99cms.jj99.icu:8199/api.php/app/"},
- {"key": "闪影","name": "闪影 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "http://38.47.213.61:41271/mogai_api.php/v1.vod"},
{"key": "飞飞","name": "飞飞 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "https://qpcj.69mini.com/api.php/app/"},
{"key": "金牌","name": "金牌 • APP","type": 3,"api": "csp_AppQi","searchable": 1,"changeable": 1,"ext": {"url": "https://qj4.catbb.xyz","dataKey": "eecbio48dsq13kkk","dataIv": "eecbio48dsq13kkk","search": "searchList4","init": "initV122","ua": "okhttp/3.10.0"}},
{"key": "蓝鹰","name": "蓝鹰 • APP","type": 3,"api": "csp_AppQi","searchable": 1,"changeable": 1,"ext": {"site": "https://xinlanyinghb.oss-cn-wuhan-lr.aliyuncs.com/xly.txt","dataKey": "ca94b06ca359d80e","dataIv": "ca94b06ca359d80e","init": "initV120","ua": "okhttp/3.10.0"}},
@@ -57,19 +56,20 @@
{"key": "笒鬼短剧","name": "笒鬼 • 短剧","type": 3,"api": "csp_CenguiDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
{"key": "好看短剧","name": "好看 • 短剧","type": 3,"api": "csp_HaokanDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
{"key": "围观短剧","name": "围观 • 短剧","type": 3,"api": "csp_WeiguanDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
- {"key": "星芽短剧","name": "星芽 • 短剧","type": 3,"api": "./py/星芽短剧.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
+ {"key": "星芽短剧","name": "星芽 • 短剧","type": 3,"api": "./py/XYDJ.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
+ {"key": "山楂影视","name": "山楂 • 影视","type": 3,"api": "./py/SZYS.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1},
+ {"key": "瓜子影视","name": "瓜子 • 影视","type": 3,"api": "./py/GZYS.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1},
{"key": "独播影视","name": "独播 • 影视","type": 3,"api": "csp_Duboku"},
{"key": "厂长影视","name": "厂长 • 影视","type": 3,"api": "csp_Czsapp","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "https://cz01.vip"},
{"key": "金牌影视","name": "金牌 • 影视","type": 3,"api": "csp_Jpys","ext": "https://y2s52n7.com,https://m.hkybqufgh.com,https://m.sizhengxt.com,https://m.9zhoukj.com,https://m.sizhengxt.com,https://m.jiabaide.cn"},
{"key": "修罗影视","name": "修罗 • 影视","api": "csp_Xlys","type": 3,"ext": "https://www.xlysw.ggff.net"},
- {"key": "瓜子影视","name": "瓜子 • 影视","type": 3,"api": "csp_Gz360"},
- {"key": "云播影视","name": "云播 • 影视","type": 3,"api": "csp_Tvyb"},
{"key": "骚火影视","name": "骚火 • 影视","type": 3,"api": "csp_SaoHuo","ext": "https://shdy5.us"},
{"key": "农民影视","name": "农民 • 影视","type": 3,"api": "csp_Wwys","ext": "https://vip.wwgz.cn:5200"},
{"key": "爱看机器人","name": "爱看 • 影视","type": 3,"api": "csp_Ikanbot","searchable": 1,"quickSearch": 1,"filterable": 1},
- {"key": "小镇影视","name": "小镇 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/XZYS.json"},
- {"key": "面包影视","name": "面包 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/MBYS.json"},
- {"key": "永乐影视","name": "永乐 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/YLYS.json"},
+ {"key": "枫叶影视","name": "枫叶 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "手机","简介": "text cor3\">[*],&&","图片": "data-src=\"&&\"[替换:amp;>>空]","主页url": "https://www.cd-zj.com/label/qq.html","副标题": "ft2\">&&","线路数组": "&&","播放数组": "anthology-list-play size\">&&","分类url": "https://www.cd-zj.com/cupfox-list/{cateId}-{area}--{class}-----{catePg}---{year}.html","分类": "电影$1#电视剧$2#综艺$3#动漫$4#短剧$5"}},
+ {"key": "小镇影视","name": "小镇 • 影视","type": 3,"api": "csp_XBPQ","ext": {"线路数组": "","线路标题": ">&&<","分类url": "http://dyxz.tv/list/{cateId}_{catePg}.html","分类": "电影$1#电视剧$2#动漫$3#综艺$4"}},
+ {"key": "面包影视","name": "面包 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "User-Agent$MOBILE_UA","编码": "UTF-8","分类url": "https://v.aiwule.com/vodshow/{cateId}-{area}-{by}-{class}-{lang}-{letter}---{catePg}---{year}.html","分类": "电影$20#电视剧$21#动漫$23#综艺$22#短剧$47","简介": "简介:&&"}},
+ {"key": "永乐影视","name": "永乐 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "User-Agent$MOBILE_UA","编码": "UTF-8","分类": "电影$1#电视剧$2#综艺$3#动漫$4","类型": "动作片$6#喜剧片$7#爱情片$8#科幻片$9#奇幻片$10#恐怖片$11#剧情片$12#战争片$20#动画片$26#悬疑片$22#冒险片$23#犯罪片$24#惊悚片$45#歌舞片$46#灾难片$47#网络片$48||国产剧$13#港台剧$14#日剧$15#韩剧$33#欧美剧$16#泰剧$34#新马剧$35#其他剧$25||内地综艺$27#港台综艺$28#日本综艺$29#韩国综艺$36#欧美综艺$30#新马泰综艺$37#其他综艺$38||国产动漫$31#日本动漫$32#韩国动漫$39#港台动漫$40#新马泰动漫$41#欧美动漫$42#其他动漫$43","分类url": "https://www.ylys.tv/vodshow/{cateId}-{area}-{by}-{class}-{lang}-{letter}---{catePg}---{year}.html"}},
{"key": "剧圈影视","name": "剧圈 • 影视","type": 3,"api": "csp_XYQHiker","ext": "./json/JQYS.json"},
{"key": "1905","name": "1905 • 影视","type": 3,"api": "csp_Web1905","searchable": 1,"quickSearch": 0,"filterable": 0},
{"key": "哆啦新番社","name": "哆啦 • 新番社","type": 3,"api": "csp_XBPQ","style": {"type": "list"},"ext": "./json/DLXFS.json"},
diff --git a/jar/spider.jar b/jar/spider.jar
index 38a911a9..83b2ba7a 100644
Binary files a/jar/spider.jar and b/jar/spider.jar differ
diff --git a/jsm.json b/jsm.json
index dcb68ce1..324a1b28 100644
--- a/jsm.json
+++ b/jsm.json
@@ -1,5 +1,5 @@
{
- "spider": "./jar/spider.jar;md5;dc774ec05ddb2be146f3fee027d5b51a",
+ "spider": "./jar/spider.jar;md5;20b61ea1e8772fd2f8e013c5c2fab86f",
"lives": [
{"name": "migu","type": 0,"url": "https://develop202.github.io/migu_video/interface.txt","playerType": 1,"ua": "okhttp/3.8.1","timeout": 20,"epg": "https://epg.cdn.loc.cc/?ch={name}&date={date}","logo": "https://logo.wyfc.qzz.io/{name}.png"},
{"name": "live","type": 0,"url": "https://epg.pw/test_channels.m3u","playerType": 1,"ua": "okhttp/3.8.1","timeout": 20,"epg": "https://epg.cdn.loc.cc/?ch={name}&date={date}","logo": "https://logo.wyfc.qzz.io/{name}.png"},
@@ -18,6 +18,7 @@
{"key": "qiletv","name": "奇乐影视","type": 3,"api": "csp_XBPQ","searchable": 1,"quickSearch": 1,"filterable": 1,"changeable": 1,"ext": "./json/奇乐.json","header": {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0"}},
{"key": "热播影视","name": "热播 • APP","type": 3,"api": "csp_AppRJ","searchable": 1,"quickSearch": 1,"filterable": 0,"ext": {"url": "http://v.rbotv.cn"}},
{"key": "三秋影视","name": "三秋 • APP","type": 3,"api": "csp_App3Q"},
+ {"key": "马猴影视","name": "马猴 • APP","type": 3,"api": "csp_AppMH"},
{"key": "韩圈","name": "韩圈 • APP","type": 3,"api": "csp_Hxq","ext": "http://www.小不点.com/api/2026/fishhxq.php"},
{"key": "无极99","name": "无极 • APP","type": 3,"api": "csp_App99","ext": {"host": "http://111.170.140.70:19987/app/bn","LoginPath": "/app/log","version": "","appkey": "c403317c2a2946659b0bcbabdd5de43a","versionName": "1.0.1","name": "无极影视","package": "com.wujiys.app","buildNumber": "2001","buildSignature": "A0859BDC35AD3E81D4051E2A4678C77C62773EDB3423DDF1DEFF94CB331D398E"}},
{"key": "橙子99","name": "橙子 • APP","type": 3,"api": "csp_App99","ext": {"host": "http://113.31.180.218:19987/app/bn","LoginPath": "/app/log","appkey": "6e8f3764a0644f3686ab5cdd57bcdf48","versionName": "4.3.1","name": "小橙子","package": "com.xchengzix.app","buildNumber": "2001","buildSignature": "99DA983CF878CEC016616175464A91A94775E20A489F07F76FCDEA1A0F2B6B45"}},
@@ -29,8 +30,6 @@
{"key": "华谊","name": "华谊 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "华谊","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp9Ek4wIlQAtwFnuBRlsFiow2tr+4UOciGeNKbY7nL74etUqUb6fvpOSOHhFEfaWlfwUpOB17x3JEL3No19nfjCeVYrYPjlJcgoqUWH/tfIfFAQWvtxBIBlKazkhw8d3ChysWmeWRikKqkBsVRY4oqNPuj4sjm6Zult0U4I4prRQIDAQAB","dataKey": "NDBYSZR1DMRRZ05NSUCWEJNIYWLBPT0=","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.muyue.tool","host": "","site": "https://vip.123pan.cn/1851089669/oss/az5.txt","version": "1.0.0.4","decrypt": "1"}},
{"key": "苹果","name": "苹果 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "小苹果","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCduNEnfxGaLuQRk5ABzXHhPV43zi00sCHjLo8BYc+Wi6xXm2b4v0i28Sq4WlNCKhseft9fz8kO/qLr6/022o1RcuOU7e4GFL3U9WnNODwRBYSYWd+K8nqpI/tAUDmZEBGRWqjrc7x6aMl3A+xpnWkLbPCLsuhbuuUE3tv09oeOpwIDAQAB","dataKey": "S0VNDTJHOFHHCNMRAW5IV2TOS2PBPQ==","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.juechufsh.android.xpg1","host": "","site": "https://xpgandroid03-1372779881.cos.ap-beijing.myqcloud.com/app_xpg_android.txt","version": "1.0.0.3","decrypt": "0"}},
{"key": "薯条","name": "薯条 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "薯条影视","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCduNEnfxGaLuQRk5ABzXHhPV43zi00sCHjLo8BYc+Wi6xXm2b4v0i28Sq4WlNCKhseft9fz8kO/qLr6/022o1RcuOU7e4GFL3U9WnNODwRBYSYWd+K8nqpI/tAUDmZEBGRWqjrc7x6aMl3A+xpnWkLbPCLsuhbuuUE3tv09oeOpwIDAQAB","dataKey": "A1VACZJWDKRZY1P3MFV0DDRAZ3F3PT0=","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.st.standroid","host": "","site": "https://dyttandroid-1372779881.cos.ap-guangzhou.myqcloud.com/app_dyttandroid.txt","version": "5.0.0.1","decrypt": "0"}},
- {"key": "久久","name": "久久 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "http://99cms.jj99.icu:8199/api.php/app/"},
- {"key": "闪影","name": "闪影 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "http://38.47.213.61:41271/mogai_api.php/v1.vod"},
{"key": "飞飞","name": "飞飞 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "https://qpcj.69mini.com/api.php/app/"},
{"key": "金牌","name": "金牌 • APP","type": 3,"api": "csp_AppQi","searchable": 1,"changeable": 1,"ext": {"url": "https://qj4.catbb.xyz","dataKey": "eecbio48dsq13kkk","dataIv": "eecbio48dsq13kkk","search": "searchList4","init": "initV122","ua": "okhttp/3.10.0"}},
{"key": "蓝鹰","name": "蓝鹰 • APP","type": 3,"api": "csp_AppQi","searchable": 1,"changeable": 1,"ext": {"site": "https://xinlanyinghb.oss-cn-wuhan-lr.aliyuncs.com/xly.txt","dataKey": "ca94b06ca359d80e","dataIv": "ca94b06ca359d80e","init": "initV120","ua": "okhttp/3.10.0"}},
@@ -58,19 +57,20 @@
{"key": "笒鬼短剧","name": "笒鬼 • 短剧","type": 3,"api": "csp_CenguiDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
{"key": "好看短剧","name": "好看 • 短剧","type": 3,"api": "csp_HaokanDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
{"key": "围观短剧","name": "围观 • 短剧","type": 3,"api": "csp_WeiguanDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
- {"key": "星芽短剧","name": "星芽 • 短剧","type": 3,"api": "./py/星芽短剧.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
+ {"key": "星芽短剧","name": "星芽 • 短剧","type": 3,"api": "./py/XYDJ.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
+ {"key": "山楂影视","name": "山楂 • 影视","type": 3,"api": "./py/SZYS.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1},
+ {"key": "瓜子影视","name": "瓜子 • 影视","type": 3,"api": "./py/GZYS.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1},
{"key": "独播影视","name": "独播 • 影视","type": 3,"api": "csp_Duboku"},
{"key": "厂长影视","name": "厂长 • 影视","type": 3,"api": "csp_Czsapp","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "https://cz01.vip"},
{"key": "金牌影视","name": "金牌 • 影视","type": 3,"api": "csp_Jpys","ext": "https://y2s52n7.com,https://m.hkybqufgh.com,https://m.sizhengxt.com,https://m.9zhoukj.com,https://m.sizhengxt.com,https://m.jiabaide.cn"},
{"key": "修罗影视","name": "修罗 • 影视","api": "csp_Xlys","type": 3,"ext": "https://www.xlysw.ggff.net"},
- {"key": "瓜子影视","name": "瓜子 • 影视","type": 3,"api": "csp_Gz360"},
- {"key": "云播影视","name": "云播 • 影视","type": 3,"api": "csp_Tvyb"},
{"key": "骚火影视","name": "骚火 • 影视","type": 3,"api": "csp_SaoHuo","ext": "https://shdy5.us"},
{"key": "农民影视","name": "农民 • 影视","type": 3,"api": "csp_Wwys","ext": "https://vip.wwgz.cn:5200"},
{"key": "爱看机器人","name": "爱看 • 影视","type": 3,"api": "csp_Ikanbot","searchable": 1,"quickSearch": 1,"filterable": 1},
- {"key": "小镇影视","name": "小镇 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/XZYS.json"},
- {"key": "面包影视","name": "面包 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/MBYS.json"},
- {"key": "永乐影视","name": "永乐 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/YLYS.json"},
+ {"key": "枫叶影视","name": "枫叶 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "手机","简介": "text cor3\">[*],&&","图片": "data-src=\"&&\"[替换:amp;>>空]","主页url": "https://www.cd-zj.com/label/qq.html","副标题": "ft2\">&&","线路数组": "&&","播放数组": "anthology-list-play size\">&&","分类url": "https://www.cd-zj.com/cupfox-list/{cateId}-{area}--{class}-----{catePg}---{year}.html","分类": "电影$1#电视剧$2#综艺$3#动漫$4#短剧$5"}},
+ {"key": "小镇影视","name": "小镇 • 影视","type": 3,"api": "csp_XBPQ","ext": {"线路数组": "","线路标题": ">&&<","分类url": "http://dyxz.tv/list/{cateId}_{catePg}.html","分类": "电影$1#电视剧$2#动漫$3#综艺$4"}},
+ {"key": "面包影视","name": "面包 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "User-Agent$MOBILE_UA","编码": "UTF-8","分类url": "https://v.aiwule.com/vodshow/{cateId}-{area}-{by}-{class}-{lang}-{letter}---{catePg}---{year}.html","分类": "电影$20#电视剧$21#动漫$23#综艺$22#短剧$47","简介": "简介:&&"}},
+ {"key": "永乐影视","name": "永乐 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "User-Agent$MOBILE_UA","编码": "UTF-8","分类": "电影$1#电视剧$2#综艺$3#动漫$4","类型": "动作片$6#喜剧片$7#爱情片$8#科幻片$9#奇幻片$10#恐怖片$11#剧情片$12#战争片$20#动画片$26#悬疑片$22#冒险片$23#犯罪片$24#惊悚片$45#歌舞片$46#灾难片$47#网络片$48||国产剧$13#港台剧$14#日剧$15#韩剧$33#欧美剧$16#泰剧$34#新马剧$35#其他剧$25||内地综艺$27#港台综艺$28#日本综艺$29#韩国综艺$36#欧美综艺$30#新马泰综艺$37#其他综艺$38||国产动漫$31#日本动漫$32#韩国动漫$39#港台动漫$40#新马泰动漫$41#欧美动漫$42#其他动漫$43","分类url": "https://www.ylys.tv/vodshow/{cateId}-{area}-{by}-{class}-{lang}-{letter}---{catePg}---{year}.html"}},
{"key": "剧圈影视","name": "剧圈 • 影视","type": 3,"api": "csp_XYQHiker","ext": "./json/JQYS.json"},
{"key": "1905","name": "1905 • 影视","type": 3,"api": "csp_Web1905","searchable": 1,"quickSearch": 0,"filterable": 0},
{"key": "哆啦新番社","name": "哆啦 • 新番社","type": 3,"api": "csp_XBPQ","style": {"type": "list"},"ext": "./json/DLXFS.json"},
diff --git a/py/GZYS.py b/py/GZYS.py
new file mode 100644
index 00000000..220e1fcb
--- /dev/null
+++ b/py/GZYS.py
@@ -0,0 +1,496 @@
+# coding = utf-8
+#!/usr/bin/python
+import re
+import sys
+import json
+import time
+import base64
+import hashlib
+import random
+import string
+import urllib.parse
+from Crypto.Cipher import AES
+from Crypto.Util.Padding import pad, unpad
+from Crypto.PublicKey import RSA
+from Crypto.Cipher import PKCS1_v1_5
+from base.spider import Spider
+
+sys.path.append('..')
+
+class Spider(Spider):
+ def __init__(self):
+ self.name = "瓜子"
+ self.hosts = [
+ 'https://apinew.uozvr.com',
+ 'https://api.w32z7vtd.com',
+ 'https://api.6a7nnf7.com',
+ 'https://api.umygrx3.com',
+ 'https://api.rmedphk.com'
+ ]
+ self.host_index = 0
+ self.host = self.hosts[self.host_index]
+
+ # AES 固定密钥(与Java版一致)
+ self.AES_KEY = 'OITxa5OqAYjhswxx'
+ self.AES_IV = 'rCMNwZASNBKZ8mXV'
+
+ # RSA 公钥/私钥
+ self.RSA_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUM5+/y8sPsWkd1/RQS64X259EUwxFXFE5HlA65MqrxnPs0JqoSRojSDy5QhwvROlaD6TwRQHKMY2OAZ6SnQeUJsChTEFIR9qUkwrs3/MVUMxjsv6JS6Oe/juclyJGTgVmDhB55EafXsD0SQYVj/QXXsxR6ewR5E2kL52yAAD4yQIDAQAB"
+ self.RSA_PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGAe6hKrWLi1zQmjTT1
+ozbE4QdFeJGNxubxld6GrFGximxfMsMB6BpJhpcTouAqywAFppiKetUBBbXwYsYU
+1wNr648XVmPmCMCy4rY8vdliFnbMUj086DU6Z+/oXBdWU3/b1G0DN3E9wULRSwcK
+ZT3wj/cCI1vsCm3gj2R5SqkA9Y0CAwEAAQKBgAJH+4CxV0/zBVcLiBCHvSANm0l7
+HetybTh/j2p0Y1sTXro4ALwAaCTUeqdBjWiLSo9lNwDHFyq8zX90+gNxa7c5EqcW
+V9FmlVXr8VhfBzcZo1nXeNdXFT7tQ2yah/odtdcx+vRMSGJd1t/5k5bDd9wAvYdI
+DblMAg+wiKKZ5KcdAkEA1cCakEN4NexkF5tHPRrR6XOY/XHfkqXxEhMqmNbB9U34
+saTJnLWIHC8IXys6Qmzz30TtzCjuOqKRRy+FMM4TdwJBAJQZFPjsGC+RqcG5UvVM
+iMPhnwe/bXEehShK86yJK/g/UiKrO87h3aEu5gcJqBygTq3BBBoH2md3pr/W+hUM
+WBsCQQChfhTIrdDinKi6lRxrdBnn0Ohjg2cwuqK5zzU9p/N+S9x7Ck8wUI53DKm8
+jUJE8WAG7WLj/oCOWEh+ic6NIwTdAkEAj0X8nhx6AXsgCYRql1klbqtVmL8+95KZ
+K7PnLWG/IfjQUy3pPGoSaZ7fdquG8bq8oyf5+dzjE/oTXcByS+6XRQJAP/5ciy1b
+L3NhUhsaOVy55MHXnPjdcTX0FaLi+ybXZIfIQ2P4rb19mVq1feMbCXhz+L1rG8oa
+t5lYKfpe8k83ZA==
+-----END RSA PRIVATE KEY-----"""
+
+ self.DEVICE_OLD_KEY = "aLFBMWpxBrIDAD1Si/KVvm41"
+
+ # 设备信息(随机生成)
+ self.deviceId = str(864150060000000 + random.randint(0, 9999))
+ self.deviceKey = ''.join(random.choices('0123456789ABCDEF', k=40)) # 20字节hex大写
+ self.token = ""
+ self.token_id = ""
+ self.registered = False
+
+ self.header = {
+ 'User-Agent': 'Lavf/57.83.100',
+ 'code': 'GZ0369',
+ 'deviceId': self.deviceId,
+ 'lang': 'zh_cn',
+ 'Cache-Control': 'no-cache',
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Version': '2604028',
+ 'PackageName': 'com.ae06aebdbb.y286327f5a.ofe849883320260517',
+ 'Ver': '3.0.3.2',
+ 'api-ver': '3.0.3.2',
+ 'Referer': self.host
+ }
+
+ self.cache = {}
+ self.cache_timeout = 300
+
+ # 初始化token
+ self.init_token()
+
+ def getName(self):
+ return self.name
+
+ def init(self, extend=''):
+ pass
+
+ # ---------- 设备注册与认证 ----------
+ def init_token(self):
+ """初始化token:注册设备 -> 刷新"""
+ print("===== 初始化设备认证 =====")
+ try:
+ if not self.registered:
+ self.sign_up()
+ # 刷新获取最终token
+ self.refresh_token()
+ except Exception as e:
+ print(f"初始化token失败: {e}")
+ # 兜底使用原有硬编码(几乎没用)
+ self.token = '024212ef0975c5306a1434e113a46463.bc77313e11a248558a6ca244ca980944ec3421fa480c50e0229ad91f1cb15aea582603202cd71796885c9e5163e500f1b72f737059aff1ddb8beea47c5a331d6760540345b7f88b2302a0e6e09589f9dcf3ff9175d8c905f990203f5fc04748008ea7a366571cbf5b09509a873dcfba3cf1d5590385f5f7ef6e01d1850974aa220eb5178c89e61c24411af9b9a19435e.06fde789ece48d9b33c5dc857e04e9b5838f08264d928b87237d3476c4484b46'
+
+ def sign_up(self):
+ """注册设备"""
+ print("注册新设备...")
+ params = {
+ "new_key": self.deviceKey,
+ "old_key": self.DEVICE_OLD_KEY,
+ "phone_type": 1,
+ "code": ""
+ }
+ result = self._auth_request('/App/Authentication/Device/signUp', params)
+ self._apply_auth(result)
+ self.registered = True
+
+ def sign_in(self):
+ """登录设备"""
+ print("设备登录...")
+ params = {
+ "new_key": self.deviceKey,
+ "old_key": self.DEVICE_OLD_KEY
+ }
+ result = self._auth_request('/App/Authentication/Device/signIn', params)
+ self._apply_auth(result)
+
+ def _apply_auth(self, result):
+ """从认证响应中提取token"""
+ new_token = result.get('token', '')
+ if not new_token:
+ raise Exception("认证失败,无token返回: {}".format(result))
+ self.token = new_token
+ new_token_id = result.get('app_user_id', '')
+ if new_token_id:
+ self.token_id = new_token_id
+ print(f"获取token成功, token前缀: {self.token[:30]}...")
+
+ def refresh_token(self):
+ """刷新token"""
+ print("刷新token...")
+ result = self._auth_request('/App/Authentication/Authenticator/refresh', {})
+ self._apply_auth(result)
+
+ def _auth_request(self, path, params):
+ """认证类请求(不需要ensure_token)"""
+ return self._send_encrypted_request(params, path, is_auth=True)
+
+ # ---------- 业务请求核心(修复加密与签名) ----------
+ def ensure_token(self):
+ """确保token有效,如未就绪则重新获取"""
+ if not self.token or not self.token_id:
+ if self.registered:
+ self.sign_in()
+ else:
+ self.sign_up()
+ self.refresh_token()
+
+ def _send_encrypted_request(self, data, path, is_auth=False):
+ """
+ 发送加密请求,返回解密后的字典
+ :param data: 业务参数字典
+ :param path: 请求路径
+ :param is_auth: 是否为认证类请求(signUp/signIn/refresh),此时不使用ensure_token
+ """
+ try:
+ if not is_auth:
+ self.ensure_token()
+
+ # 1. 将参数转为JSON并AES加密
+ json_params = json.dumps(data)
+ encrypted = self.aes_encrypt(json_params, self.AES_KEY, self.AES_IV)
+ request_key = encrypted.upper() # Java中是bytesToHex(encrypted).toUpperCase()
+
+ # 2. 生成keys (RSA加密 iv/key JSON)
+ key_json = json.dumps({"iv": self.AES_IV, "key": self.AES_KEY})
+ keys = self.rsa_encrypt(key_json, self.RSA_PUBLIC_KEY)
+
+ # 3. 生成签名
+ t = str(int(time.time()))
+ sign_str = f"token_id=,token={self.token},phone_type=1,request_key={request_key},app_id=1,time={t},keys={keys}*&zvdvdvddbfikkkumtmdwqppp?|4Y!s!2br"
+ signature = self.get_md5(sign_str) # 已改为大写
+
+ # 4. 构建请求体
+ body = {
+ 'token': self.token,
+ 'token_id': '',
+ 'phone_type': '1',
+ 'time': t,
+ 'phone_model': 'xiaomi-25031', # 与Java版保持一致
+ 'keys': keys,
+ 'request_key': request_key,
+ 'signature': signature,
+ 'app_id': '1',
+ 'ad_version': '1'
+ }
+
+ # 5. 发送请求
+ url = f"{self.host}{path}"
+ response = self.post(url, headers=self.header, data=body, timeout=10)
+
+ if response.status_code != 200:
+ raise Exception(f"HTTP {response.status_code}")
+
+ resp_json = response.json()
+ # 检查业务code(若不为200可能token过期)
+ if 'code' in resp_json and resp_json['code'] != 200:
+ print(f"业务错误码: {resp_json['code']}, 信息: {resp_json}")
+ # 如果不是认证请求,尝试重新获取token后重试一次(这里简单处理,外层get_data已有重试)
+ raise Exception("业务错误")
+
+ data_section = resp_json.get('data')
+ if not data_section:
+ raise Exception("响应缺少data字段")
+
+ encrypted_response = data_section.get('response_key', '')
+ encrypted_keys = data_section.get('keys', '')
+
+ # 6. 解密响应
+ decrypted_keys_json = self.rsa_decrypt(encrypted_keys, self.RSA_PRIVATE_KEY)
+ key_info = json.loads(decrypted_keys_json)
+ resp_key = key_info['key']
+ resp_iv = key_info['iv']
+ decrypted_data = self.aes_decrypt(encrypted_response, resp_key, resp_iv)
+ return json.loads(decrypted_data)
+
+ except Exception as e:
+ print(f"请求失败 [{path}]: {e}")
+ return None
+
+ def get_data(self, data, path, use_cache=True):
+ """带重试和域名轮询的数据获取(保持原框架)"""
+ try:
+ cache_key = f"{path}_{hash(str(data))}" if use_cache else None
+ if use_cache and cache_key in self.cache:
+ cached_data, timestamp = self.cache[cache_key]
+ if time.time() - timestamp < self.cache_timeout:
+ return cached_data
+
+ for attempt in range(3):
+ tried = 0
+ while tried < len(self.hosts):
+ self.host = self.hosts[self.host_index]
+ self.header['Referer'] = self.host
+ result = self._send_encrypted_request(data, path)
+ if result is not None:
+ print(f"请求成功: {path}, 域名: {self.host}")
+ if use_cache and cache_key:
+ self.cache[cache_key] = (result, time.time())
+ return result
+
+ # 切换到下一个域名
+ self.host_index = (self.host_index + 1) % len(self.hosts)
+ tried += 1
+
+ # 所有域名失败,尝试重新认证并重试
+ if attempt < 2:
+ print("所有域名失败,尝试重新认证...")
+ try:
+ self.ensure_token()
+ except:
+ pass
+ self.host_index = 0
+ else:
+ break
+ return None
+ except Exception as e:
+ print(f"get_data异常: {e}")
+ return None
+
+ # ---------- 加解密工具 ----------
+ def aes_encrypt(self, text, key, iv):
+ try:
+ key_bytes = key.encode('utf-8')
+ iv_bytes = iv.encode('utf-8')
+ cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
+ encrypted = cipher.encrypt(pad(text.encode('utf-8'), AES.block_size))
+ return encrypted.hex().upper()
+ except Exception as e:
+ print(f"AES加密失败: {e}")
+ return ""
+
+ def aes_decrypt(self, text, key, iv):
+ try:
+ key_bytes = key.encode('utf-8')
+ iv_bytes = iv.encode('utf-8')
+ cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
+ encrypted_bytes = bytes.fromhex(text)
+ decrypted = unpad(cipher.decrypt(encrypted_bytes), AES.block_size)
+ return decrypted.decode('utf-8')
+ except Exception as e:
+ print(f"AES解密失败: {e}")
+ return ""
+
+ def rsa_encrypt(self, text, public_key_str):
+ """RSA公钥加密(PKCS1v1.5)"""
+ try:
+ key = RSA.import_key("-----BEGIN PUBLIC KEY-----\n" + public_key_str + "\n-----END PUBLIC KEY-----")
+ cipher = PKCS1_v1_5.new(key)
+ encrypted = cipher.encrypt(text.encode('utf-8'))
+ return base64.b64encode(encrypted).decode('utf-8')
+ except Exception as e:
+ print(f"RSA加密失败: {e}")
+ return ""
+
+ def rsa_decrypt(self, encrypted_data, private_key_str):
+ """RSA私钥解密"""
+ try:
+ encrypted_bytes = base64.b64decode(encrypted_data)
+ rsa_key = RSA.import_key(private_key_str)
+ cipher = PKCS1_v1_5.new(rsa_key)
+ decrypted = cipher.decrypt(encrypted_bytes, None)
+ return decrypted.decode('utf-8') if decrypted else ""
+ except Exception as e:
+ print(f"RSA解密失败: {e}")
+ return ""
+
+ def get_md5(self, text):
+ return hashlib.md5(text.encode()).hexdigest().upper() # 与Java一致大写
+
+ # ---------- 业务方法(不变) ----------
+ def homeContent(self, filter):
+ result = {}
+ classes = [
+ {"type_name": "电影", "type_id": "1"},
+ {"type_name": "电视剧", "type_id": "2"},
+ {"type_name": "动漫", "type_id": "4"},
+ {"type_name": "综艺", "type_id": "3"},
+ {"type_name": "短剧", "type_id": "64"}
+ ]
+ result['class'] = classes
+ filters = {}
+ for cate in classes:
+ tid = cate['type_id']
+ filters[tid] = [
+ {"key": "area", "name": "地区", "value": [
+ {"n": "全部", "v": "0"}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"},
+ {"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"},
+ {"n": "日本", "v": "日本"}, {"n": "英国", "v": "英国"}, {"n": "法国", "v": "法国"},
+ {"n": "泰国", "v": "泰国"}, {"n": "印度", "v": "印度"}, {"n": "其他", "v": "其他"}
+ ]},
+ {"key": "year", "name": "年份", "value": [
+ {"n": "全部", "v": "0"}, {"n": "2025", "v": "2025"}, {"n": "2024", "v": "2024"},
+ {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"}, {"n": "2021", "v": "2021"},
+ {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"}, {"n": "2018", "v": "2018"},
+ {"n": "2017", "v": "2017"}, {"n": "2016", "v": "2016"}, {"n": "2015", "v": "2015"},
+ {"n": "2014", "v": "2014"}, {"n": "2013", "v": "2013"}, {"n": "2012", "v": "2012"},
+ {"n": "2011", "v": "2011"}, {"n": "2010", "v": "2010"}, {"n": "2009", "v": "2009"},
+ {"n": "2008", "v": "2008"}, {"n": "2007", "v": "2007"}, {"n": "2006", "v": "2006"},
+ {"n": "2005", "v": "2005"}, {"n": "更早", "v": "2004"}
+ ]},
+ {"key": "sort", "name": "排序", "value": [
+ {"n": "最新", "v": "d_id"}, {"n": "最热", "v": "d_hits"}, {"n": "推荐", "v": "d_score"}
+ ]}
+ ]
+ result['filters'] = filters
+ return result
+
+ def homeVideoContent(self):
+ return {'list': []}
+
+ def categoryContent(self, tid, pg, filter, extend):
+ videos = []
+ try:
+ body = {
+ "area": extend.get('area', '0'),
+ "year": extend.get('year', '0'),
+ "pageSize": "30",
+ "sort": extend.get('sort', 'd_id'),
+ "page": str(pg),
+ "tid": tid
+ }
+ cache_key = f"category_{tid}_{pg}_{hash(str(body))}"
+ data = self.get_cached_data(cache_key, body, '/App/IndexList/indexList')
+ if data and 'list' in data:
+ for item in data['list']:
+ vod_continu = item.get('vod_continu', 0)
+ remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}集'
+ video = {
+ "vod_id": f"{item.get('vod_id', '')}/{vod_continu}",
+ "vod_name": item.get('vod_name', ''),
+ "vod_pic": item.get('vod_pic', ''),
+ "vod_remarks": remarks
+ }
+ videos.append(video)
+ except Exception as e:
+ print(f"获取分类内容失败: {e}")
+ return {'list': videos, 'page': int(pg), 'pagecount': 9999, 'limit': 30, 'total': 999999}
+
+ def detailContent(self, ids):
+ try:
+ vod_id = ids[0].split('/')[0]
+ t = str(int(time.time()))
+ body1 = {"token_id": self.token_id, "vod_id": vod_id, "mobile_time": t, "token": self.token}
+ qdata = self.get_data(body1, '/App/IndexPlay/playInfo')
+ body2 = {"vurl_cloud_id": "2", "vod_d_id": vod_id}
+ jdata = self.get_data(body2, '/App/Resource/Vurl/show')
+ if not qdata or 'vodInfo' not in qdata:
+ return {'list': []}
+ vod = qdata['vodInfo']
+ video_detail = {
+ "vod_id": vod_id,
+ "vod_name": vod.get('vod_name', ''),
+ "vod_pic": vod.get('vod_pic', ''),
+ "vod_year": vod.get('vod_year', ''),
+ "vod_area": vod.get('vod_area', ''),
+ "vod_actor": vod.get('vod_actor', ''),
+ "vod_director": vod.get('vod_director', ''),
+ "vod_content": vod.get('vod_use_content', '').strip(),
+ "vod_play_from": "瓜子影视"
+ }
+ play_list = []
+ if jdata and 'list' in jdata:
+ for index, item in enumerate(jdata['list']):
+ if 'play' in item:
+ n, p = [], []
+ for key, value in item['play'].items():
+ if 'param' in value and value['param']:
+ n.append(key)
+ p.append(value['param'])
+ if p:
+ play_name = str(index + 1) if len(jdata['list']) != 1 else vod.get('vod_name', '')
+ play_url = f"{p[-1]}||{'@'.join(n)}"
+ play_list.append(f"{play_name}${play_url}")
+ video_detail["vod_play_url"] = "#".join(play_list)
+ return {'list': [video_detail]}
+ except Exception as e:
+ print(f"获取详情失败: {e}")
+ return {'list': []}
+
+ def searchContent(self, key, quick, pg=1):
+ videos = []
+ try:
+ body = {"keywords": key, "order_val": "1", "page": str(pg)}
+ data = self.get_data(body, '/App/Index/findMoreVod', use_cache=False)
+ if data and 'list' in data:
+ for item in data['list']:
+ vod_continu = item.get('vod_continu', 0)
+ remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}集'
+ videos.append({
+ "vod_id": f"{item.get('vod_id', '')}/{vod_continu}",
+ "vod_name": item.get('vod_name', ''),
+ "vod_pic": item.get('vod_pic', ''),
+ "vod_remarks": remarks
+ })
+ except Exception as e:
+ print(f"搜索失败: {e}")
+ return {'list': videos, 'page': int(pg), 'pagecount': 9999, 'limit': 30, 'total': 999999}
+
+ def playerContent(self, flag, id, vipFlags):
+ try:
+ parts = id.split('||')
+ if len(parts) < 2:
+ return {"parse": 0, "playUrl": "", "url": ""}
+ param_str = parts[0]
+ resolutions = parts[1].split('@') if len(parts) > 1 else []
+ params = {}
+ for pair in param_str.split('&'):
+ if '=' in pair:
+ key, value = pair.split('=', 1)
+ params[key] = value
+ if resolutions:
+ resolutions.sort(key=lambda x: int(x) if x.isdigit() else 0, reverse=True)
+ params['resolution'] = resolutions[0]
+ data = self.get_data(params, '/App/Resource/VurlDetail/showOne', use_cache=False)
+ if data and 'url' in data:
+ return {"parse": 0, "playUrl": "", "url": data['url'],
+ "header": json.dumps({"User-Agent": "Lavf/57.83.100", "Referer": "http://WJiZxLXA2.com/"}), 'danmaku': 'http://127.0.0.1:9978/proxy?do=diydanmu'}
+ return {"parse": 0, "playUrl": "", "url": ""}
+ except Exception as e:
+ print(f"播放解析失败: {e}")
+ return {"parse": 0, "playUrl": "", "url": ""}
+
+ def isVideoFormat(self, url):
+ video_formats = ['.m3u8', '.mp4', '.avi', '.mkv', '.flv', '.ts']
+ return any(url.lower().endswith(fmt) for fmt in video_formats)
+
+ def manualVideoCheck(self):
+ pass
+
+ def localProxy(self, params):
+ return None
+
+ def get_cached_data(self, cache_key, data, path):
+ current_time = time.time()
+ if cache_key in self.cache:
+ cached_data, timestamp = self.cache[cache_key]
+ if current_time - timestamp < self.cache_timeout:
+ return cached_data
+ result = self.get_data(data, path)
+ if result:
+ self.cache[cache_key] = (result, current_time)
+ return result
+
+if __name__ == '__main__':
+ pass
\ No newline at end of file
diff --git a/py/SZYS.py b/py/SZYS.py
new file mode 100644
index 00000000..2e76bc1a
--- /dev/null
+++ b/py/SZYS.py
@@ -0,0 +1,330 @@
+# -*- coding: utf-8 -*-
+# by @嗷呜
+import json
+import random
+import sys
+from base64 import b64encode, b64decode
+from concurrent.futures import ThreadPoolExecutor
+
+# 引入 RSA 加解密所需模块
+from Crypto.PublicKey import RSA
+from Crypto.Cipher import PKCS1_v1_5
+
+sys.path.append('..')
+from base.spider import Spider
+
+class Spider(Spider):
+
+ def init(self, extend=""):
+ did = self.getdid()
+ self.headers.update({'deviceId': did})
+ token = self.gettk()
+ self.headers.update({'token': token})
+
+ def getName(self):
+ pass
+
+ def isVideoFormat(self, url):
+ pass
+
+ def manualVideoCheck(self):
+ pass
+
+ def destroy(self):
+ pass
+
+ # 1. 修改为主机域名
+ host = 'http://qkys.qukanwh.com'
+
+ # 2. 同步原脚本的配置请求头
+ headers = {
+ 'HOST': 'qkys.qukanwh.com',
+ 'User-Agent': 'okhttp/4.12.0',
+ 'client': 'app',
+ 'deviceType': 'Android',
+ 'Referer': ''
+ }
+
+ # 3. 导入原脚本中的 RSA 密钥对与配置
+ publicKey_str = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoYt0BP77U+DM08BiI/QbSRIfxijXo85BTPqIM1Ow8BNwhLETzRIZ+dEwdWDbydG/PspgBAfRpGaYVdJYtvaC2JnoO8+Ik6qMWojfEJxSFLa0Pb0A892tun4gsxoEMjcreZ+YGyaBxAfqX0BSMfdrOgIYaZQjYrw9TRLlUT31QoQIDAQAB\n-----END PUBLIC KEY-----"
+ privateKey_str = "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCquQQ5r6+yJI8CDFkXRp8vUsdD45ov8EP12ooLs56ca2DQXaSNGS9910bAPVA9chkp0mKIvKqjAsHz5Tl9EeNPblarGEeJUIxpxZtiSqNTpvtiD/TjhpzuHYic7RAfQ/h7p/ypE8ymU42pYjsB5t26Mv6XgkLV+jzrSf73HlCuS0iMyLmt6zz3Mw9izM13EpB8iFLtfbbYymycKTx4RAmPQLwhNGex/AlUIYxXP4R2yyaa4W6mEtc6aME2QuzJFxPgP3HJ9NBx/LWVn4skxWjZ7zg+VRQRHnjyVaSLu3Z5gN5ITWCyE32qaHJa6WBahZj5jWhRyAG1bQ+xKJa8lBL5AgMBAAECggEAUwv9SjJ0PSwbhNuM2w23kcWquROWhYtTA91zGY4esehqB/IFgb2mpIh8Gje5OKqwIu/8jpd4SiOlRYdUF8sD0DfUYRZGdj2AkFNX6tBz8tVfo6wvbB6naA1lzzBij1L5JO3qsjS3cJFkb+kg2yP66AC2Z+0tpfk8eRhdtshAZwfcd1DEGt1uAvYL1eaUK9HRvpt9lPeGcHERDl2hBd4uyaF0K1O+zF9y59nYbTySWPxRZq3sFEE85xRMlstD7YZi7W2gKvMFRD4/FKmrZ3m7aKJRITtyKOyyPcYmepNv3Qv7kk59Pg38n2WWQ0Ra/bCH3E48YNCnQvZMpitkTfJhoQKBgQDbnROOYTP8OTJ6f/qhoGjxeO3x1VOaOp8l0x7b0SCfoqNGS0Cyiqj72BmJtPMPqSTjn6MmNzqbg1KOdhXyzNozs+i5ccW1M56j96mr5I/Z0FpE3oyIHNfDDBlf9M8YQqEF9oYxniYYft9oapO7cRQkHER6qpvnHTavwlv4m78CXwKBgQDHAjs2YlpKDdI1lcbZJCc7TwtH+Pd2bUki8YXafWNcPhITQHbOZjr310eK1QJC6GJncjkOqbX7yv3ivvTO35FZTQhuA1xEG1P00FG8bE0tHYPIwQHi9y0eA5cieMdo8E6XYria1mw/3fqSQEsfZyJlR32JQIoGAipM8iO1X2nZpwKBgDkMFIhnt5lNQk+P7wsNIDWZtDWdtJnboHuy29E+Abt2A/O+mI/IdRz2hau/1WO8DFkUnszOi+rZshhPlGP90rCbi1igtTrcrdjp/KkqNjPea5R4OwkgdOu1uOG0NheXNzzVTQaWjk7Opjn5dWa7eP/oV+GFb/oZHJuLYVizHGsBAoGADA7rjZEKDYCm4w5PPSr+oY5ZjaPdQrS+gLqHtMRyN82fBMGcMUdqfUfzEstzVqCEDeaS5HuOBlK3bXzKkppjUTjksN3NQmcxgBz7RuJ9DqXCLXDcb2cwuafYCYOt+YLOEEgwDVm+t2P44dG5e46hO+fICH/7nP+WlpD5buz4GfMCgYB57r3g/6hi9WUDnfc7ZAzWMqR0EhJVYKYy+KFEtdIPzhkkIHq5RASe88E9kzoGoZFdb3tIjvGZWcHerirrqWkMsuQtP/Qi0zjieid5tAPj+r4kbiCVTw0E0jnmPBzGInQi7lpeTTKnG1fbyS5lBS+WmHfIuzpECgCkxhaT+LJJkg==\n-----END PRIVATE KEY-----"
+
+ # RSA 公钥加密实现
+ def rsa_encrypt(self, text):
+ try:
+ key = RSA.import_key(self.publicKey_str)
+ cipher = PKCS1_v1_5.new(key)
+ cipher_text = cipher.encrypt(text.encode('utf-8'))
+ return b64encode(cipher_text).decode('utf-8')
+ except Exception as e:
+ print(f"RSA加密失败: {e}")
+ return ""
+
+ # RSA 私钥解密实现
+ def rsa_decrypt(self, text):
+ try:
+ key = RSA.import_key(self.privateKey_str)
+ cipher = PKCS1_v1_5.new(key)
+ raw_bytes = b64decode(text.encode('utf-8'))
+
+ decrypted = b""
+ offset = 0
+ while offset < len(raw_bytes):
+ chunk = raw_bytes[offset:offset + 256]
+ decrypted += cipher.decrypt(chunk, None)
+ offset += 256
+ return decrypted.decode('utf-8')
+ except Exception as e:
+ print(f"RSA解密失败: {e}")
+ return ""
+
+ def homeContent(self, filter):
+ data = self.post(f"{self.host}/api/v1/app/screen/screenType", headers=self.headers).json()
+ result = {}
+ cate = {
+ "类型": "type",
+ "地区": "area",
+ "年份": "year"
+ }
+ sort = {
+ 'key': 'sort',
+ 'name': '排序',
+ 'value': [{'n': '最新', 'v': 'NEWEST'}, {'n': '热门', 'v': 'HOT'}, {'n': '收藏', 'v': 'COLLECT'}]
+ }
+ classes = []
+ filters = {}
+ for k in data.get('data', []):
+ classes.append({
+ 'type_name': k['name'],
+ 'type_id': str(k['id'])
+ })
+ filters[str(k['id'])] = []
+ for v in k.get('children', []):
+ if v['name'] in cate:
+ filters[str(k['id'])].append({
+ 'name': v['name'],
+ 'key': cate[v['name']],
+ 'value': [{'n': i['name'], 'v': i['name']} for i in v.get('children', [])]
+ })
+ filters[str(k['id'])].append(sort)
+ result['class'] = classes
+ result['filters'] = filters
+ return result
+
+ def homeVideoContent(self):
+ jdata = {
+ "condition": {
+ "sreecnTypeEnum": "NEWEST"
+ },
+ "pageNum": 1,
+ "pageSize": 40
+ }
+ data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
+ return {'list': self.getlist(data.get('data', {}).get('records', []))}
+
+ def categoryContent(self, tid, pg, filter, extend):
+ # 保持最纯粹的条件字段,移除任何空字符串占位
+ condition = {
+ 'sreecnTypeEnum': 'NEWEST',
+ 'typeId': int(tid) if str(tid).isdigit() else tid
+ }
+
+ if extend:
+ if 'sort' in extend:
+ condition['sreecnTypeEnum'] = extend.pop('sort')
+ condition.update(extend)
+
+ jdata = {
+ 'condition': condition,
+ 'pageNum': int(pg),
+ 'pageSize': 40,
+ }
+
+ try:
+ data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
+ result = {}
+ if data and data.get('data') and 'records' in data['data']:
+ result['list'] = self.getlist(data['data']['records'])
+ else:
+ result['list'] = []
+ result['page'] = pg
+ result['pagecount'] = 9999
+ result['limit'] = 40
+ result['total'] = 999999
+ return result
+ except Exception as e:
+ print(f"分类获取错误: {e}")
+ return {'list': [], 'page': pg}
+
+ def detailContent(self, ids):
+ ids = ids[0].split('@@')
+ jdata = {"id": int(ids[0]), "typeId": ids[-1]}
+ v = self.post(f"{self.host}/api/v1/app/play/movieDesc", headers=self.headers, json=jdata).json()
+ v = v.get('data', {})
+ vod = {
+ 'type_name': v.get('typeId', ''),
+ 'vod_year': v.get('year', ''),
+ 'vod_area': v.get('area', ''),
+ 'vod_actor': v.get('star', ''),
+ 'vod_director': v.get('director', ''),
+ 'vod_content': v.get('introduce', ''),
+ 'vod_play_from': '',
+ 'vod_play_url': ''
+ }
+
+ play_params = {
+ "id": int(ids[0]),
+ "source": 0,
+ "typeId": ids[-1]
+ }
+ encrypt_payload = {"key": self.rsa_encrypt(json.dumps(play_params))}
+
+ c_res = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
+ decrypted_play_str = self.rsa_decrypt(c_res.get('data', ''))
+ if not decrypted_play_str:
+ return {'list': [vod]}
+
+ decrypted_play_data = json.loads(decrypted_play_str)
+ l = decrypted_play_data.get('moviePlayerList', [])
+ if not l:
+ return {'list': [vod]}
+
+ n = {str(i['id']): i['moviePlayerName'] for i in l}
+
+ m = play_params.copy()
+ m.update({'playerId': l[0]['id']})
+
+ first_source_payload = {"key": self.rsa_encrypt(json.dumps(m))}
+ first_res = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=first_source_payload).json()
+
+ decrypted_first_str = self.rsa_decrypt(first_res.get('data', ''))
+ if decrypted_first_str:
+ decrypted_first_episode = json.loads(decrypted_first_str)
+ pd = self.getv(m, decrypted_first_episode.get('episodeList', []))
+ else:
+ pd = {}
+
+ if len(l) > 1:
+ with ThreadPoolExecutor(max_workers=len(l)-1) as executor:
+ future_to_player = {executor.submit(self.getd, play_params, player): player for player in l[1:]}
+ for future in future_to_player:
+ try:
+ o, p = future.result()
+ if p:
+ pd.update(self.getv(o, p))
+ except Exception as e:
+ print(f"多线路请求失败: {e}")
+ w, e = [], []
+ for i, x in pd.items():
+ if x:
+ w.append(n.get(i, '未知线路'))
+ e.append(x)
+ vod['vod_play_from'] = '$$$'.join(w)
+ vod['vod_play_url'] = '$$$'.join(e)
+ return {'list': [vod]}
+
+ def searchContent(self, key, quick, pg="1"):
+ jdata = {
+ "condition": {
+ "value": str(key)
+ },
+ "pageNum": int(pg),
+ "pageSize": 40
+ }
+ try:
+ data = self.post(f"{self.host}/api/v1/app/search/searchMovie", headers=self.headers, json=jdata).json()
+ return {'list': self.getlist(data.get('data', {}).get('records', [])), 'page': pg}
+ except Exception as e:
+ print(f"搜索请求失败: {e}")
+ return {'list': [], 'page': pg}
+
+ def playerContent(self, flag, id, vipFlags):
+ raw_id_str = self.d64(id)
+ if not raw_id_str:
+ return {'parse': 0, 'url': ''}
+ jdata = json.loads(raw_id_str)
+ encrypt_payload = {"key": self.rsa_encrypt(json.dumps(jdata))}
+ data = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
+
+ try:
+ decrypted_url_data = json.loads(self.rsa_decrypt(data.get('data', '')))
+ playerUrl = decrypted_url_data.get('url', '')
+ if not playerUrl:
+ return {'parse': 0, 'url': ''}
+
+ params = {'playerUrl': playerUrl, 'playerId': jdata['playerId']}
+ pd = self.fetch(f"{self.host}/api/v1/app/play/analysisMovieUrl", headers=self.headers, params=params).json()
+ url, p = pd.get('data', ''), 0
+ except Exception as e:
+ print(f"解析流媒体直链失败: {e}")
+ url, p = "", 0
+ return {'parse': p, 'url': url, 'header': {'User-Agent': 'okhttp/4.12.0'}, 'danmaku': 'http://127.0.0.1:9978/proxy?do=diydanmu'}
+
+ def localProxy(self, param):
+ pass
+
+ def liveContent(self, url):
+ pass
+
+ def gettk(self):
+ self.headers.update({'deviceId': self.getdid()})
+ try:
+ data = self.fetch(f"{self.host}/api/v1/app/user/visitorInfo", headers=self.headers).json()
+ return data.get('data', {}).get('token', '')
+ except:
+ return ""
+
+ def getdid(self):
+ did = self.getCache('ldid')
+ if not did:
+ hex_chars = '0123456789abcdef'
+ did = ''.join(random.choice(hex_chars) for _ in range(16))
+ self.setCache('ldid', did)
+ return did
+
+ def getd(self, jdata, player):
+ x = jdata.copy()
+ x.update({'playerId': player['id']})
+ encrypt_payload = {"key": self.rsa_encrypt(json.dumps(x))}
+ response = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
+ decrypted_str = self.rsa_decrypt(response.get('data', ''))
+ if decrypted_str:
+ decrypted_episode = json.loads(decrypted_str)
+ return x, decrypted_episode.get('episodeList', [])
+ return x, []
+
+ def getv(self, d, c):
+ f = {str(d['playerId']): ''}
+ g = []
+ for i in c:
+ j = d.copy()
+ j.update({'episodeId': i['id']})
+ g.append(f"{i['episode']}${self.e64(json.dumps(j))}")
+ f[str(d['playerId'])] = '#'.join(g)
+ return f
+
+ def getlist(self, data):
+ videos = []
+ for i in data:
+ if not i.get('id'):
+ continue
+ videos.append({
+ 'vod_id': f"{i['id']}@@{i.get('typeId', '')}",
+ 'vod_name': i.get('name', ''),
+ 'vod_pic': i.get('cover', ''),
+ 'vod_year': i.get('year', ''),
+ 'vod_remarks': i.get('totalEpisode', '')
+ })
+ return videos
+
+ def e64(self, text):
+ try:
+ return b64encode(text.encode('utf-8')).decode('utf-8')
+ except:
+ return ""
+
+ def d64(self, encoded_text):
+ try:
+ return b64decode(encoded_text.encode('utf-8')).decode('utf-8')
+ except:
+ return ""
diff --git a/py/XYDJ.py b/py/XYDJ.py
new file mode 100644
index 00000000..8a7d540f
--- /dev/null
+++ b/py/XYDJ.py
@@ -0,0 +1,343 @@
+# coding = utf-8
+# !/usr/bin/python
+
+"""
+"""
+
+from Crypto.Util.Padding import unpad
+from Crypto.Util.Padding import pad
+from urllib.parse import unquote
+from Crypto.Cipher import ARC4
+from urllib.parse import quote
+from base.spider import Spider
+from Crypto.Cipher import AES
+from bs4 import BeautifulSoup
+from base64 import b64decode
+import urllib.request
+import urllib.parse
+import binascii
+import requests
+import base64
+import json
+import time
+import sys
+import re
+import os
+
+sys.path.append('..')
+
+xurl = "https://app.whjzjx.cn"
+
+headers = {
+ 'User-Agent': 'Linux; Android 12; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36'
+ }
+
+headerf = {
+ "platform": "1",
+ "user_agent": "Mozilla/5.0 (Linux; Android 9; V1938T Build/PQ3A.190705.08211809; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
+ "content-type": "application/json; charset=utf-8"
+ }
+
+times = int(time.time() * 1000)
+
+data = {
+ "device": "2a50580e69d38388c94c93605241fb306",
+ "package_name": "com.jz.xydj",
+ "android_id": "ec1280db12795506",
+ "install_first_open": True,
+ "first_install_time": 1752505243345,
+ "last_update_time": 1752505243345,
+ "report_link_url": "",
+ "authorization": "",
+ "timestamp": times
+ }
+
+plain_text = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
+
+key = "B@ecf920Od8A4df7"
+key_bytes = key.encode('utf-8')
+plain_bytes = plain_text.encode('utf-8')
+cipher = AES.new(key_bytes, AES.MODE_ECB)
+padded_data = pad(plain_bytes, AES.block_size)
+ciphertext = cipher.encrypt(padded_data)
+encrypted = base64.b64encode(ciphertext).decode('utf-8')
+
+response = requests.post("https://u.shytkjgs.com/user/v3/account/login", headers=headerf, data=encrypted)
+response_data = response.json()
+Authorization = response_data['data']['token']
+
+headerx = {
+ 'authorization': Authorization,
+ 'platform': '1',
+ 'version_name': '3.8.3.1'
+ }
+
+class Spider(Spider):
+ global xurl
+ global headerx
+ global headers
+
+ def getName(self):
+ return "首页"
+
+ def init(self, extend):
+ pass
+
+ def isVideoFormat(self, url):
+ pass
+
+ def manualVideoCheck(self):
+ pass
+
+ def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
+ if pl == 3:
+ plx = []
+ while True:
+ start_index = text.find(start_str)
+ if start_index == -1:
+ break
+ end_index = text.find(end_str, start_index + len(start_str))
+ if end_index == -1:
+ break
+ middle_text = text[start_index + len(start_str):end_index]
+ plx.append(middle_text)
+ text = text.replace(start_str + middle_text + end_str, '')
+ if len(plx) > 0:
+ purl = ''
+ for i in range(len(plx)):
+ matches = re.findall(start_index1, plx[i])
+ output = ""
+ for match in matches:
+ match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
+ if match3:
+ number = match3.group(1)
+ else:
+ number = 0
+ if 'http' not in match[0]:
+ output += f"#{match[1]}${number}{xurl}{match[0]}"
+ else:
+ output += f"#{match[1]}${number}{match[0]}"
+ output = output[1:]
+ purl = purl + output + "$$$"
+ purl = purl[:-3]
+ return purl
+ else:
+ return ""
+ else:
+ start_index = text.find(start_str)
+ if start_index == -1:
+ return ""
+ end_index = text.find(end_str, start_index + len(start_str))
+ if end_index == -1:
+ return ""
+
+ if pl == 0:
+ middle_text = text[start_index + len(start_str):end_index]
+ return middle_text.replace("\\", "")
+
+ if pl == 1:
+ middle_text = text[start_index + len(start_str):end_index]
+ matches = re.findall(start_index1, middle_text)
+ if matches:
+ jg = ' '.join(matches)
+ return jg
+
+ if pl == 2:
+ middle_text = text[start_index + len(start_str):end_index]
+ matches = re.findall(start_index1, middle_text)
+ if matches:
+ new_list = [f'{item}' for item in matches]
+ jg = '$$$'.join(new_list)
+ return jg
+
+ def homeContent(self, filter):
+ result = {}
+ result = {"class": [{"type_id": "1", "type_name": "剧场"},
+ {"type_id": "3", "type_name": "新剧"},
+ {"type_id": "2", "type_name": "热播"},
+ {"type_id": "7", "type_name": "星选"},
+ {"type_id": "5", "type_name": "阳光"}],
+ }
+
+ return result
+
+ def homeVideoContent(self):
+ videos = []
+
+ url= f'{xurl}/v1/theater/home_page?theater_class_id=1&class2_id=4&page_num=1&page_size=24'
+ detail = requests.get(url=url, headers=headerx)
+ detail.encoding = "utf-8"
+ if detail.status_code == 200:
+ data = detail.json()
+
+ for vod in data['data']['list']:
+
+ name = vod['theater']['title']
+
+ id = vod['theater']['id']
+
+ pic = vod['theater']['cover_url']
+
+ remark = vod['theater']['play_amount_str']
+
+ video = {
+ "vod_id": id,
+ "vod_name": name,
+ "vod_pic": pic,
+ "vod_remarks": remark
+ }
+ videos.append(video)
+
+ result = {'list': videos}
+ return result
+
+ def categoryContent(self, cid, pg, filter, ext):
+ result = {}
+ videos = []
+
+ url = f'{xurl}/v1/theater/home_page?theater_class_id={cid}&page_num={pg}&page_size=24'
+ detail = requests.get(url=url,headers=headerx)
+ detail.encoding = "utf-8"
+ if detail.status_code == 200:
+ data = detail.json()
+
+ for vod in data['data']['list']:
+
+ name = vod['theater']['title']
+
+ id = vod['theater']['id']
+
+ pic = vod['theater']['cover_url']
+
+ remark = vod['theater']['theme']
+
+ video = {
+ "vod_id": id,
+ "vod_name": name,
+ "vod_pic": pic,
+ "vod_remarks": remark
+ }
+ videos.append(video)
+
+ result = {'list': videos}
+ result['page'] = pg
+ result['pagecount'] = 9999
+ result['limit'] = 90
+ result['total'] = 999999
+ return result
+
+ def detailContent(self, ids):
+ did = ids[0]
+ result = {}
+ videos = []
+ xianlu = ''
+ bofang = ''
+
+ url = f'{xurl}/v2/theater_parent/detail?theater_parent_id={did}'
+ detail = requests.get(url=url, headers=headerx)
+ detail.encoding = "utf-8"
+ if detail.status_code == 200:
+ data = detail.json()
+
+ url = 'https://fs-im-kefu.7moor-fs1.com/ly/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1732707176882/jiduo.txt'
+ response = requests.get(url)
+ response.encoding = 'utf-8'
+ code = response.text
+ name = self.extract_middle_text(code, "s1='", "'", 0)
+ Jumps = self.extract_middle_text(code, "s2='", "'", 0)
+
+ content = '剧情:' + data['data']['introduction']
+
+ area = data['data']['desc_tags'][0]
+
+ remarks = data['data']['filing']
+
+ # 修复剧集只有一集的问题 - 检查theaters数据是否存在且不为空
+ if 'theaters' in data['data'] and data['data']['theaters']:
+ for sou in data['data']['theaters']:
+ id = sou['son_video_url']
+ name = sou['num']
+ bofang = bofang + str(name) + '$' + id + '#'
+
+ bofang = bofang[:-1] if bofang.endswith('#') else bofang
+ xianlu = '星芽'
+ else:
+ # 如果没有theaters数据,检查是否有单个视频URL
+ if 'video_url' in data['data'] and data['data']['video_url']:
+ bofang = '1$' + data['data']['video_url']
+ xianlu = '星芽'
+ else:
+ bofang = Jumps
+ xianlu = '1'
+
+ videos.append({
+ "vod_id": did,
+ "vod_content": content,
+ "vod_remarks": remarks,
+ "vod_area": area,
+ "vod_play_from": xianlu,
+ "vod_play_url": bofang
+ })
+
+ result['list'] = videos
+ return result
+
+ def playerContent(self, flag, id, vipFlags):
+
+ result = {}
+ result["parse"] = 0
+ result["playUrl"] = ''
+ result["url"] = id
+ result["header"] = headers
+ return result
+
+ def searchContentPage(self, key, quick, page):
+ result = {}
+ videos = []
+
+ payload = {
+ "text": key
+ }
+
+ url = f"{xurl}/v3/search"
+ detail = requests.post(url=url, headers=headerx, json=payload)
+ if detail.status_code == 200:
+ detail.encoding = "utf-8"
+ data = detail.json()
+
+ for vod in data['data']['theater']['search_data']:
+
+ name = vod['title']
+
+ id = vod['id']
+
+ pic = vod['cover_url']
+
+ remark = vod['score_str']
+
+ video = {
+ "vod_id": id,
+ "vod_name": name,
+ "vod_pic": pic,
+ "vod_remarks": remark
+ }
+ videos.append(video)
+
+ result['list'] = videos
+ result['page'] = page
+ result['pagecount'] = 9999
+ result['limit'] = 90
+ result['total'] = 999999
+ return result
+
+ def searchContent(self, key, quick, pg="1"):
+ return self.searchContentPage(key, quick, '1')
+
+ def localProxy(self, params):
+ if params['type'] == "m3u8":
+ return self.proxyM3u8(params)
+ elif params['type'] == "media":
+ return self.proxyMedia(params)
+ elif params['type'] == "ts":
+ return self.proxyTs(params)
+ return None
\ No newline at end of file
diff --git a/xiaosa/api.json b/xiaosa/api.json
index fb52815f..06368944 100644
--- a/xiaosa/api.json
+++ b/xiaosa/api.json
@@ -1,5 +1,5 @@
{
- "spider": "./spider.jar;md5;dc774ec05ddb2be146f3fee027d5b51a",
+ "spider": "./spider.jar;md5;20b61ea1e8772fd2f8e013c5c2fab86f",
"wallpaper": "http://127.0.0.1:9978/proxy?do=wallpaper",
"danmaku": "http://127.0.0.1:9978/proxy?do=danmaku&name={name}&episode={episode}",
"sites": [
@@ -9,6 +9,7 @@
{"key": "预告","name": "新片 • 预告","type": 3,"api": "csp_YGP","searchable": 0},
{"key": "热播影视","name": "热播 • APP","type": 3,"api": "csp_AppRJ","searchable": 1,"quickSearch": 1,"filterable": 0,"ext": {"url": "http://v.rbotv.cn"}},
{"key": "三秋影视","name": "三秋 • APP","type": 3,"api": "csp_App3Q"},
+ {"key": "马猴影视","name": "马猴 • APP","type": 3,"api": "csp_AppMH"},
{"key": "韩圈","name": "韩圈 • APP","type": 3,"api": "csp_Hxq","ext": "http://www.小不点.com/api/2026/fishhxq.php"},
{"key": "无极99","name": "无极 • APP","type": 3,"api": "csp_App99","ext": {"host": "http://111.170.140.70:19987/app/bn","LoginPath": "/app/log","version": "","appkey": "c403317c2a2946659b0bcbabdd5de43a","versionName": "1.0.1","name": "无极影视","package": "com.wujiys.app","buildNumber": "2001","buildSignature": "A0859BDC35AD3E81D4051E2A4678C77C62773EDB3423DDF1DEFF94CB331D398E"}},
{"key": "橙子99","name": "橙子 • APP","type": 3,"api": "csp_App99","ext": {"host": "http://113.31.180.218:19987/app/bn","LoginPath": "/app/log","appkey": "6e8f3764a0644f3686ab5cdd57bcdf48","versionName": "4.3.1","name": "小橙子","package": "com.xchengzix.app","buildNumber": "2001","buildSignature": "99DA983CF878CEC016616175464A91A94775E20A489F07F76FCDEA1A0F2B6B45"}},
@@ -20,8 +21,6 @@
{"key": "华谊","name": "华谊 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "华谊","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCp9Ek4wIlQAtwFnuBRlsFiow2tr+4UOciGeNKbY7nL74etUqUb6fvpOSOHhFEfaWlfwUpOB17x3JEL3No19nfjCeVYrYPjlJcgoqUWH/tfIfFAQWvtxBIBlKazkhw8d3ChysWmeWRikKqkBsVRY4oqNPuj4sjm6Zult0U4I4prRQIDAQAB","dataKey": "NDBYSZR1DMRRZ05NSUCWEJNIYWLBPT0=","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.muyue.tool","host": "","site": "https://vip.123pan.cn/1851089669/oss/az5.txt","version": "1.0.0.4","decrypt": "1"}},
{"key": "苹果","name": "苹果 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "小苹果","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCduNEnfxGaLuQRk5ABzXHhPV43zi00sCHjLo8BYc+Wi6xXm2b4v0i28Sq4WlNCKhseft9fz8kO/qLr6/022o1RcuOU7e4GFL3U9WnNODwRBYSYWd+K8nqpI/tAUDmZEBGRWqjrc7x6aMl3A+xpnWkLbPCLsuhbuuUE3tv09oeOpwIDAQAB","dataKey": "S0VNDTJHOFHHCNMRAW5IV2TOS2PBPQ==","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.juechufsh.android.xpg1","host": "","site": "https://xpgandroid03-1372779881.cos.ap-beijing.myqcloud.com/app_xpg_android.txt","version": "1.0.0.3","decrypt": "0"}},
{"key": "薯条","name": "薯条 • APP","type": 3,"api": "csp_AppDrama","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": {"appName": "薯条影视","publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCduNEnfxGaLuQRk5ABzXHhPV43zi00sCHjLo8BYc+Wi6xXm2b4v0i28Sq4WlNCKhseft9fz8kO/qLr6/022o1RcuOU7e4GFL3U9WnNODwRBYSYWd+K8nqpI/tAUDmZEBGRWqjrc7x6aMl3A+xpnWkLbPCLsuhbuuUE3tv09oeOpwIDAQAB","dataKey": "A1VACZJWDKRZY1P3MFV0DDRAZ3F3PT0=","dataIv": "OC1A06E197EF10CF3F6058CA7A803B5E","pkg": "com.st.standroid","host": "","site": "https://dyttandroid-1372779881.cos.ap-guangzhou.myqcloud.com/app_dyttandroid.txt","version": "5.0.0.1","decrypt": "0"}},
- {"key": "久久","name": "久久 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "http://99cms.jj99.icu:8199/api.php/app/"},
- {"key": "闪影","name": "闪影 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "http://38.47.213.61:41271/mogai_api.php/v1.vod"},
{"key": "飞飞","name": "飞飞 • APP","type": 3,"api": "csp_AppYsV2","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "https://qpcj.69mini.com/api.php/app/"},
{"key": "金牌","name": "金牌 • APP","type": 3,"api": "csp_AppQi","searchable": 1,"changeable": 1,"ext": {"url": "https://qj4.catbb.xyz","dataKey": "eecbio48dsq13kkk","dataIv": "eecbio48dsq13kkk","search": "searchList4","init": "initV122","ua": "okhttp/3.10.0"}},
{"key": "蓝鹰","name": "蓝鹰 • APP","type": 3,"api": "csp_AppQi","searchable": 1,"changeable": 1,"ext": {"site": "https://xinlanyinghb.oss-cn-wuhan-lr.aliyuncs.com/xly.txt","dataKey": "ca94b06ca359d80e","dataIv": "ca94b06ca359d80e","init": "initV120","ua": "okhttp/3.10.0"}},
@@ -50,19 +49,20 @@
{"key": "笒鬼短剧","name": "笒鬼 • 短剧","type": 3,"api": "csp_CenguiDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
{"key": "好看短剧","name": "好看 • 短剧","type": 3,"api": "csp_HaokanDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
{"key": "围观短剧","name": "围观 • 短剧","type": 3,"api": "csp_WeiguanDJ","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
- {"key": "星芽短剧","name": "星芽 • 短剧","type": 3,"api": "./py/星芽短剧.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
+ {"key": "星芽短剧","name": "星芽 • 短剧","type": 3,"api": "./py/XYDJ.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1,"genre": "shortdrama"},
+ {"key": "山楂影视","name": "山楂 • 影视","type": 3,"api": "./py/SZYS.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1},
+ {"key": "瓜子影视","name": "瓜子 • 影视","type": 3,"api": "./py/GZYS.py","searchable": 1,"changeable": 1,"quickSearch": 1,"filterable": 1},
{"key": "独播影视","name": "独播 • 影视","type": 3,"api": "csp_Duboku"},
{"key": "厂长影视","name": "厂长 • 影视","type": 3,"api": "csp_Czsapp","searchable": 1,"quickSearch": 1,"filterable": 1,"ext": "https://cz01.vip"},
{"key": "金牌影视","name": "金牌 • 影视","type": 3,"api": "csp_Jpys","ext": "https://y2s52n7.com,https://m.hkybqufgh.com,https://m.sizhengxt.com,https://m.9zhoukj.com,https://m.sizhengxt.com,https://m.jiabaide.cn"},
{"key": "修罗影视","name": "修罗 • 影视","api": "csp_Xlys","type": 3,"ext": "https://www.xlysw.ggff.net"},
- {"key": "瓜子影视","name": "瓜子 • 影视","type": 3,"api": "csp_Gz360"},
- {"key": "云播影视","name": "云播 • 影视","type": 3,"api": "csp_Tvyb"},
{"key": "骚火影视","name": "骚火 • 影视","type": 3,"api": "csp_SaoHuo","ext": "https://shdy5.us"},
{"key": "农民影视","name": "农民 • 影视","type": 3,"api": "csp_Wwys","ext": "https://vip.wwgz.cn:5200"},
{"key": "爱看机器人","name": "爱看 • 影视","type": 3,"api": "csp_Ikanbot","searchable": 1,"quickSearch": 1,"filterable": 1},
- {"key": "小镇影视","name": "小镇 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/XZYS.json"},
- {"key": "面包影视","name": "面包 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/MBYS.json"},
- {"key": "永乐影视","name": "永乐 • 影视","type": 3,"api": "csp_XBPQ","ext": "./json/YLYS.json"},
+ {"key": "枫叶影视","name": "枫叶 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "手机","简介": "text cor3\">[*],&&","图片": "data-src=\"&&\"[替换:amp;>>空]","主页url": "https://www.cd-zj.com/label/qq.html","副标题": "ft2\">&&","线路数组": "&&","播放数组": "anthology-list-play size\">&&","分类url": "https://www.cd-zj.com/cupfox-list/{cateId}-{area}--{class}-----{catePg}---{year}.html","分类": "电影$1#电视剧$2#综艺$3#动漫$4#短剧$5"}},
+ {"key": "小镇影视","name": "小镇 • 影视","type": 3,"api": "csp_XBPQ","ext": {"线路数组": "","线路标题": ">&&<","分类url": "http://dyxz.tv/list/{cateId}_{catePg}.html","分类": "电影$1#电视剧$2#动漫$3#综艺$4"}},
+ {"key": "面包影视","name": "面包 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "User-Agent$MOBILE_UA","编码": "UTF-8","分类url": "https://v.aiwule.com/vodshow/{cateId}-{area}-{by}-{class}-{lang}-{letter}---{catePg}---{year}.html","分类": "电影$20#电视剧$21#动漫$23#综艺$22#短剧$47","简介": "简介:&&"}},
+ {"key": "永乐影视","name": "永乐 • 影视","type": 3,"api": "csp_XBPQ","ext": {"请求头": "User-Agent$MOBILE_UA","编码": "UTF-8","分类": "电影$1#电视剧$2#综艺$3#动漫$4","类型": "动作片$6#喜剧片$7#爱情片$8#科幻片$9#奇幻片$10#恐怖片$11#剧情片$12#战争片$20#动画片$26#悬疑片$22#冒险片$23#犯罪片$24#惊悚片$45#歌舞片$46#灾难片$47#网络片$48||国产剧$13#港台剧$14#日剧$15#韩剧$33#欧美剧$16#泰剧$34#新马剧$35#其他剧$25||内地综艺$27#港台综艺$28#日本综艺$29#韩国综艺$36#欧美综艺$30#新马泰综艺$37#其他综艺$38||国产动漫$31#日本动漫$32#韩国动漫$39#港台动漫$40#新马泰动漫$41#欧美动漫$42#其他动漫$43","分类url": "https://www.ylys.tv/vodshow/{cateId}-{area}-{by}-{class}-{lang}-{letter}---{catePg}---{year}.html"}},
{"key": "剧圈影视","name": "剧圈 • 影视","type": 3,"api": "csp_XYQHiker","ext": "./json/JQYS.json"},
{"key": "1905","name": "1905 • 影视","type": 3,"api": "csp_Web1905","searchable": 1,"quickSearch": 0,"filterable": 0},
{"key": "哆啦新番社","name": "哆啦 • 新番社","type": 3,"api": "csp_XBPQ","style": {"type": "list"},"ext": "./json/DLXFS.json"},
diff --git a/xiaosa/py/GZYS.py b/xiaosa/py/GZYS.py
new file mode 100644
index 00000000..220e1fcb
--- /dev/null
+++ b/xiaosa/py/GZYS.py
@@ -0,0 +1,496 @@
+# coding = utf-8
+#!/usr/bin/python
+import re
+import sys
+import json
+import time
+import base64
+import hashlib
+import random
+import string
+import urllib.parse
+from Crypto.Cipher import AES
+from Crypto.Util.Padding import pad, unpad
+from Crypto.PublicKey import RSA
+from Crypto.Cipher import PKCS1_v1_5
+from base.spider import Spider
+
+sys.path.append('..')
+
+class Spider(Spider):
+ def __init__(self):
+ self.name = "瓜子"
+ self.hosts = [
+ 'https://apinew.uozvr.com',
+ 'https://api.w32z7vtd.com',
+ 'https://api.6a7nnf7.com',
+ 'https://api.umygrx3.com',
+ 'https://api.rmedphk.com'
+ ]
+ self.host_index = 0
+ self.host = self.hosts[self.host_index]
+
+ # AES 固定密钥(与Java版一致)
+ self.AES_KEY = 'OITxa5OqAYjhswxx'
+ self.AES_IV = 'rCMNwZASNBKZ8mXV'
+
+ # RSA 公钥/私钥
+ self.RSA_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUM5+/y8sPsWkd1/RQS64X259EUwxFXFE5HlA65MqrxnPs0JqoSRojSDy5QhwvROlaD6TwRQHKMY2OAZ6SnQeUJsChTEFIR9qUkwrs3/MVUMxjsv6JS6Oe/juclyJGTgVmDhB55EafXsD0SQYVj/QXXsxR6ewR5E2kL52yAAD4yQIDAQAB"
+ self.RSA_PRIVATE_KEY = """-----BEGIN RSA PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGAe6hKrWLi1zQmjTT1
+ozbE4QdFeJGNxubxld6GrFGximxfMsMB6BpJhpcTouAqywAFppiKetUBBbXwYsYU
+1wNr648XVmPmCMCy4rY8vdliFnbMUj086DU6Z+/oXBdWU3/b1G0DN3E9wULRSwcK
+ZT3wj/cCI1vsCm3gj2R5SqkA9Y0CAwEAAQKBgAJH+4CxV0/zBVcLiBCHvSANm0l7
+HetybTh/j2p0Y1sTXro4ALwAaCTUeqdBjWiLSo9lNwDHFyq8zX90+gNxa7c5EqcW
+V9FmlVXr8VhfBzcZo1nXeNdXFT7tQ2yah/odtdcx+vRMSGJd1t/5k5bDd9wAvYdI
+DblMAg+wiKKZ5KcdAkEA1cCakEN4NexkF5tHPRrR6XOY/XHfkqXxEhMqmNbB9U34
+saTJnLWIHC8IXys6Qmzz30TtzCjuOqKRRy+FMM4TdwJBAJQZFPjsGC+RqcG5UvVM
+iMPhnwe/bXEehShK86yJK/g/UiKrO87h3aEu5gcJqBygTq3BBBoH2md3pr/W+hUM
+WBsCQQChfhTIrdDinKi6lRxrdBnn0Ohjg2cwuqK5zzU9p/N+S9x7Ck8wUI53DKm8
+jUJE8WAG7WLj/oCOWEh+ic6NIwTdAkEAj0X8nhx6AXsgCYRql1klbqtVmL8+95KZ
+K7PnLWG/IfjQUy3pPGoSaZ7fdquG8bq8oyf5+dzjE/oTXcByS+6XRQJAP/5ciy1b
+L3NhUhsaOVy55MHXnPjdcTX0FaLi+ybXZIfIQ2P4rb19mVq1feMbCXhz+L1rG8oa
+t5lYKfpe8k83ZA==
+-----END RSA PRIVATE KEY-----"""
+
+ self.DEVICE_OLD_KEY = "aLFBMWpxBrIDAD1Si/KVvm41"
+
+ # 设备信息(随机生成)
+ self.deviceId = str(864150060000000 + random.randint(0, 9999))
+ self.deviceKey = ''.join(random.choices('0123456789ABCDEF', k=40)) # 20字节hex大写
+ self.token = ""
+ self.token_id = ""
+ self.registered = False
+
+ self.header = {
+ 'User-Agent': 'Lavf/57.83.100',
+ 'code': 'GZ0369',
+ 'deviceId': self.deviceId,
+ 'lang': 'zh_cn',
+ 'Cache-Control': 'no-cache',
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ 'Version': '2604028',
+ 'PackageName': 'com.ae06aebdbb.y286327f5a.ofe849883320260517',
+ 'Ver': '3.0.3.2',
+ 'api-ver': '3.0.3.2',
+ 'Referer': self.host
+ }
+
+ self.cache = {}
+ self.cache_timeout = 300
+
+ # 初始化token
+ self.init_token()
+
+ def getName(self):
+ return self.name
+
+ def init(self, extend=''):
+ pass
+
+ # ---------- 设备注册与认证 ----------
+ def init_token(self):
+ """初始化token:注册设备 -> 刷新"""
+ print("===== 初始化设备认证 =====")
+ try:
+ if not self.registered:
+ self.sign_up()
+ # 刷新获取最终token
+ self.refresh_token()
+ except Exception as e:
+ print(f"初始化token失败: {e}")
+ # 兜底使用原有硬编码(几乎没用)
+ self.token = '024212ef0975c5306a1434e113a46463.bc77313e11a248558a6ca244ca980944ec3421fa480c50e0229ad91f1cb15aea582603202cd71796885c9e5163e500f1b72f737059aff1ddb8beea47c5a331d6760540345b7f88b2302a0e6e09589f9dcf3ff9175d8c905f990203f5fc04748008ea7a366571cbf5b09509a873dcfba3cf1d5590385f5f7ef6e01d1850974aa220eb5178c89e61c24411af9b9a19435e.06fde789ece48d9b33c5dc857e04e9b5838f08264d928b87237d3476c4484b46'
+
+ def sign_up(self):
+ """注册设备"""
+ print("注册新设备...")
+ params = {
+ "new_key": self.deviceKey,
+ "old_key": self.DEVICE_OLD_KEY,
+ "phone_type": 1,
+ "code": ""
+ }
+ result = self._auth_request('/App/Authentication/Device/signUp', params)
+ self._apply_auth(result)
+ self.registered = True
+
+ def sign_in(self):
+ """登录设备"""
+ print("设备登录...")
+ params = {
+ "new_key": self.deviceKey,
+ "old_key": self.DEVICE_OLD_KEY
+ }
+ result = self._auth_request('/App/Authentication/Device/signIn', params)
+ self._apply_auth(result)
+
+ def _apply_auth(self, result):
+ """从认证响应中提取token"""
+ new_token = result.get('token', '')
+ if not new_token:
+ raise Exception("认证失败,无token返回: {}".format(result))
+ self.token = new_token
+ new_token_id = result.get('app_user_id', '')
+ if new_token_id:
+ self.token_id = new_token_id
+ print(f"获取token成功, token前缀: {self.token[:30]}...")
+
+ def refresh_token(self):
+ """刷新token"""
+ print("刷新token...")
+ result = self._auth_request('/App/Authentication/Authenticator/refresh', {})
+ self._apply_auth(result)
+
+ def _auth_request(self, path, params):
+ """认证类请求(不需要ensure_token)"""
+ return self._send_encrypted_request(params, path, is_auth=True)
+
+ # ---------- 业务请求核心(修复加密与签名) ----------
+ def ensure_token(self):
+ """确保token有效,如未就绪则重新获取"""
+ if not self.token or not self.token_id:
+ if self.registered:
+ self.sign_in()
+ else:
+ self.sign_up()
+ self.refresh_token()
+
+ def _send_encrypted_request(self, data, path, is_auth=False):
+ """
+ 发送加密请求,返回解密后的字典
+ :param data: 业务参数字典
+ :param path: 请求路径
+ :param is_auth: 是否为认证类请求(signUp/signIn/refresh),此时不使用ensure_token
+ """
+ try:
+ if not is_auth:
+ self.ensure_token()
+
+ # 1. 将参数转为JSON并AES加密
+ json_params = json.dumps(data)
+ encrypted = self.aes_encrypt(json_params, self.AES_KEY, self.AES_IV)
+ request_key = encrypted.upper() # Java中是bytesToHex(encrypted).toUpperCase()
+
+ # 2. 生成keys (RSA加密 iv/key JSON)
+ key_json = json.dumps({"iv": self.AES_IV, "key": self.AES_KEY})
+ keys = self.rsa_encrypt(key_json, self.RSA_PUBLIC_KEY)
+
+ # 3. 生成签名
+ t = str(int(time.time()))
+ sign_str = f"token_id=,token={self.token},phone_type=1,request_key={request_key},app_id=1,time={t},keys={keys}*&zvdvdvddbfikkkumtmdwqppp?|4Y!s!2br"
+ signature = self.get_md5(sign_str) # 已改为大写
+
+ # 4. 构建请求体
+ body = {
+ 'token': self.token,
+ 'token_id': '',
+ 'phone_type': '1',
+ 'time': t,
+ 'phone_model': 'xiaomi-25031', # 与Java版保持一致
+ 'keys': keys,
+ 'request_key': request_key,
+ 'signature': signature,
+ 'app_id': '1',
+ 'ad_version': '1'
+ }
+
+ # 5. 发送请求
+ url = f"{self.host}{path}"
+ response = self.post(url, headers=self.header, data=body, timeout=10)
+
+ if response.status_code != 200:
+ raise Exception(f"HTTP {response.status_code}")
+
+ resp_json = response.json()
+ # 检查业务code(若不为200可能token过期)
+ if 'code' in resp_json and resp_json['code'] != 200:
+ print(f"业务错误码: {resp_json['code']}, 信息: {resp_json}")
+ # 如果不是认证请求,尝试重新获取token后重试一次(这里简单处理,外层get_data已有重试)
+ raise Exception("业务错误")
+
+ data_section = resp_json.get('data')
+ if not data_section:
+ raise Exception("响应缺少data字段")
+
+ encrypted_response = data_section.get('response_key', '')
+ encrypted_keys = data_section.get('keys', '')
+
+ # 6. 解密响应
+ decrypted_keys_json = self.rsa_decrypt(encrypted_keys, self.RSA_PRIVATE_KEY)
+ key_info = json.loads(decrypted_keys_json)
+ resp_key = key_info['key']
+ resp_iv = key_info['iv']
+ decrypted_data = self.aes_decrypt(encrypted_response, resp_key, resp_iv)
+ return json.loads(decrypted_data)
+
+ except Exception as e:
+ print(f"请求失败 [{path}]: {e}")
+ return None
+
+ def get_data(self, data, path, use_cache=True):
+ """带重试和域名轮询的数据获取(保持原框架)"""
+ try:
+ cache_key = f"{path}_{hash(str(data))}" if use_cache else None
+ if use_cache and cache_key in self.cache:
+ cached_data, timestamp = self.cache[cache_key]
+ if time.time() - timestamp < self.cache_timeout:
+ return cached_data
+
+ for attempt in range(3):
+ tried = 0
+ while tried < len(self.hosts):
+ self.host = self.hosts[self.host_index]
+ self.header['Referer'] = self.host
+ result = self._send_encrypted_request(data, path)
+ if result is not None:
+ print(f"请求成功: {path}, 域名: {self.host}")
+ if use_cache and cache_key:
+ self.cache[cache_key] = (result, time.time())
+ return result
+
+ # 切换到下一个域名
+ self.host_index = (self.host_index + 1) % len(self.hosts)
+ tried += 1
+
+ # 所有域名失败,尝试重新认证并重试
+ if attempt < 2:
+ print("所有域名失败,尝试重新认证...")
+ try:
+ self.ensure_token()
+ except:
+ pass
+ self.host_index = 0
+ else:
+ break
+ return None
+ except Exception as e:
+ print(f"get_data异常: {e}")
+ return None
+
+ # ---------- 加解密工具 ----------
+ def aes_encrypt(self, text, key, iv):
+ try:
+ key_bytes = key.encode('utf-8')
+ iv_bytes = iv.encode('utf-8')
+ cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
+ encrypted = cipher.encrypt(pad(text.encode('utf-8'), AES.block_size))
+ return encrypted.hex().upper()
+ except Exception as e:
+ print(f"AES加密失败: {e}")
+ return ""
+
+ def aes_decrypt(self, text, key, iv):
+ try:
+ key_bytes = key.encode('utf-8')
+ iv_bytes = iv.encode('utf-8')
+ cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
+ encrypted_bytes = bytes.fromhex(text)
+ decrypted = unpad(cipher.decrypt(encrypted_bytes), AES.block_size)
+ return decrypted.decode('utf-8')
+ except Exception as e:
+ print(f"AES解密失败: {e}")
+ return ""
+
+ def rsa_encrypt(self, text, public_key_str):
+ """RSA公钥加密(PKCS1v1.5)"""
+ try:
+ key = RSA.import_key("-----BEGIN PUBLIC KEY-----\n" + public_key_str + "\n-----END PUBLIC KEY-----")
+ cipher = PKCS1_v1_5.new(key)
+ encrypted = cipher.encrypt(text.encode('utf-8'))
+ return base64.b64encode(encrypted).decode('utf-8')
+ except Exception as e:
+ print(f"RSA加密失败: {e}")
+ return ""
+
+ def rsa_decrypt(self, encrypted_data, private_key_str):
+ """RSA私钥解密"""
+ try:
+ encrypted_bytes = base64.b64decode(encrypted_data)
+ rsa_key = RSA.import_key(private_key_str)
+ cipher = PKCS1_v1_5.new(rsa_key)
+ decrypted = cipher.decrypt(encrypted_bytes, None)
+ return decrypted.decode('utf-8') if decrypted else ""
+ except Exception as e:
+ print(f"RSA解密失败: {e}")
+ return ""
+
+ def get_md5(self, text):
+ return hashlib.md5(text.encode()).hexdigest().upper() # 与Java一致大写
+
+ # ---------- 业务方法(不变) ----------
+ def homeContent(self, filter):
+ result = {}
+ classes = [
+ {"type_name": "电影", "type_id": "1"},
+ {"type_name": "电视剧", "type_id": "2"},
+ {"type_name": "动漫", "type_id": "4"},
+ {"type_name": "综艺", "type_id": "3"},
+ {"type_name": "短剧", "type_id": "64"}
+ ]
+ result['class'] = classes
+ filters = {}
+ for cate in classes:
+ tid = cate['type_id']
+ filters[tid] = [
+ {"key": "area", "name": "地区", "value": [
+ {"n": "全部", "v": "0"}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"},
+ {"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"},
+ {"n": "日本", "v": "日本"}, {"n": "英国", "v": "英国"}, {"n": "法国", "v": "法国"},
+ {"n": "泰国", "v": "泰国"}, {"n": "印度", "v": "印度"}, {"n": "其他", "v": "其他"}
+ ]},
+ {"key": "year", "name": "年份", "value": [
+ {"n": "全部", "v": "0"}, {"n": "2025", "v": "2025"}, {"n": "2024", "v": "2024"},
+ {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"}, {"n": "2021", "v": "2021"},
+ {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"}, {"n": "2018", "v": "2018"},
+ {"n": "2017", "v": "2017"}, {"n": "2016", "v": "2016"}, {"n": "2015", "v": "2015"},
+ {"n": "2014", "v": "2014"}, {"n": "2013", "v": "2013"}, {"n": "2012", "v": "2012"},
+ {"n": "2011", "v": "2011"}, {"n": "2010", "v": "2010"}, {"n": "2009", "v": "2009"},
+ {"n": "2008", "v": "2008"}, {"n": "2007", "v": "2007"}, {"n": "2006", "v": "2006"},
+ {"n": "2005", "v": "2005"}, {"n": "更早", "v": "2004"}
+ ]},
+ {"key": "sort", "name": "排序", "value": [
+ {"n": "最新", "v": "d_id"}, {"n": "最热", "v": "d_hits"}, {"n": "推荐", "v": "d_score"}
+ ]}
+ ]
+ result['filters'] = filters
+ return result
+
+ def homeVideoContent(self):
+ return {'list': []}
+
+ def categoryContent(self, tid, pg, filter, extend):
+ videos = []
+ try:
+ body = {
+ "area": extend.get('area', '0'),
+ "year": extend.get('year', '0'),
+ "pageSize": "30",
+ "sort": extend.get('sort', 'd_id'),
+ "page": str(pg),
+ "tid": tid
+ }
+ cache_key = f"category_{tid}_{pg}_{hash(str(body))}"
+ data = self.get_cached_data(cache_key, body, '/App/IndexList/indexList')
+ if data and 'list' in data:
+ for item in data['list']:
+ vod_continu = item.get('vod_continu', 0)
+ remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}集'
+ video = {
+ "vod_id": f"{item.get('vod_id', '')}/{vod_continu}",
+ "vod_name": item.get('vod_name', ''),
+ "vod_pic": item.get('vod_pic', ''),
+ "vod_remarks": remarks
+ }
+ videos.append(video)
+ except Exception as e:
+ print(f"获取分类内容失败: {e}")
+ return {'list': videos, 'page': int(pg), 'pagecount': 9999, 'limit': 30, 'total': 999999}
+
+ def detailContent(self, ids):
+ try:
+ vod_id = ids[0].split('/')[0]
+ t = str(int(time.time()))
+ body1 = {"token_id": self.token_id, "vod_id": vod_id, "mobile_time": t, "token": self.token}
+ qdata = self.get_data(body1, '/App/IndexPlay/playInfo')
+ body2 = {"vurl_cloud_id": "2", "vod_d_id": vod_id}
+ jdata = self.get_data(body2, '/App/Resource/Vurl/show')
+ if not qdata or 'vodInfo' not in qdata:
+ return {'list': []}
+ vod = qdata['vodInfo']
+ video_detail = {
+ "vod_id": vod_id,
+ "vod_name": vod.get('vod_name', ''),
+ "vod_pic": vod.get('vod_pic', ''),
+ "vod_year": vod.get('vod_year', ''),
+ "vod_area": vod.get('vod_area', ''),
+ "vod_actor": vod.get('vod_actor', ''),
+ "vod_director": vod.get('vod_director', ''),
+ "vod_content": vod.get('vod_use_content', '').strip(),
+ "vod_play_from": "瓜子影视"
+ }
+ play_list = []
+ if jdata and 'list' in jdata:
+ for index, item in enumerate(jdata['list']):
+ if 'play' in item:
+ n, p = [], []
+ for key, value in item['play'].items():
+ if 'param' in value and value['param']:
+ n.append(key)
+ p.append(value['param'])
+ if p:
+ play_name = str(index + 1) if len(jdata['list']) != 1 else vod.get('vod_name', '')
+ play_url = f"{p[-1]}||{'@'.join(n)}"
+ play_list.append(f"{play_name}${play_url}")
+ video_detail["vod_play_url"] = "#".join(play_list)
+ return {'list': [video_detail]}
+ except Exception as e:
+ print(f"获取详情失败: {e}")
+ return {'list': []}
+
+ def searchContent(self, key, quick, pg=1):
+ videos = []
+ try:
+ body = {"keywords": key, "order_val": "1", "page": str(pg)}
+ data = self.get_data(body, '/App/Index/findMoreVod', use_cache=False)
+ if data and 'list' in data:
+ for item in data['list']:
+ vod_continu = item.get('vod_continu', 0)
+ remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}集'
+ videos.append({
+ "vod_id": f"{item.get('vod_id', '')}/{vod_continu}",
+ "vod_name": item.get('vod_name', ''),
+ "vod_pic": item.get('vod_pic', ''),
+ "vod_remarks": remarks
+ })
+ except Exception as e:
+ print(f"搜索失败: {e}")
+ return {'list': videos, 'page': int(pg), 'pagecount': 9999, 'limit': 30, 'total': 999999}
+
+ def playerContent(self, flag, id, vipFlags):
+ try:
+ parts = id.split('||')
+ if len(parts) < 2:
+ return {"parse": 0, "playUrl": "", "url": ""}
+ param_str = parts[0]
+ resolutions = parts[1].split('@') if len(parts) > 1 else []
+ params = {}
+ for pair in param_str.split('&'):
+ if '=' in pair:
+ key, value = pair.split('=', 1)
+ params[key] = value
+ if resolutions:
+ resolutions.sort(key=lambda x: int(x) if x.isdigit() else 0, reverse=True)
+ params['resolution'] = resolutions[0]
+ data = self.get_data(params, '/App/Resource/VurlDetail/showOne', use_cache=False)
+ if data and 'url' in data:
+ return {"parse": 0, "playUrl": "", "url": data['url'],
+ "header": json.dumps({"User-Agent": "Lavf/57.83.100", "Referer": "http://WJiZxLXA2.com/"}), 'danmaku': 'http://127.0.0.1:9978/proxy?do=diydanmu'}
+ return {"parse": 0, "playUrl": "", "url": ""}
+ except Exception as e:
+ print(f"播放解析失败: {e}")
+ return {"parse": 0, "playUrl": "", "url": ""}
+
+ def isVideoFormat(self, url):
+ video_formats = ['.m3u8', '.mp4', '.avi', '.mkv', '.flv', '.ts']
+ return any(url.lower().endswith(fmt) for fmt in video_formats)
+
+ def manualVideoCheck(self):
+ pass
+
+ def localProxy(self, params):
+ return None
+
+ def get_cached_data(self, cache_key, data, path):
+ current_time = time.time()
+ if cache_key in self.cache:
+ cached_data, timestamp = self.cache[cache_key]
+ if current_time - timestamp < self.cache_timeout:
+ return cached_data
+ result = self.get_data(data, path)
+ if result:
+ self.cache[cache_key] = (result, current_time)
+ return result
+
+if __name__ == '__main__':
+ pass
\ No newline at end of file
diff --git a/xiaosa/py/SZYS.py b/xiaosa/py/SZYS.py
new file mode 100644
index 00000000..2e76bc1a
--- /dev/null
+++ b/xiaosa/py/SZYS.py
@@ -0,0 +1,330 @@
+# -*- coding: utf-8 -*-
+# by @嗷呜
+import json
+import random
+import sys
+from base64 import b64encode, b64decode
+from concurrent.futures import ThreadPoolExecutor
+
+# 引入 RSA 加解密所需模块
+from Crypto.PublicKey import RSA
+from Crypto.Cipher import PKCS1_v1_5
+
+sys.path.append('..')
+from base.spider import Spider
+
+class Spider(Spider):
+
+ def init(self, extend=""):
+ did = self.getdid()
+ self.headers.update({'deviceId': did})
+ token = self.gettk()
+ self.headers.update({'token': token})
+
+ def getName(self):
+ pass
+
+ def isVideoFormat(self, url):
+ pass
+
+ def manualVideoCheck(self):
+ pass
+
+ def destroy(self):
+ pass
+
+ # 1. 修改为主机域名
+ host = 'http://qkys.qukanwh.com'
+
+ # 2. 同步原脚本的配置请求头
+ headers = {
+ 'HOST': 'qkys.qukanwh.com',
+ 'User-Agent': 'okhttp/4.12.0',
+ 'client': 'app',
+ 'deviceType': 'Android',
+ 'Referer': ''
+ }
+
+ # 3. 导入原脚本中的 RSA 密钥对与配置
+ publicKey_str = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoYt0BP77U+DM08BiI/QbSRIfxijXo85BTPqIM1Ow8BNwhLETzRIZ+dEwdWDbydG/PspgBAfRpGaYVdJYtvaC2JnoO8+Ik6qMWojfEJxSFLa0Pb0A892tun4gsxoEMjcreZ+YGyaBxAfqX0BSMfdrOgIYaZQjYrw9TRLlUT31QoQIDAQAB\n-----END PUBLIC KEY-----"
+ privateKey_str = "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCquQQ5r6+yJI8CDFkXRp8vUsdD45ov8EP12ooLs56ca2DQXaSNGS9910bAPVA9chkp0mKIvKqjAsHz5Tl9EeNPblarGEeJUIxpxZtiSqNTpvtiD/TjhpzuHYic7RAfQ/h7p/ypE8ymU42pYjsB5t26Mv6XgkLV+jzrSf73HlCuS0iMyLmt6zz3Mw9izM13EpB8iFLtfbbYymycKTx4RAmPQLwhNGex/AlUIYxXP4R2yyaa4W6mEtc6aME2QuzJFxPgP3HJ9NBx/LWVn4skxWjZ7zg+VRQRHnjyVaSLu3Z5gN5ITWCyE32qaHJa6WBahZj5jWhRyAG1bQ+xKJa8lBL5AgMBAAECggEAUwv9SjJ0PSwbhNuM2w23kcWquROWhYtTA91zGY4esehqB/IFgb2mpIh8Gje5OKqwIu/8jpd4SiOlRYdUF8sD0DfUYRZGdj2AkFNX6tBz8tVfo6wvbB6naA1lzzBij1L5JO3qsjS3cJFkb+kg2yP66AC2Z+0tpfk8eRhdtshAZwfcd1DEGt1uAvYL1eaUK9HRvpt9lPeGcHERDl2hBd4uyaF0K1O+zF9y59nYbTySWPxRZq3sFEE85xRMlstD7YZi7W2gKvMFRD4/FKmrZ3m7aKJRITtyKOyyPcYmepNv3Qv7kk59Pg38n2WWQ0Ra/bCH3E48YNCnQvZMpitkTfJhoQKBgQDbnROOYTP8OTJ6f/qhoGjxeO3x1VOaOp8l0x7b0SCfoqNGS0Cyiqj72BmJtPMPqSTjn6MmNzqbg1KOdhXyzNozs+i5ccW1M56j96mr5I/Z0FpE3oyIHNfDDBlf9M8YQqEF9oYxniYYft9oapO7cRQkHER6qpvnHTavwlv4m78CXwKBgQDHAjs2YlpKDdI1lcbZJCc7TwtH+Pd2bUki8YXafWNcPhITQHbOZjr310eK1QJC6GJncjkOqbX7yv3ivvTO35FZTQhuA1xEG1P00FG8bE0tHYPIwQHi9y0eA5cieMdo8E6XYria1mw/3fqSQEsfZyJlR32JQIoGAipM8iO1X2nZpwKBgDkMFIhnt5lNQk+P7wsNIDWZtDWdtJnboHuy29E+Abt2A/O+mI/IdRz2hau/1WO8DFkUnszOi+rZshhPlGP90rCbi1igtTrcrdjp/KkqNjPea5R4OwkgdOu1uOG0NheXNzzVTQaWjk7Opjn5dWa7eP/oV+GFb/oZHJuLYVizHGsBAoGADA7rjZEKDYCm4w5PPSr+oY5ZjaPdQrS+gLqHtMRyN82fBMGcMUdqfUfzEstzVqCEDeaS5HuOBlK3bXzKkppjUTjksN3NQmcxgBz7RuJ9DqXCLXDcb2cwuafYCYOt+YLOEEgwDVm+t2P44dG5e46hO+fICH/7nP+WlpD5buz4GfMCgYB57r3g/6hi9WUDnfc7ZAzWMqR0EhJVYKYy+KFEtdIPzhkkIHq5RASe88E9kzoGoZFdb3tIjvGZWcHerirrqWkMsuQtP/Qi0zjieid5tAPj+r4kbiCVTw0E0jnmPBzGInQi7lpeTTKnG1fbyS5lBS+WmHfIuzpECgCkxhaT+LJJkg==\n-----END PRIVATE KEY-----"
+
+ # RSA 公钥加密实现
+ def rsa_encrypt(self, text):
+ try:
+ key = RSA.import_key(self.publicKey_str)
+ cipher = PKCS1_v1_5.new(key)
+ cipher_text = cipher.encrypt(text.encode('utf-8'))
+ return b64encode(cipher_text).decode('utf-8')
+ except Exception as e:
+ print(f"RSA加密失败: {e}")
+ return ""
+
+ # RSA 私钥解密实现
+ def rsa_decrypt(self, text):
+ try:
+ key = RSA.import_key(self.privateKey_str)
+ cipher = PKCS1_v1_5.new(key)
+ raw_bytes = b64decode(text.encode('utf-8'))
+
+ decrypted = b""
+ offset = 0
+ while offset < len(raw_bytes):
+ chunk = raw_bytes[offset:offset + 256]
+ decrypted += cipher.decrypt(chunk, None)
+ offset += 256
+ return decrypted.decode('utf-8')
+ except Exception as e:
+ print(f"RSA解密失败: {e}")
+ return ""
+
+ def homeContent(self, filter):
+ data = self.post(f"{self.host}/api/v1/app/screen/screenType", headers=self.headers).json()
+ result = {}
+ cate = {
+ "类型": "type",
+ "地区": "area",
+ "年份": "year"
+ }
+ sort = {
+ 'key': 'sort',
+ 'name': '排序',
+ 'value': [{'n': '最新', 'v': 'NEWEST'}, {'n': '热门', 'v': 'HOT'}, {'n': '收藏', 'v': 'COLLECT'}]
+ }
+ classes = []
+ filters = {}
+ for k in data.get('data', []):
+ classes.append({
+ 'type_name': k['name'],
+ 'type_id': str(k['id'])
+ })
+ filters[str(k['id'])] = []
+ for v in k.get('children', []):
+ if v['name'] in cate:
+ filters[str(k['id'])].append({
+ 'name': v['name'],
+ 'key': cate[v['name']],
+ 'value': [{'n': i['name'], 'v': i['name']} for i in v.get('children', [])]
+ })
+ filters[str(k['id'])].append(sort)
+ result['class'] = classes
+ result['filters'] = filters
+ return result
+
+ def homeVideoContent(self):
+ jdata = {
+ "condition": {
+ "sreecnTypeEnum": "NEWEST"
+ },
+ "pageNum": 1,
+ "pageSize": 40
+ }
+ data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
+ return {'list': self.getlist(data.get('data', {}).get('records', []))}
+
+ def categoryContent(self, tid, pg, filter, extend):
+ # 保持最纯粹的条件字段,移除任何空字符串占位
+ condition = {
+ 'sreecnTypeEnum': 'NEWEST',
+ 'typeId': int(tid) if str(tid).isdigit() else tid
+ }
+
+ if extend:
+ if 'sort' in extend:
+ condition['sreecnTypeEnum'] = extend.pop('sort')
+ condition.update(extend)
+
+ jdata = {
+ 'condition': condition,
+ 'pageNum': int(pg),
+ 'pageSize': 40,
+ }
+
+ try:
+ data = self.post(f"{self.host}/api/v1/app/screen/screenMovie", headers=self.headers, json=jdata).json()
+ result = {}
+ if data and data.get('data') and 'records' in data['data']:
+ result['list'] = self.getlist(data['data']['records'])
+ else:
+ result['list'] = []
+ result['page'] = pg
+ result['pagecount'] = 9999
+ result['limit'] = 40
+ result['total'] = 999999
+ return result
+ except Exception as e:
+ print(f"分类获取错误: {e}")
+ return {'list': [], 'page': pg}
+
+ def detailContent(self, ids):
+ ids = ids[0].split('@@')
+ jdata = {"id": int(ids[0]), "typeId": ids[-1]}
+ v = self.post(f"{self.host}/api/v1/app/play/movieDesc", headers=self.headers, json=jdata).json()
+ v = v.get('data', {})
+ vod = {
+ 'type_name': v.get('typeId', ''),
+ 'vod_year': v.get('year', ''),
+ 'vod_area': v.get('area', ''),
+ 'vod_actor': v.get('star', ''),
+ 'vod_director': v.get('director', ''),
+ 'vod_content': v.get('introduce', ''),
+ 'vod_play_from': '',
+ 'vod_play_url': ''
+ }
+
+ play_params = {
+ "id": int(ids[0]),
+ "source": 0,
+ "typeId": ids[-1]
+ }
+ encrypt_payload = {"key": self.rsa_encrypt(json.dumps(play_params))}
+
+ c_res = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
+ decrypted_play_str = self.rsa_decrypt(c_res.get('data', ''))
+ if not decrypted_play_str:
+ return {'list': [vod]}
+
+ decrypted_play_data = json.loads(decrypted_play_str)
+ l = decrypted_play_data.get('moviePlayerList', [])
+ if not l:
+ return {'list': [vod]}
+
+ n = {str(i['id']): i['moviePlayerName'] for i in l}
+
+ m = play_params.copy()
+ m.update({'playerId': l[0]['id']})
+
+ first_source_payload = {"key": self.rsa_encrypt(json.dumps(m))}
+ first_res = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=first_source_payload).json()
+
+ decrypted_first_str = self.rsa_decrypt(first_res.get('data', ''))
+ if decrypted_first_str:
+ decrypted_first_episode = json.loads(decrypted_first_str)
+ pd = self.getv(m, decrypted_first_episode.get('episodeList', []))
+ else:
+ pd = {}
+
+ if len(l) > 1:
+ with ThreadPoolExecutor(max_workers=len(l)-1) as executor:
+ future_to_player = {executor.submit(self.getd, play_params, player): player for player in l[1:]}
+ for future in future_to_player:
+ try:
+ o, p = future.result()
+ if p:
+ pd.update(self.getv(o, p))
+ except Exception as e:
+ print(f"多线路请求失败: {e}")
+ w, e = [], []
+ for i, x in pd.items():
+ if x:
+ w.append(n.get(i, '未知线路'))
+ e.append(x)
+ vod['vod_play_from'] = '$$$'.join(w)
+ vod['vod_play_url'] = '$$$'.join(e)
+ return {'list': [vod]}
+
+ def searchContent(self, key, quick, pg="1"):
+ jdata = {
+ "condition": {
+ "value": str(key)
+ },
+ "pageNum": int(pg),
+ "pageSize": 40
+ }
+ try:
+ data = self.post(f"{self.host}/api/v1/app/search/searchMovie", headers=self.headers, json=jdata).json()
+ return {'list': self.getlist(data.get('data', {}).get('records', [])), 'page': pg}
+ except Exception as e:
+ print(f"搜索请求失败: {e}")
+ return {'list': [], 'page': pg}
+
+ def playerContent(self, flag, id, vipFlags):
+ raw_id_str = self.d64(id)
+ if not raw_id_str:
+ return {'parse': 0, 'url': ''}
+ jdata = json.loads(raw_id_str)
+ encrypt_payload = {"key": self.rsa_encrypt(json.dumps(jdata))}
+ data = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
+
+ try:
+ decrypted_url_data = json.loads(self.rsa_decrypt(data.get('data', '')))
+ playerUrl = decrypted_url_data.get('url', '')
+ if not playerUrl:
+ return {'parse': 0, 'url': ''}
+
+ params = {'playerUrl': playerUrl, 'playerId': jdata['playerId']}
+ pd = self.fetch(f"{self.host}/api/v1/app/play/analysisMovieUrl", headers=self.headers, params=params).json()
+ url, p = pd.get('data', ''), 0
+ except Exception as e:
+ print(f"解析流媒体直链失败: {e}")
+ url, p = "", 0
+ return {'parse': p, 'url': url, 'header': {'User-Agent': 'okhttp/4.12.0'}, 'danmaku': 'http://127.0.0.1:9978/proxy?do=diydanmu'}
+
+ def localProxy(self, param):
+ pass
+
+ def liveContent(self, url):
+ pass
+
+ def gettk(self):
+ self.headers.update({'deviceId': self.getdid()})
+ try:
+ data = self.fetch(f"{self.host}/api/v1/app/user/visitorInfo", headers=self.headers).json()
+ return data.get('data', {}).get('token', '')
+ except:
+ return ""
+
+ def getdid(self):
+ did = self.getCache('ldid')
+ if not did:
+ hex_chars = '0123456789abcdef'
+ did = ''.join(random.choice(hex_chars) for _ in range(16))
+ self.setCache('ldid', did)
+ return did
+
+ def getd(self, jdata, player):
+ x = jdata.copy()
+ x.update({'playerId': player['id']})
+ encrypt_payload = {"key": self.rsa_encrypt(json.dumps(x))}
+ response = self.post(f"{self.host}/api/v1/app/play/movieDetails", headers=self.headers, json=encrypt_payload).json()
+ decrypted_str = self.rsa_decrypt(response.get('data', ''))
+ if decrypted_str:
+ decrypted_episode = json.loads(decrypted_str)
+ return x, decrypted_episode.get('episodeList', [])
+ return x, []
+
+ def getv(self, d, c):
+ f = {str(d['playerId']): ''}
+ g = []
+ for i in c:
+ j = d.copy()
+ j.update({'episodeId': i['id']})
+ g.append(f"{i['episode']}${self.e64(json.dumps(j))}")
+ f[str(d['playerId'])] = '#'.join(g)
+ return f
+
+ def getlist(self, data):
+ videos = []
+ for i in data:
+ if not i.get('id'):
+ continue
+ videos.append({
+ 'vod_id': f"{i['id']}@@{i.get('typeId', '')}",
+ 'vod_name': i.get('name', ''),
+ 'vod_pic': i.get('cover', ''),
+ 'vod_year': i.get('year', ''),
+ 'vod_remarks': i.get('totalEpisode', '')
+ })
+ return videos
+
+ def e64(self, text):
+ try:
+ return b64encode(text.encode('utf-8')).decode('utf-8')
+ except:
+ return ""
+
+ def d64(self, encoded_text):
+ try:
+ return b64decode(encoded_text.encode('utf-8')).decode('utf-8')
+ except:
+ return ""
diff --git a/xiaosa/py/XYDJ.py b/xiaosa/py/XYDJ.py
new file mode 100644
index 00000000..8a7d540f
--- /dev/null
+++ b/xiaosa/py/XYDJ.py
@@ -0,0 +1,343 @@
+# coding = utf-8
+# !/usr/bin/python
+
+"""
+"""
+
+from Crypto.Util.Padding import unpad
+from Crypto.Util.Padding import pad
+from urllib.parse import unquote
+from Crypto.Cipher import ARC4
+from urllib.parse import quote
+from base.spider import Spider
+from Crypto.Cipher import AES
+from bs4 import BeautifulSoup
+from base64 import b64decode
+import urllib.request
+import urllib.parse
+import binascii
+import requests
+import base64
+import json
+import time
+import sys
+import re
+import os
+
+sys.path.append('..')
+
+xurl = "https://app.whjzjx.cn"
+
+headers = {
+ 'User-Agent': 'Linux; Android 12; Pixel 3 XL) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36'
+ }
+
+headerf = {
+ "platform": "1",
+ "user_agent": "Mozilla/5.0 (Linux; Android 9; V1938T Build/PQ3A.190705.08211809; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Safari/537.36",
+ "content-type": "application/json; charset=utf-8"
+ }
+
+times = int(time.time() * 1000)
+
+data = {
+ "device": "2a50580e69d38388c94c93605241fb306",
+ "package_name": "com.jz.xydj",
+ "android_id": "ec1280db12795506",
+ "install_first_open": True,
+ "first_install_time": 1752505243345,
+ "last_update_time": 1752505243345,
+ "report_link_url": "",
+ "authorization": "",
+ "timestamp": times
+ }
+
+plain_text = json.dumps(data, separators=(',', ':'), ensure_ascii=False)
+
+key = "B@ecf920Od8A4df7"
+key_bytes = key.encode('utf-8')
+plain_bytes = plain_text.encode('utf-8')
+cipher = AES.new(key_bytes, AES.MODE_ECB)
+padded_data = pad(plain_bytes, AES.block_size)
+ciphertext = cipher.encrypt(padded_data)
+encrypted = base64.b64encode(ciphertext).decode('utf-8')
+
+response = requests.post("https://u.shytkjgs.com/user/v3/account/login", headers=headerf, data=encrypted)
+response_data = response.json()
+Authorization = response_data['data']['token']
+
+headerx = {
+ 'authorization': Authorization,
+ 'platform': '1',
+ 'version_name': '3.8.3.1'
+ }
+
+class Spider(Spider):
+ global xurl
+ global headerx
+ global headers
+
+ def getName(self):
+ return "首页"
+
+ def init(self, extend):
+ pass
+
+ def isVideoFormat(self, url):
+ pass
+
+ def manualVideoCheck(self):
+ pass
+
+ def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
+ if pl == 3:
+ plx = []
+ while True:
+ start_index = text.find(start_str)
+ if start_index == -1:
+ break
+ end_index = text.find(end_str, start_index + len(start_str))
+ if end_index == -1:
+ break
+ middle_text = text[start_index + len(start_str):end_index]
+ plx.append(middle_text)
+ text = text.replace(start_str + middle_text + end_str, '')
+ if len(plx) > 0:
+ purl = ''
+ for i in range(len(plx)):
+ matches = re.findall(start_index1, plx[i])
+ output = ""
+ for match in matches:
+ match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
+ if match3:
+ number = match3.group(1)
+ else:
+ number = 0
+ if 'http' not in match[0]:
+ output += f"#{match[1]}${number}{xurl}{match[0]}"
+ else:
+ output += f"#{match[1]}${number}{match[0]}"
+ output = output[1:]
+ purl = purl + output + "$$$"
+ purl = purl[:-3]
+ return purl
+ else:
+ return ""
+ else:
+ start_index = text.find(start_str)
+ if start_index == -1:
+ return ""
+ end_index = text.find(end_str, start_index + len(start_str))
+ if end_index == -1:
+ return ""
+
+ if pl == 0:
+ middle_text = text[start_index + len(start_str):end_index]
+ return middle_text.replace("\\", "")
+
+ if pl == 1:
+ middle_text = text[start_index + len(start_str):end_index]
+ matches = re.findall(start_index1, middle_text)
+ if matches:
+ jg = ' '.join(matches)
+ return jg
+
+ if pl == 2:
+ middle_text = text[start_index + len(start_str):end_index]
+ matches = re.findall(start_index1, middle_text)
+ if matches:
+ new_list = [f'{item}' for item in matches]
+ jg = '$$$'.join(new_list)
+ return jg
+
+ def homeContent(self, filter):
+ result = {}
+ result = {"class": [{"type_id": "1", "type_name": "剧场"},
+ {"type_id": "3", "type_name": "新剧"},
+ {"type_id": "2", "type_name": "热播"},
+ {"type_id": "7", "type_name": "星选"},
+ {"type_id": "5", "type_name": "阳光"}],
+ }
+
+ return result
+
+ def homeVideoContent(self):
+ videos = []
+
+ url= f'{xurl}/v1/theater/home_page?theater_class_id=1&class2_id=4&page_num=1&page_size=24'
+ detail = requests.get(url=url, headers=headerx)
+ detail.encoding = "utf-8"
+ if detail.status_code == 200:
+ data = detail.json()
+
+ for vod in data['data']['list']:
+
+ name = vod['theater']['title']
+
+ id = vod['theater']['id']
+
+ pic = vod['theater']['cover_url']
+
+ remark = vod['theater']['play_amount_str']
+
+ video = {
+ "vod_id": id,
+ "vod_name": name,
+ "vod_pic": pic,
+ "vod_remarks": remark
+ }
+ videos.append(video)
+
+ result = {'list': videos}
+ return result
+
+ def categoryContent(self, cid, pg, filter, ext):
+ result = {}
+ videos = []
+
+ url = f'{xurl}/v1/theater/home_page?theater_class_id={cid}&page_num={pg}&page_size=24'
+ detail = requests.get(url=url,headers=headerx)
+ detail.encoding = "utf-8"
+ if detail.status_code == 200:
+ data = detail.json()
+
+ for vod in data['data']['list']:
+
+ name = vod['theater']['title']
+
+ id = vod['theater']['id']
+
+ pic = vod['theater']['cover_url']
+
+ remark = vod['theater']['theme']
+
+ video = {
+ "vod_id": id,
+ "vod_name": name,
+ "vod_pic": pic,
+ "vod_remarks": remark
+ }
+ videos.append(video)
+
+ result = {'list': videos}
+ result['page'] = pg
+ result['pagecount'] = 9999
+ result['limit'] = 90
+ result['total'] = 999999
+ return result
+
+ def detailContent(self, ids):
+ did = ids[0]
+ result = {}
+ videos = []
+ xianlu = ''
+ bofang = ''
+
+ url = f'{xurl}/v2/theater_parent/detail?theater_parent_id={did}'
+ detail = requests.get(url=url, headers=headerx)
+ detail.encoding = "utf-8"
+ if detail.status_code == 200:
+ data = detail.json()
+
+ url = 'https://fs-im-kefu.7moor-fs1.com/ly/4d2c3f00-7d4c-11e5-af15-41bf63ae4ea0/1732707176882/jiduo.txt'
+ response = requests.get(url)
+ response.encoding = 'utf-8'
+ code = response.text
+ name = self.extract_middle_text(code, "s1='", "'", 0)
+ Jumps = self.extract_middle_text(code, "s2='", "'", 0)
+
+ content = '剧情:' + data['data']['introduction']
+
+ area = data['data']['desc_tags'][0]
+
+ remarks = data['data']['filing']
+
+ # 修复剧集只有一集的问题 - 检查theaters数据是否存在且不为空
+ if 'theaters' in data['data'] and data['data']['theaters']:
+ for sou in data['data']['theaters']:
+ id = sou['son_video_url']
+ name = sou['num']
+ bofang = bofang + str(name) + '$' + id + '#'
+
+ bofang = bofang[:-1] if bofang.endswith('#') else bofang
+ xianlu = '星芽'
+ else:
+ # 如果没有theaters数据,检查是否有单个视频URL
+ if 'video_url' in data['data'] and data['data']['video_url']:
+ bofang = '1$' + data['data']['video_url']
+ xianlu = '星芽'
+ else:
+ bofang = Jumps
+ xianlu = '1'
+
+ videos.append({
+ "vod_id": did,
+ "vod_content": content,
+ "vod_remarks": remarks,
+ "vod_area": area,
+ "vod_play_from": xianlu,
+ "vod_play_url": bofang
+ })
+
+ result['list'] = videos
+ return result
+
+ def playerContent(self, flag, id, vipFlags):
+
+ result = {}
+ result["parse"] = 0
+ result["playUrl"] = ''
+ result["url"] = id
+ result["header"] = headers
+ return result
+
+ def searchContentPage(self, key, quick, page):
+ result = {}
+ videos = []
+
+ payload = {
+ "text": key
+ }
+
+ url = f"{xurl}/v3/search"
+ detail = requests.post(url=url, headers=headerx, json=payload)
+ if detail.status_code == 200:
+ detail.encoding = "utf-8"
+ data = detail.json()
+
+ for vod in data['data']['theater']['search_data']:
+
+ name = vod['title']
+
+ id = vod['id']
+
+ pic = vod['cover_url']
+
+ remark = vod['score_str']
+
+ video = {
+ "vod_id": id,
+ "vod_name": name,
+ "vod_pic": pic,
+ "vod_remarks": remark
+ }
+ videos.append(video)
+
+ result['list'] = videos
+ result['page'] = page
+ result['pagecount'] = 9999
+ result['limit'] = 90
+ result['total'] = 999999
+ return result
+
+ def searchContent(self, key, quick, pg="1"):
+ return self.searchContentPage(key, quick, '1')
+
+ def localProxy(self, params):
+ if params['type'] == "m3u8":
+ return self.proxyM3u8(params)
+ elif params['type'] == "media":
+ return self.proxyMedia(params)
+ elif params['type'] == "ts":
+ return self.proxyTs(params)
+ return None
\ No newline at end of file
diff --git a/xiaosa/spider.jar b/xiaosa/spider.jar
index 38a911a9..83b2ba7a 100644
Binary files a/xiaosa/spider.jar and b/xiaosa/spider.jar differ