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