From 38b84436e0a670da2cb8c94719208f70d5260b64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 11:22:44 +0000 Subject: [PATCH] Sync all projects --- FGBLH/Web鱼壳海豚.json | 340 +- FGBLH/Web鱼壳海豚py.json | 6 + FGBLH/ok海豚.json | 320 +- FGBLH/py/采集聚合.py | 23 +- FGBLH/py/采集聚合成人.py | 7 + Tomorrow/html/玩偶聚合.html | 3524 +++++++++++++ Tomorrow/html/盘链.html | 8175 +++++++++++++++++++++++++++++-- cluntop_tvbox/lib/iptv.m3u | 2208 +++++---- cluntop_tvbox/lib/iptv.txt | 2198 +++++---- cluntop_tvbox/lib/iptv_test.m3u | 810 +-- cluntop_tvbox/lib/iptv_test.txt | 803 +-- tvbox/lib/iptv.m3u | 2208 +++++---- tvbox/lib/iptv.txt | 2198 +++++---- tvbox/lib/iptv_test.m3u | 810 +-- tvbox/lib/iptv_test.txt | 803 +-- 15 files changed, 17274 insertions(+), 7159 deletions(-) create mode 100644 Tomorrow/html/玩偶聚合.html diff --git a/FGBLH/Web鱼壳海豚.json b/FGBLH/Web鱼壳海豚.json index 90786bce..778a2287 100644 --- a/FGBLH/Web鱼壳海豚.json +++ b/FGBLH/Web鱼壳海豚.json @@ -550,53 +550,13 @@ "api":"csp_gy", "homePage":"https://fgblh.github.io/uhuj.github.io/观影.html" }, - {"key": "aidan", "name": "🐬艾旦影视(聚)(有三级片)🔞[追剧]", "type": 1, "api": "https://lovedan.net/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "ikun", "name": "🐬爱坤┃影视(有三级片)[追剧]", "type": 1, "api": "https://ikunzyapi.com/api.php/provide/vod/from/ikm3u8/at/json", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "U酷資源", "name": "🐬U酷資源(有三级片)[追剧]", "type": 1, "api": "https://api.ukuapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "虎牙资源", "name": "🐬虎牙资源(有三级片)[追剧]", "type": 1, "api": "https://www.huyaapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "ttzy", "name": "🐬天天┃影视(有三级片)🔞[追剧]", "type": 1, "api": "https://api.1080zyku.com/inc/apijson.php", "searchable": 1, "quickSearch": 1, "filterable": 1}, - { - "key": "非凡", - "name": "🐬非凡影视[追剧]", - "type": 1, - "api": "http://cj.ffzyapi.com/api.php/provide/vod/", - "searchable": 1, - "changeable": 1, - "categories": [ - "国产动漫", - "日韩动漫", - "国产剧", - "韩国剧", - "日本剧", - "电影片", - "连续剧", - "综艺片", - "动漫片", - "动作片", - "喜剧片", - "爱情片", - "科幻片", - "恐怖片", - "剧情片", - "战争片", - "香港剧", - "欧美剧", - "记录片", - "台湾剧", - "海外剧", - "泰国剧", - "大陆综艺", - "港台综艺", - "日韩综艺", - "欧美综艺", - "欧美动漫", - "港台动漫", - "海外动漫" - ] - }, + {"key": "aidan", "name": "🐬艾旦影视┃伦理🔞[追剧]", "type": 1, "api": "https://lovedan.net/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, + {"key": "U酷資源", "name": "🐬U酷資源┃伦理[追剧]", "type": 1, "api": "https://api.ukuapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, + {"key": "虎牙资源", "name": "🐬虎牙资源┃伦理[追剧]", "type": 1, "api": "https://www.huyaapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, + {"key": "ttzy", "name": "🐬天天影视┃伦理🔞[追剧]", "type": 1, "api": "https://api.1080zyku.com/inc/apijson.php", "searchable": 1, "quickSearch": 1, "filterable": 1}, { "key": "天涯资源", - "name": "🐬天涯资源(有三级片)[追剧]", + "name": "🐬天涯资源┃伦理[追剧]", "type": 0, "api": "https://tyyszyapi.com/api.php/provide/vod/at/xml/?ac=list", "searchable": 1, @@ -604,156 +564,12 @@ }, { "key": "iKun资源", - "name": "🐬iKun资源(有三级片)[追剧]", + "name": "🐬iKun资源┃伦理[追剧]", "type": 0, "api": "https://ikunzyapi.com/api.php/provide/vod/at/xml", "searchable": 1, "quickSearch": 1 }, - { - "key": "闪电影视", - "name": "🐬闪电影视[追剧]", - "type": 1, - "api": "http://sdzyapi.com/api.php/provide/vod/", - "searchable": 1, - "quickSearch": 1, - "categories": [ - "国产剧", - "短剧", - "韩国剧", - "香港剧", - "台湾剧", - "欧美剧", - "动作片", - "科幻片", - "战争片", - "奇幻片", - "喜剧片", - "爱情片", - "恐怖片", - "犯罪片", - "悬疑片", - "惊悚片", - "剧情片", - "冒险片", - "记录片", - "动作片", - "日本剧", - "泰剧", - "国产综艺", - "港台综艺", - "欧美综艺", - "日韩综艺", - "国产动漫", - "港台动漫", - "日韩动漫" - ] - }, - { - "key": "牛牛影视", - "name": "🐬牛牛影视[追剧]", - "type": 0, - "api": "https://api.niuniuzy.me/api.php/provide/vod/at/xml", - "searchable": 1, - "quickSearch": 1, - "categories": [ - "动作片", - "喜剧片", - "爱情片", - "科幻片", - "恐怖片", - "剧情片", - "战争片", - "动画片", - "纪录片", - "预告片", - "邵氏电影", - "4K电影", - "国产剧", - "欧美剧", - "韩剧", - "日剧", - "港剧", - "台剧", - "泰剧", - "海外剧", - "国产动漫", - "日韩动漫", - "欧美动漫", - "港台动漫", - "海外动漫", - "影视解说", - "爽文短剧", - "有声动漫", - "女频恋爱", - "反转爽剧", - "古装仙侠", - "年代穿越", - "脑洞悬疑", - "现代都市", - "演唱会", - "大陆综艺", - "日韩综艺", - "港台综艺", - "欧美综艺", - "篮球", - "足球" - ] - }, - { - "key": "雲飛影视", - "name": "🐬雲飛影视[追剧]", - "type": 1, - "api": "http://cj.lziapi.com/api.php/provide/vod/", - "searchable": 1, - "quickSearch": 1, - "filterable": 0, - "playurl": "json:https://lziplayer.com/?url=", - "categories": [ - "国产剧", - "国产动漫", - "泰国剧", - "小湾剧", - "香港剧", - "欧美剧", - "韩国剧", - "日本剧", - "动漫", - "体育", - "加片", - "动作片", - "爱情片", - "喜剧片" - ] - }, -{"key": "火狐","name": "🐬火狐影视🦊[追剧]","type": 1,"api": "https://hhzyapi.com/api.php/provide/vod/","searchable": 1,"quickSearch": 0,"filterable": 1,"categories": [ "内地剧", "动作片", "科幻片", "战争片", "喜剧片", "爱情片", "恐怖片", "犯罪片", "剧情片", "冒险片", "记录片", "韩剧", "香港剧", "台湾剧", "欧美剧", "日剧", "马泰剧", "体育赛事", "综艺", "动画片", "中国动漫", "日本动漫", "欧美动漫"] -}, - { - "key": "量子2k", - "name": "🐬量子影视[追剧]", - "type": 1, - "api": "http://cj.lziapi.com/api.php/provide/vod/", - "searchable": 1, - "quickSearch": 1, - "filterable": 0, - "playurl": "json:https://lziplayer.com/?url=", - "categories": [ - "国产剧", - "国产动漫", - "泰国剧", - "台湾剧", - "香港剧", - "欧美剧", - "韩国剧", - "日本剧", - "动漫", - "体育", - "剧情片", - "动作片", - "爱情片", - "喜剧片" - ] - }, { "key": "API_如意", "name": "🐬如意影视[追剧]", @@ -802,7 +618,7 @@ }, { "key": "MiFun", - "name": "🐬MiFun动漫.py", + "name": "🐬MiFun动漫.py[动漫]", "type": 3, "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/MiFun动漫.py" }, @@ -998,6 +814,12 @@ "type": 3, "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/BadNews.py" }, + { + "key": "樱花传媒", + "name": "🐬樱花传媒.py|🔞[成人]", + "type": 3, + "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/樱花传媒.py" + }, { "key": "麻豆社", "name": "🐬麻豆社.py|🔞[成人]", @@ -1172,68 +994,6 @@ "type": 3, "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/麻豆免费在线播放.py" }, - {"key": "slyzy", "name": "🐬湿乐园|🔞[成人]", "type": 1, "api": "https://xxavs.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "nxxzy", "name": "🐬奶香香|🔞[成人]", "type": 1, "api": "https://naixxzy.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "无尽资源", "name": "🐬无尽资源|🔞[成人]", "type": 1, "api": "https://api.wujinapi.net/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "玉兔资源", "name": "🐬玉兔資源|🔞[成人]", "type": 1, "api": "https://apiyutu.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "精品资源", "name": "🐬精品資源|🔞[成人]", "type": 1, "api": "https://www.jingpinx.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "夜色资源", "name": "🐬越南資源|🔞[成人]", "type": 1, "api": "https://www.vnzyz.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "辣椒資源", "name": "🐬辣椒資源|🔞[成人]", "type": 1, "api": "http://apilj.com/api.php/provide/vod/at/json/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "鯊魚資源", "name": "🐬鯊魚資源|🔞[成人]", "type": 1, "api": "https://shayuapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "155資源", "name": "🐬155資源|🔞[成人]", "type": 1, "api": "https://155api.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "滴滴资源", "name": "🐬滴滴资源|🔞[成人]", "type": 1, "api": "https://api.ddapi.cc/api.php/provide/vod/at/json", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "蕃茄", "name": "🐬18+蕃茄|🔞[成人]", "type": 1, "api": "https://fqzy.me//api.php/provide/vod/", "searchable": 1, "quickSearch": 0, "filterable": 1}, - {"key": "红桃🍑", "name": "🐬红桃视频🍑|🔞[成人]", "type": 1, "api": "https://apidanaizi.com/api.php/provide/vod/?ac=list", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "jkun资源", "name": "🐬jkun资源|🔞[成人]", "type": 1, "api": "https://jkunzyapi.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "精品X资源", "name": "🐬精品X资源|🔞[成人]", "type": 1, "api": "https://www.jingpinx.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "CK资源", "name": "🐬CK资源|🔞[成人]", "type": 1, "api": "https://ckzy.me/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "🔞七天资源🌈", "name": "🐬七天资源🌈|🔞[成人]", "type": 1, "api": "https://8day.icu/api.php/provide/vod/at/json", "searchable": 1, "quickSearch": 1, "filterable": 0}, - { - "key": "百万", - "name": "🐬百万|🔞[成人]", - "type": 1, - "api": "https://api.bwzyz.com/api.php/provide/vod/at/json", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "鸡坤", - "name": "🐬鸡坤|🔞[成人]", - "type": 1, - "api": "https://jkunzyapi.com/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key":"MissAV", - "name":"🐬MissAV🔞[成人]", - "type":3, - "api":"csp_MissAV", - "homePage":"https://fgblh.github.io/uhuj.github.io/missav.html" - }, - { - "key":"WhosTV", - "name":"🐬WhosTV┃番号🔞[成人]", - "type":3, - "api":"csp_MissAV", - "homePage":"https://fgblh.github.io/uhuj.github.io/WhosTV.html" - }, - { - "key": "*AIvin", - "name": "🐬AIvin|🔞[成人]", - "type": 0, - "api": "http://lbapiby.com/api.php/provide/vod/at/xml" - }, - { - "key": "香蕉资源", - "name": "🐬香蕉资源🔞[成人]", - "type": 0, - "api": "https://www.xiangjiaozyw.com/api.php/provide/vod/at/xml/", - "searchable": 1, - "quickSearch": 0 - }, { "key": "大地采集", "name": "🐬大地av|🔞[成人]", @@ -1244,80 +1004,6 @@ "quickSearch": 1, "filterable": 0 }, - { - "key": "*番号资源", - "name": "🐬番号资源|🔞[成人]", - "type": 1, - "api": "http://fhapi9.com/api.php/provide/vod/", - "searchable": 0, - "quickSearch": 0, - "filterable": 0 - }, - { - "key": "黄色仓库|AV", - "name": "🐬黄色仓库(旧)|🔞[成人]", - "type": 1, - "api": "https://hsckzy.vip/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "桃花资源|AV", - "name": "🐬桃花资源|🔞[成人]", - "type": 1, - "api": "https://thzy1.me/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "乐播资源|AV", - "name": "🐬乐播资源|🔞[成人]", - "type": 1, - "api": "https://lbapi9.com/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "fqzy资源", - "name": "🐬国产资源|🔞[成人]", - "type": 1, - "api": "https://fqzy.me//api.php/provide/vod/?ac=list", - "searchable": 1, - "changeable": 1, - "categories": [ - "黑料网曝", - "国产大制作", - "偷拍自拍", - "高清无码", - "乱伦毁三观", - "淫乱学生", - "主播女网红", - "高清有码", - "中文字幕", - "动漫精选", - "欧美精品" - ] - }, - { - "key": "lsb资源|AV", - "name": "🐬lsb资源|🔞[成人]", - "type": 1, - "api": "https://apilsbzy1.com/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "91", - "name": "🐬传媒系列|🔞[成人](梯子)", - "type": 1, - "api": "https://91md.me/api.php/provide/vod/", - "searchable": 1, - "changeable": 1 - }, { "key": "豆瓣", "name": "豆瓣|首页[追剧]", diff --git a/FGBLH/Web鱼壳海豚py.json b/FGBLH/Web鱼壳海豚py.json index 21b1a0b4..de45a594 100644 --- a/FGBLH/Web鱼壳海豚py.json +++ b/FGBLH/Web鱼壳海豚py.json @@ -551,6 +551,12 @@ "type": 3, "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/采集聚合成人.py" }, + { + "key": "樱花传媒", + "name": "🐬樱花传媒.py|🔞[成人]", + "type": 3, + "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/樱花传媒.py" + }, { "key": "BadNews", "name": "🐬BadNews.py|🔞[成人](挂梯)", diff --git a/FGBLH/ok海豚.json b/FGBLH/ok海豚.json index 6ce52539..31e738e9 100644 --- a/FGBLH/ok海豚.json +++ b/FGBLH/ok海豚.json @@ -483,53 +483,13 @@ "type": 3, "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/油管直播.py" }, - {"key": "aidan", "name": "🐬艾旦影视(聚)(有三级片)🔞", "type": 1, "api": "https://lovedan.net/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "ikun", "name": "🐬爱坤┃影视(有三级片)", "type": 1, "api": "https://ikunzyapi.com/api.php/provide/vod/from/ikm3u8/at/json", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "U酷資源", "name": "🐬U酷資源(有三级片)", "type": 1, "api": "https://api.ukuapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "虎牙资源", "name": "🐬虎牙资源(有三级片)", "type": 1, "api": "https://www.huyaapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "ttzy", "name": "🐬天天┃影视(有三级片)🔞", "type": 1, "api": "https://api.1080zyku.com/inc/apijson.php", "searchable": 1, "quickSearch": 1, "filterable": 1}, - { - "key": "非凡", - "name": "🐬非凡影视", - "type": 1, - "api": "http://cj.ffzyapi.com/api.php/provide/vod/", - "searchable": 1, - "changeable": 1, - "categories": [ - "国产动漫", - "日韩动漫", - "国产剧", - "韩国剧", - "日本剧", - "电影片", - "连续剧", - "综艺片", - "动漫片", - "动作片", - "喜剧片", - "爱情片", - "科幻片", - "恐怖片", - "剧情片", - "战争片", - "香港剧", - "欧美剧", - "记录片", - "台湾剧", - "海外剧", - "泰国剧", - "大陆综艺", - "港台综艺", - "日韩综艺", - "欧美综艺", - "欧美动漫", - "港台动漫", - "海外动漫" - ] - }, + {"key": "aidan", "name": "🐬艾旦影视┃伦理🔞", "type": 1, "api": "https://lovedan.net/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, + {"key": "U酷資源", "name": "🐬U酷資源┃伦理", "type": 1, "api": "https://api.ukuapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, + {"key": "虎牙资源", "name": "🐬虎牙资源┃伦理", "type": 1, "api": "https://www.huyaapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, + {"key": "ttzy", "name": "🐬天天影视┃伦理🔞", "type": 1, "api": "https://api.1080zyku.com/inc/apijson.php", "searchable": 1, "quickSearch": 1, "filterable": 1}, { "key": "天涯资源", - "name": "🐬天涯资源(有三级片)", + "name": "🐬天涯资源┃伦理", "type": 0, "api": "https://tyyszyapi.com/api.php/provide/vod/at/xml/?ac=list", "searchable": 1, @@ -537,156 +497,12 @@ }, { "key": "iKun资源", - "name": "🐬iKun资源(有三级片)", + "name": "🐬iKun资源┃伦理", "type": 0, "api": "https://ikunzyapi.com/api.php/provide/vod/at/xml", "searchable": 1, "quickSearch": 1 }, - { - "key": "闪电影视", - "name": "🐬闪电影视|追剧", - "type": 1, - "api": "http://sdzyapi.com/api.php/provide/vod/", - "searchable": 1, - "quickSearch": 1, - "categories": [ - "国产剧", - "短剧", - "韩国剧", - "香港剧", - "台湾剧", - "欧美剧", - "动作片", - "科幻片", - "战争片", - "奇幻片", - "喜剧片", - "爱情片", - "恐怖片", - "犯罪片", - "悬疑片", - "惊悚片", - "剧情片", - "冒险片", - "记录片", - "动作片", - "日本剧", - "泰剧", - "国产综艺", - "港台综艺", - "欧美综艺", - "日韩综艺", - "国产动漫", - "港台动漫", - "日韩动漫" - ] - }, - { - "key": "牛牛影视", - "name": "🐬牛牛影视|追剧", - "type": 0, - "api": "https://api.niuniuzy.me/api.php/provide/vod/at/xml", - "searchable": 1, - "quickSearch": 1, - "categories": [ - "动作片", - "喜剧片", - "爱情片", - "科幻片", - "恐怖片", - "剧情片", - "战争片", - "动画片", - "纪录片", - "预告片", - "邵氏电影", - "4K电影", - "国产剧", - "欧美剧", - "韩剧", - "日剧", - "港剧", - "台剧", - "泰剧", - "海外剧", - "国产动漫", - "日韩动漫", - "欧美动漫", - "港台动漫", - "海外动漫", - "影视解说", - "爽文短剧", - "有声动漫", - "女频恋爱", - "反转爽剧", - "古装仙侠", - "年代穿越", - "脑洞悬疑", - "现代都市", - "演唱会", - "大陆综艺", - "日韩综艺", - "港台综艺", - "欧美综艺", - "篮球", - "足球" - ] - }, - { - "key": "雲飛影视", - "name": "🐬雲飛影视|追剧", - "type": 1, - "api": "http://cj.lziapi.com/api.php/provide/vod/", - "searchable": 1, - "quickSearch": 1, - "filterable": 0, - "playurl": "json:https://lziplayer.com/?url=", - "categories": [ - "国产剧", - "国产动漫", - "泰国剧", - "小湾剧", - "香港剧", - "欧美剧", - "韩国剧", - "日本剧", - "动漫", - "体育", - "加片", - "动作片", - "爱情片", - "喜剧片" - ] - }, -{"key": "火狐","name": "🐬火狐影视🦊|追剧","type": 1,"api": "https://hhzyapi.com/api.php/provide/vod/","searchable": 1,"quickSearch": 0,"filterable": 1,"categories": [ "内地剧", "动作片", "科幻片", "战争片", "喜剧片", "爱情片", "恐怖片", "犯罪片", "剧情片", "冒险片", "记录片", "韩剧", "香港剧", "台湾剧", "欧美剧", "日剧", "马泰剧", "体育赛事", "综艺", "动画片", "中国动漫", "日本动漫", "欧美动漫"] -}, - { - "key": "量子2k", - "name": "🐬量子影视|追剧", - "type": 1, - "api": "http://cj.lziapi.com/api.php/provide/vod/", - "searchable": 1, - "quickSearch": 1, - "filterable": 0, - "playurl": "json:https://lziplayer.com/?url=", - "categories": [ - "国产剧", - "国产动漫", - "泰国剧", - "台湾剧", - "香港剧", - "欧美剧", - "韩国剧", - "日本剧", - "动漫", - "体育", - "剧情片", - "动作片", - "爱情片", - "喜剧片" - ] - }, { "key": "API_如意", "name": "🐬如意影视|追剧", @@ -1117,54 +933,6 @@ "type": 3, "api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/麻豆免费在线播放.py" }, - {"key": "slyzy", "name": "🐬湿乐园|🔞", "type": 1, "api": "https://xxavs.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "nxxzy", "name": "🐬奶香香|🔞", "type": 1, "api": "https://naixxzy.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "无尽资源", "name": "🐬无尽资源|🔞", "type": 1, "api": "https://api.wujinapi.net/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "玉兔资源", "name": "🐬玉兔資源|🔞", "type": 1, "api": "https://apiyutu.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "精品资源", "name": "🐬精品資源|🔞", "type": 1, "api": "https://www.jingpinx.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "夜色资源", "name": "🐬越南資源|🔞", "type": 1, "api": "https://www.vnzyz.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "辣椒資源", "name": "🐬辣椒資源|🔞", "type": 1, "api": "http://apilj.com/api.php/provide/vod/at/json/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "鯊魚資源", "name": "🐬鯊魚資源|🔞", "type": 1, "api": "https://shayuapi.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "155資源", "name": "🐬155資源|🔞", "type": 1, "api": "https://155api.com/api.php/provide/vod/", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "滴滴资源", "name": "🐬滴滴资源|🔞", "type": 1, "api": "https://api.ddapi.cc/api.php/provide/vod/at/json", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "蕃茄", "name": "🐬18+蕃茄|🔞", "type": 1, "api": "https://fqzy.me//api.php/provide/vod/", "searchable": 1, "quickSearch": 0, "filterable": 1}, - {"key": "红桃🍑", "name": "🐬红桃视频🍑|🔞", "type": 1, "api": "https://apidanaizi.com/api.php/provide/vod/?ac=list", "searchable": 1, "quickSearch": 1, "filterable": 1}, - {"key": "jkun资源", "name": "🐬jkun资源|🔞", "type": 1, "api": "https://jkunzyapi.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "精品X资源", "name": "🐬精品X资源|🔞", "type": 1, "api": "https://www.jingpinx.com/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "CK资源", "name": "🐬CK资源|🔞", "type": 1, "api": "https://ckzy.me/api.php/provide/vod", "searchable": 1, "quickSearch": 1, "filterable": 0}, - {"key": "🔞七天资源🌈", "name": "🐬七天资源🌈|🔞", "type": 1, "api": "https://8day.icu/api.php/provide/vod/at/json", "searchable": 1, "quickSearch": 1, "filterable": 0}, - { - "key": "百万", - "name": "🐬百万|🔞", - "type": 1, - "api": "https://api.bwzyz.com/api.php/provide/vod/at/json", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "鸡坤", - "name": "🐬鸡坤|🔞", - "type": 1, - "api": "https://jkunzyapi.com/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "*AIvin", - "name": "🐬AIvin|🔞", - "type": 0, - "api": "http://lbapiby.com/api.php/provide/vod/at/xml" - }, - { - "key": "香蕉资源", - "name": "🐬香蕉资源🔞", - "type": 0, - "api": "https://www.xiangjiaozyw.com/api.php/provide/vod/at/xml/", - "searchable": 1, - "quickSearch": 0 - }, { "key": "大地采集", "name": "🐬大地av|🔞", @@ -1174,81 +942,7 @@ "searchable": 1, "quickSearch": 1, "filterable": 0 - }, - { - "key": "*番号资源", - "name": "🐬番号资源|🔞", - "type": 1, - "api": "http://fhapi9.com/api.php/provide/vod/", - "searchable": 0, - "quickSearch": 0, - "filterable": 0 - }, - { - "key": "黄色仓库|AV", - "name": "🐬黄色仓库(旧)|🔞", - "type": 1, - "api": "https://hsckzy.vip/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "桃花资源|AV", - "name": "🐬桃花资源|🔞", - "type": 1, - "api": "https://thzy1.me/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "乐播资源|AV", - "name": "🐬乐播资源|🔞", - "type": 1, - "api": "https://lbapi9.com/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "fqzy资源", - "name": "🐬国产资源|🔞", - "type": 1, - "api": "https://fqzy.me//api.php/provide/vod/?ac=list", - "searchable": 1, - "changeable": 1, - "categories": [ - "黑料网曝", - "国产大制作", - "偷拍自拍", - "高清无码", - "乱伦毁三观", - "淫乱学生", - "主播女网红", - "高清有码", - "中文字幕", - "动漫精选", - "欧美精品" - ] - }, - { - "key": "lsb资源|AV", - "name": "🐬lsb资源|🔞", - "type": 1, - "api": "https://apilsbzy1.com/api.php/provide/vod/", - "quickSearch": 1, - "searchable": 1, - "filterable": 0 - }, - { - "key": "91", - "name": "🐬传媒系列|🔞(梯子)", - "type": 1, - "api": "https://91md.me/api.php/provide/vod/", - "searchable": 1, - "changeable": 1 - } + } ], "parses": [ { diff --git a/FGBLH/py/采集聚合.py b/FGBLH/py/采集聚合.py index 82448df8..724f6e85 100644 --- a/FGBLH/py/采集聚合.py +++ b/FGBLH/py/采集聚合.py @@ -16,17 +16,17 @@ class Spider(Spider): 's3': {'name': '🧸量子', 'api': 'https://cj.lziapi.com/api.php/provide/vod'}, 's4': {'name': '📺1080资源', 'api': 'https://api.1080zyku.com/inc/api_mac10.php'}, 's5': {'name': '🔥155资源', 'api': 'https://155api.com/api.php/provide/vod'}, - 's6': {'name': '📺天涯', 'api': 'https://tyyszy.com/api.php/provide/vod'}, - 's7': {'name': '📺暴风', 'api': 'https://bfzyapi.com/api.php/provide/vod'}, - 's8': {'name': '⚡索尼闪电', 'api': 'https://xsd.sdzyapi.com/api.php/provide/vod'}, - 's9': {'name': '📺索尼', 'api': 'https://suoniapi.com/api.php/provide/vod'}, - 's10': {'name': '📺红牛', 'api': 'https://www.hongniuzy2.com/api.php/provide/vod'}, - 's11': {'name': '📺茅台', 'api': 'https://caiji.maotaizy.cc/api.php/provide/vod'}, - 's12': {'name': '🐯虎牙', 'api': 'https://www.huyaapi.com/api.php/provide/vod'}, - 's13': {'name': '📺豆瓣', 'api': 'https://caiji.dbzy.tv/api.php/provide/vod'}, - 's14': {'name': '📺豆瓣2', 'api': 'https://dbzy.tv/api.php/provide/vod'}, - 's15': {'name': '📺豪华', 'api': 'https://hhzyapi.com/api.php/provide/vod'}, - 's16': {'name': '📺CK资源', 'api': 'https://ckzy.me/api.php/provide/vod'}, + 's6': {'name': '📺天涯', 'api': 'https://tyyszy.com/api.php/provide/vod/'}, + 's7': {'name': '📺暴风', 'api': 'https://bfzyapi.com/api.php/provide/vod/'}, + 's8': {'name': '⚡索尼闪电', 'api': 'https://xsd.sdzyapi.com/api.php/provide/vod/'}, + 's9': {'name': '📺索尼', 'api': 'https://suoniapi.com/api.php/provide/vod/'}, + 's10': {'name': '📺红牛', 'api': 'https://www.hongniuzy2.com/api.php/provide/vod/'}, + 's11': {'name': '📺茅台', 'api': 'https://caiji.maotaizy.cc/api.php/provide/vod/'}, + 's12': {'name': '🐯虎牙', 'api': 'https://www.huyaapi.com/api.php/provide/vod/'}, + 's13': {'name': '📺豆瓣', 'api': 'https://caiji.dbzy.tv/api.php/provide/vod/'}, + 's14': {'name': '📺豆瓣2', 'api': 'https://dbzy.tv/api.php/provide/vod/'}, + 's15': {'name': '📺豪华', 'api': 'https://hhzyapi.com/api.php/provide/vod/'}, + 's16': {'name': '📺如意影视', 'api': 'https://cj.rycjapi.com/api.php/provide/vod/'}, 's17': {'name': '📺U酷', 'api': 'https://api.ukuapi.com/api.php/provide/vod'}, 's18': {'name': '📺ikun', 'api': 'https://ikunzyapi.com/api.php/provide/vod'}, 's19': {'name': '📺无尽', 'api': 'https://api.wujinapi.cc/api.php/provide/vod'}, @@ -47,6 +47,7 @@ class Spider(Spider): 's34': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'}, 's35': {'name': '📺西瓜资源', 'api': 'https://caiji.xgzyapi.com/api.php/provide/vod/'}, 's36': {'name': '📺98资源', 'api': 'https://98zy.vip/api.php/provide/vod/'}, + 's37': {'name': '🦊火狐影视', 'api': 'https://hhzyapi.com/api.php/provide/vod/'}, } headers = { diff --git a/FGBLH/py/采集聚合成人.py b/FGBLH/py/采集聚合成人.py index 6ba178b5..6e7fdee3 100644 --- a/FGBLH/py/采集聚合成人.py +++ b/FGBLH/py/采集聚合成人.py @@ -34,6 +34,13 @@ class Spider(Spider): 's21': {'name': '番号资源', 'api': 'http://fhapi9.com/api.php/provide/vod/'}, 's22': {'name': '精品X资源', 'api': 'https://www.jingpinx.com/api.php/provide/vod/'}, 's23': {'name': 'jkun资源', 'api': 'https://jkunzyapi.com/api.php/provide/vod/'}, + 's24': {'name': '湿乐园', 'api': 'https://xxavs.com/api.php/provide/vod/'}, + 's25': {'name': '越南资源', 'api': 'https://www.vnzyz.com/api.php/provide/vod/'}, + 's26': {'name': '红桃视频', 'api': 'https://apidanaizi.com/api.php/provide/vod/'}, + 's27': {'name': '鸡坤资源', 'api': 'https://jkunzyapi.com/api.php/provide/vod/'}, + 's28': {'name': 'AIvin', 'api': 'http://lbapiby.com/api.php/provide/vod/'}, + 's29': {'name': 'lsb资源', 'api': 'https://apilsbzy1.com/api.php/provide/vod/'}, + 's30': {'name': '91麻豆', 'api': 'https://91md.me/api.php/provide/vod/'}, } headers = { diff --git a/Tomorrow/html/玩偶聚合.html b/Tomorrow/html/玩偶聚合.html new file mode 100644 index 00000000..cb07e0f9 --- /dev/null +++ b/Tomorrow/html/玩偶聚合.html @@ -0,0 +1,3524 @@ + + + + + + +玩偶聚合 WebHome | Tab S9 功能增强完整版 + + + + +
+
+
聚合搜索中…
+
+
+
+
+ +
+
+
+ +
准备加载…
+
+
+ + +
+
+
盘搜设置
+ + +
+ + +
+
+
+ +
+
+ +
+ +
+
+ +
+
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/Tomorrow/html/盘链.html b/Tomorrow/html/盘链.html index 7ba0aa95..ed10b890 100644 --- a/Tomorrow/html/盘链.html +++ b/Tomorrow/html/盘链.html @@ -6,6 +6,21 @@ 盘链 + + + + + + + + + + + + + + /g, ""); } + catch (e) { return html; } +} +function zn4kParseDoc(html) { + try { return new DOMParser().parseFromString(zn4kStripHeavy(html), "text/html"); } catch (e) { return null; } +} +function zn4kDiskType(u) { + for (var i = 0; i < HOSTMAP.length; i++) if (HOSTMAP[i][0].test(u)) return HOSTMAP[i][1]; + if (u.indexOf("magnet:") === 0) return "magnet"; + if (u.indexOf("ed2k:") === 0) return "ed2k"; + if (u.indexOf("thunder:") === 0) return "thunder"; + return "http"; +} +/* 指南列表页/合集页 -> [{id,title,poster,meta}] */ +function zn4kParseList(html) { + var doc = zn4kParseDoc(html); + if (!doc) return []; + var out = [], seen = {}; + function add(id, title, poster, meta) { if (!id || seen[id] || !title) return; seen[id] = 1; out.push({ id: id, title: title, poster: poster, meta: meta || "" }); } + Array.from(doc.querySelectorAll("article.book-item, article.posts-item")).forEach(function (a) { + var link = a.querySelector("a.item-image, .item-title a, a[href*='/book/']"); + if (!link) return; + var m = (link.getAttribute("href") || "").match(/\/book\/(\d+)\.html/); + if (!m) return; + var img = a.querySelector("img"); + var poster = img ? (img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || "") : ""; + if (poster && poster.indexOf(".svg") >= 0 && img.getAttribute("data-src")) poster = img.getAttribute("data-src"); + var tEl = a.querySelector(".item-title a, .item-title, h3 a"); + var title = ((tEl ? tEl.textContent : "") || (img ? img.getAttribute("alt") : "") || "").trim(); + var metaEl = a.querySelector(".text-muted"); + add(m[1], title, poster, metaEl ? metaEl.textContent.trim() : ""); + }); + if (!out.length) { + Array.from(doc.querySelectorAll("a[href*='/book/']")).forEach(function (link) { + var m = (link.getAttribute("href") || "").match(/\/book\/(\d+)\.html/); + if (!m) return; + var scope = link.closest("article, li, .module-item, .public-list-box, .item, .col") || link.parentElement || link; + var img = link.querySelector("img") || (scope ? scope.querySelector("img") : null); + if (!img) return; + var poster = img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || ""; + if (poster && poster.indexOf(".svg") >= 0 && img.getAttribute("data-src")) poster = img.getAttribute("data-src"); + var tEl = scope ? scope.querySelector(".item-title, .title, h3, h2") : null; + var title = ((tEl ? tEl.textContent : "") || img.getAttribute("alt") || link.getAttribute("title") || link.textContent || "").trim(); + add(m[1], title, poster, ""); + }); + } + return out; +} +/* 指南详情页 -> { title, poster, overview, resources:[{url,label,quality,type}] } */ +function zn4kParseDetail(html) { + var doc = zn4kParseDoc(html); + if (!doc) return { title: "", poster: "", overview: "", resources: [] }; + var h1 = doc.querySelector("h1.site-name, .book-info h1, h1"); + var title = h1 ? h1.textContent.trim() : ""; + var cover = doc.querySelector(".book-cover img, .item-media img"); + var poster = cover ? (cover.getAttribute("data-src") || cover.getAttribute("src") || "") : ""; + var descEl = doc.querySelector("meta[name='description']"); + var overview = descEl ? (descEl.getAttribute("content") || "") : ""; + var res = [], seen = {}; + function pushLink(a, quality) { + var url = (a.getAttribute("href") || "").trim(); + if (!url || !/^https?:|^magnet:|^ed2k:|^thunder:/.test(url)) return; + if (seen[url]) return; seen[url] = 1; + var bn = a.querySelector(".b-name"); + var label = ((bn ? bn.textContent : "") || a.getAttribute("title") || "").trim(); + res.push({ url: url, label: label, quality: quality || "", type: zn4kDiskType(url) }); + } + Array.from(doc.querySelectorAll(".mt-n2")).forEach(function (b) { + var q = b.querySelector("p"); + var quality = q ? q.textContent.trim() : ""; + Array.from(b.querySelectorAll(".site-go a[href]")).forEach(function (a) { pushLink(a, quality); }); + }); + if (!res.length) { + Array.from(doc.querySelectorAll(".site-go a[href], .book-down a[href], a.btn[href*='pan.'], a.btn[href*='drive.']")).forEach(function (a) { pushLink(a, ""); }); + } + return { title: title, poster: poster, overview: overview, resources: res }; +} + +/* 指南解析出的资源 -> 盘链网盘条目(只保留 App 能处理的网盘类型 + 磁力) */ +function zn4kResList(resources) { + var out = [], seen = {}; + (resources || []).forEach(function (r) { + var type = r.type; + if (PANSOU_KEEP.indexOf(type) < 0 && type !== "magnet") return; + var url = r.url || ""; + if (!url) return; + var k = type + "|" + normPanUrl(url); + if (seen[k]) return; seen[k] = 1; + var lab = PAN_META[type] ? PAN_META[type].label : (TYPE_LABEL[type] || type); + out.push({ + type: type, url: url, password: "", + title: r.label || "", + groupName: lab, quality: lab, + source: "4K指南", state: "idle", _zn4k: true + }); + }); + return out; +} +/* 详情页里已经并进来的指南资源(刷新/重载时保留,跟 panSouKeep 同理) */ +function zn4kKeep() { return (state.panItems || []).filter(function (it) { return it._zn4k; }); } +/* 把一批指南资源合并进当前详情页网盘列表(去重 + 重建 tab) */ +function zn4kMerge(pans, token) { + if (token !== state.panToken || !pans || !pans.length) return; + var have = {}; + state.panItems.forEach(function (it) { have[it.type + "|" + normPanUrl(it.url || it.token)] = 1; }); + var add = []; + pans.forEach(function (p) { var k = p.type + "|" + normPanUrl(p.url); if (!have[k]) { have[k] = 1; add.push(p); } }); + if (!add.length) return; + renderResources(state.panItems.concat(add)); +} +/* 指南站搜索:/?post_type=book&s=关键词 -> 列表 */ +function zn4kSearch(kw) { + var u = zn4kBase() + "/?post_type=book&s=" + encodeURIComponent(kw); + return zn4kHttpText(u).then(function (html) { return zn4kParseList(html); }, function () { return []; }); +} +/* 从指南搜索结果里挑与本片最匹配的一条(标题相关度优先,年份加权) */ +function zn4kPickBest(list, rawTitle, year) { + var key = cleanTitle(rawTitle); + var best = null, bestScore = 99; + (list || []).forEach(function (it) { + var ts = titleScore(it.title, key); + if (ts > 3) return; // 完全不含关键词的丢掉 + var s = ts; + if (year && ((it.title || "").indexOf(year) >= 0 || (it.meta || "").indexOf(year) >= 0)) s -= 0.3; + if (s < bestScore) { bestScore = s; best = it; } + }); + return best; +} +/* 详情页(盘链原生条目):按标题去指南搜同名,把站内网盘资源并进来 */ +function zn4kEnrich(rawTitle, token) { + var kw = cleanTitle(rawTitle); + if (!kw) return; + var year = yearOf(rawTitle); + zn4kSearch(kw).then(function (list) { + if (token !== state.panToken || !list.length) return null; + var best = zn4kPickBest(list, rawTitle, year); + if (!best) return null; + return zn4kHttpText(zn4kBase() + "/book/" + best.id + ".html").then(function (html) { + if (token !== state.panToken) return; + zn4kMerge(zn4kResList(zn4kParseDetail(html).resources), token); + }); + }).then(null, function () {}); +} +/* 详情页(指南来源条目):已知指南书籍 id,直接取其站内网盘资源 */ +function zn4kLoadById(id, token) { + if (!id) return; + zn4kHttpText(zn4kBase() + "/book/" + id + ".html").then(function (html) { + if (token !== state.panToken) return; + var d = zn4kParseDetail(html); + if (state.currentDetail && !state.currentDetail.tmdb) { + if (d.title) { state.currentDetail.rawTitle = d.title; showTitle(d.title); } + var ov = $("detailOverview"); if (ov && !ov.textContent && !(state.currentDetail && state.currentDetail.tmdb) && d.overview && !isJunkOverview(d.overview)) { ov.textContent = d.overview; state.currentDetail.overviewFromSite = true; } + if (d.poster && !state.currentDetail.poster && !(state.currentDetail && state.currentDetail.tmdb)) { state.currentDetail.poster = d.poster; setDetailHeroImages([d.poster], false); } + } + var pans = zn4kResList(d.resources); + if (pans.length) renderResources(sortPanDefault(pans).concat(panSouKeep())); + }, function () {}); +} +/* 指南来源条目:顺便按标题反查盘链本站,命中就升级成原生条目(走完整资源+反向补指南) */ +function zn4kReverseLookup(item, token) { + var title = item.title, yr = item.year || yearOf(item.title); + apiGet(apiUrl("get_videos.php") + "?wd=" + encodeURIComponent(cleanTitle(title)) + "&pg=1&_t=" + Date.now(), "").then(function (d) { + if (token !== state.panToken || state.currentItem !== item) return; + var list = ((d && (d.list || d.data)) || []).map(mapVod); + function score(it) { var s = 0; if (it.title === title) s += 8; else if (titleCloseContain(it.title, title)) s += 4; if (yr && it.year === yr) s += 3; return s; } + var good = list.filter(function (it) { return score(it) >= 4; }); + if (good.length) openDetail(good.slice().sort(function (a, b) { return score(b) - score(a); })[0]); + }, function () {}); +} + +/* ---------- 首页「合集」(放在首页最下面一行) ---------- */ +function renderZn4kCollections(box) { + var shell = makeRowShell("合集", null); + box.appendChild(shell.section); + var row = shell.row, frag = document.createDocumentFragment(); + ZN4K_COLLECTIONS.forEach(function (c, i) { + var card = document.createElement("button"); + card.type = "button"; card.className = "card focusable"; + card.setAttribute("data-card-index", String(i)); + card.setAttribute("data-focus-row", "row"); + var poster = document.createElement("div"); poster.className = "poster"; + var ph = document.createElement("div"); ph.className = "ph"; ph.textContent = c.name.replace("合集", ""); poster.appendChild(ph); + var badge = document.createElement("span"); badge.className = "badge mono"; badge.textContent = c.paged ? "榜单" : "合集"; poster.appendChild(badge); + card.appendChild(poster); + var title = document.createElement("div"); title.className = "card-title"; title.textContent = c.name; card.appendChild(title); + var meta = document.createElement("div"); meta.className = "card-meta"; meta.textContent = "指南"; card.appendChild(meta); + card.onclick = (function (col) { return function () { openZn4kCollection(col.slug, col.name, col.paged); }; })(c); + frag.appendChild(card); + (function (slug, posterEl, phEl) { rowLoad(shell.section, function () { return zn4kCollectionCover(slug, posterEl, phEl); }); })(c.slug, poster, ph); + }); + row.replaceChildren(frag); +} +/* 首页「豆瓣高分」一行(4K指南 的豆瓣高分 分类,紧跟在 TMDB 热播 下面) */ +function renderZn4kTop250(box) { + var c = ZN4K_TOP250; + var shell = makeRowShell(c.name, function () { openZn4kCollection(c.slug, c.name, c.paged); }); + box.appendChild(shell.section); + var cached = state.zn4kColCache[c.slug]; + var hasCache = !!(cached && cached.length); + if (hasCache) fillRow(shell.row, cached.slice(0, 12), function (it) { openDetail(it); }); + else shell.row.replaceChildren(el("div", "mh-row-empty", "加载中…")); + var load = function () { + if (!document.body.contains(shell.row)) return null; + return zn4kFetchPage(c.slug, 1).then(function (members) { + if (!document.body.contains(shell.row)) return; + if (!members.length) { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "暂无数据")); return; } + state.zn4kColCache[c.slug] = members; saveHomeDisk(); + fillRow(shell.row, members.slice(0, 12), function (it) { openDetail(it); }); + }, function () { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "加载失败")); }); + }; + if (hasCache) { if (homeCacheStale()) homePrefetch(load); return; } // 有缓存:先秒显,旧了才后台刷新 + rowLoad(shell.section, load); +} +/* 抓指南列表页某一页 -> 成员数组(带 _zn4k 标记,点开走指南详情逻辑) */ +function zn4kFetchPage(slug, page) { + return zn4kHttpText(zn4kListUrl(slug, page)).then(function (html) { + return zn4kParseList(html).map(function (m) { m._zn4k = true; m._zn4kId = m.id; return m; }); + }); +} +/* 封面懒加载:用该合集/榜单第一页里首个有海报的成员当封面;第一页结果缓存,点进去时秒开 */ +function zn4kCollectionCover(slug, posterEl, phEl) { + function setCover(members) { + var first = (members || []).filter(function (m) { return m.poster; })[0]; + if (!first || !posterEl) return; + var img = document.createElement("img"); + img.alt = ""; img.loading = "lazy"; img.setAttribute("decoding", "async"); + img.onerror = function () { img.style.display = "none"; }; + img.src = zn4kImgSrc(first.poster); + if (phEl) phEl.style.display = "none"; + posterEl.insertBefore(img, posterEl.firstChild); + } + var cached = state.zn4kColCache[slug]; + if (cached && cached.length) { setCover(cached); return null; } + return zn4kFetchPage(slug, 1).then(function (members) { + state.zn4kColCache[slug] = members; + saveHomeDisk(); + setCover(members); + }, function () {}); +} +/* 打开某个指南合集/榜单:复用「查看全部」分类视图铺影片。 + paged=true(豆瓣高分 这类常规分类页)滑到底会继续翻页;固定名单的合集一页到底。 */ +function openZn4kCollection(slug, name, paged) { + saveHomeReturn(); + state.view = "category"; + $("homeView").style.display = "none"; $("catView").style.display = ""; + $("genreChips").style.display = "none"; $("genreChips").replaceChildren(); + $("catTitle").textContent = name || "合集"; + $("clearSearch").style.display = "none"; $("clearRecent").style.display = "none"; + setNav(null); updateHeroVisible(); window.scrollTo(0, 0); + navigate("category"); + state.searchMode = false; state.searchKw = ""; + state.activeCat = ZN4K_CAT_SLUG; state.activeGenre = ""; + state.zn4kCol = { slug: slug, name: name, paged: !!paged, page: 1, fetching: false }; + state.reqToken++; var token = state.reqToken; + state.source = []; state.rendered = 0; state.noMore = !paged; state.loading = false; state.autoPages = 0; + var grid = $("grid"); grid.replaceChildren(); grid.classList.remove("grid-recent"); + $("browseNotice").replaceChildren(); + $("browseSub").textContent = "加载中"; + $("browseLoading").style.display = "block"; + var cached = state.zn4kColCache[slug]; + if (cached) { zn4kShowCollection(cached, token); return; } + zn4kFetchPage(slug, 1).then(function (members) { + if (token !== state.reqToken) return; + state.zn4kColCache[slug] = members; + zn4kShowCollection(members, token); + }, function () { + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + $("browseSub").textContent = ""; + $("grid").replaceChildren(el("div", "mh-row-empty", "加载失败,返回重试")); + }); +} +function zn4kShowCollection(members, token) { + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + state.source = (members || []).slice(); state.rendered = 0; + $("grid").replaceChildren(); + if (!state.source.length) { $("grid").replaceChildren(el("div", "mh-row-empty", "该分类暂无内容")); $("browseSub").textContent = ""; state.noMore = true; return; } + appendGrid(); + $("browseSub").textContent = state.source.length + " 部"; +} +/* 指南榜单(豆瓣高分)触底续页:由 loadList(false) 转发过来 */ +function zn4kLoadMore() { + var c = state.zn4kCol; + if (!c || !c.paged || c.fetching || state.noMore) return; + c.fetching = true; state.loading = true; + var token = state.reqToken, page = c.page + 1; + $("browseLoading").style.display = "block"; + zn4kFetchPage(c.slug, page).then(function (list) { + c.fetching = false; state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + var have = {}; state.source.forEach(function (s) { have[s.id] = 1; }); + var fresh = (list || []).filter(function (it) { return !have[it.id]; }); + if (!fresh.length) { state.noMore = true; return; } + c.page = page; + state.source = state.source.concat(fresh); + appendGrid(); + $("browseSub").textContent = state.source.length + " 部"; + }, function () { + c.fetching = false; state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + state.noMore = true; + }); +} + +/* ============================================================ + 七味网(qmp4.com)集成 —— 完全仿照上面 4K指南 的接法: + 把该站的搜索接口 + 站内下载资源(磁力 + 夸克/迅雷/百度等网盘)接进来。 + - 详情页:按标题去七味搜同名,解析下载区,把网盘/磁力并进「网盘资源」, + 不在搜索结果里单独列站源(同 zn4kEnrich)。 + - 搜索:并发搜七味,只有盘链、指南都没有的片子才补一张七味来源卡片。 + - 首页:新增「七味」一行(数据来自七味电影分类第一页,可翻页)。 + ============================================================ */ +var QW_DEFAULT_BASE = "https://www.qwmp4.com"; // 默认用可直连的亚太镜像(官方永久域名 qmp4.com 需科学上网,不适合做默认) +/* 已知镜像域名(发布页公布的亚太/全球线路),主域连不上时按顺序自动切换,命中后记在 state.qwGoodBase。 + 这类站换域名很勤,把镜像都列上,任一存活就能用;用户也可在设置里手动指定「七味站点」。 */ +var QW_MIRRORS = [ + "https://www.qwmp4.com", "https://www.qwmkv.com", "https://www.qwfilm.com", + "https://www.qwavi.com", "https://www.qwfun.com", + "https://www.qnmp4.com", "https://www.qnnull.com", "https://www.qnhot.com", + "https://www.qncool.com", "https://www.qwfree.com", "https://www.qmp4.com" +]; +/* 用户配置/默认的「首选」域名(设置里填了就用填的) */ +function qwPreferredBase() { return trimSlash(config.qwBase || QW_DEFAULT_BASE); } +/* 当前生效域名:优先用已探测到能用的镜像(state.qwGoodBase),否则用首选。 + URL 构造和图片 Referer 都走它,保证请求域名和 Referer 一致。 */ +function qwBase() { return trimSlash(state.qwGoodBase || qwPreferredBase()); } +function qwHost() { return qwBase().replace(/^https?:\/\//, "").split("/")[0]; } +function isQwUrl(u) { var h = qwHost(); return !!(u && h && String(u).indexOf(h) >= 0); } +/* 七味海报放在 vvmp4 图床上,带防盗链,判定时把图床也算作七味的图 */ +function isQwPoster(u) { return isQwUrl(u) || /vvmp4\.com/.test(String(u || "")); } +var QW_UA = ZN4K_UA; // 跟指南同一套移动端 UA +var QW_CAT_SLUG = "__qwcat__"; // 七味分类在分类视图里的内部 slug +/* 七味顶级分类(对应 /vt/{id}.html):电影1 / 剧集2 / 综艺3 / 动漫4 / 短剧30 */ +var QW_CATS = [ + { t: "1", name: "电影" }, { t: "2", name: "剧集" }, { t: "3", name: "综艺" }, + { t: "4", name: "动漫" }, { t: "30", name: "短剧" } +]; +/* 列表页 URL:第 1 页 /vt/{cat}.html,后续 /vt/{cat}-{page}.html */ +function qwListUrl(cat, page) { + var base = qwBase(); + return (page > 1) ? (base + "/vt/" + cat + "-" + page + ".html") : (base + "/vt/" + cat + ".html"); +} +/* 七味搜索:GET 表单 action=/vs/-------------.html,关键词走 ?wd= */ +function qwSearchUrl(kw) { return qwBase() + "/vs/-------------.html?wd=" + encodeURIComponent(kw); } +function qwDetailUrl(id) { return qwBase() + "/mv/" + id + ".html"; } + +/* 候选域名列表:上次成功的 -> 配置/默认首选 -> 其余镜像(去重) */ +function qwCandidateBases() { + var out = [], seen = {}; + function add(b) { b = trimSlash(b || ""); if (b && !seen[b]) { seen[b] = 1; out.push(b); } } + add(state.qwGoodBase); add(config.qwBase); add(QW_DEFAULT_BASE); + QW_MIRRORS.forEach(add); + return out; +} +function qwPath(url) { return String(url || "").replace(/^https?:\/\/[^/]+/, "") || "/"; } +/* 抓一次七味页面:从候选域名里逐个试,返回第一个"看着像七味页面"的结果, + 并把命中的域名记进 state.qwGoodBase(之后 URL/Referer 都用它,不再逐个试)。 */ +function qwFetchOne(base, path) { + var full = base + path; + if (state.sdkReady && window.fm && fm.req) { + return fm.req(full, { method: "GET", responseType: "text", timeout: 12, headers: { "User-Agent": QW_UA, "Referer": base + "/" } }) + .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.body; }); + } + return fetchTimeout(full, { headers: {} }).then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); }); +} +function qwLooksValid(html) { + // 旧版要求页面里出现 '/mv/'、'pic-list'、'down-link' 或 '七味' 这几个正向标记才算"像七味页面"—— + // 但一次真正的搜索如果关键词在该站确实 0 条匹配,结果页往往只是个空的 + // 或者干脆不渲染这几个 class/文案,导致这条检测把"域名没问题、只是没搜到"误判成"域名失效", + // 从而白白把 11 个镜像域名挨个试一遍(每个最多等 12 秒),要么把这次搜索拖到没意义, + // 要么真凑巧所有镜像的空结果页都通不过检测,最终整条搜索直接返回空。 + // 改成排除法:只要响应不是空/太短、也不是停放页/跳转提示/人机校验页,就认为域名可用。 + if (!html || html.length < 200) return false; + if (/该域名(已|可)(出售|转让)|域名过期|停止解析|正在跳转|即将跳转|window\.location\s*=|_guard\/|jsl_clearance|检测中|请稍候|访问异常|异常流量|人机验证|安全验证|访问验证|验证码|captcha|access denied|forbidden|频繁访问|非法访问|请开启\s*(javascript|cookie)|enable\s*javascript|bot\s*detect|blocked|unusual traffic/i.test(html)) return false; + return true; +} +/* 七味排障开关:打开后搜索完在屏幕底部弹一个可点关闭的浮层,列出每个候选域名的请求结果 + (字节数/判定结果),用来分辨到底是"域名连不上""域名连上了但判定成无效""还是解析出 0 条"。 + 排查完记得关回 false。 */ +var QW_SEARCH_DIAG = false; +function qwSDiag(line) { + if (!QW_SEARCH_DIAG) return; + if (!state.qwDiagList) state.qwDiagList = []; + if (state.qwDiagList.length < 30) state.qwDiagList.push(line); +} +function qwSDiagShow() { + if (!QW_SEARCH_DIAG) return; + var box = document.getElementById("qwSDiagBox"); + if (!box) { + box = document.createElement("div"); + box.id = "qwSDiagBox"; + box.style.cssText = "position:fixed;left:8px;right:8px;bottom:96px;z-index:70;max-height:44vh;overflow:auto;" + + "padding:10px 12px;border-radius:12px;background:rgba(10,14,24,.94);color:#cfe0ff;font-size:11px;" + + "line-height:1.5;white-space:pre-wrap;text-align:left;box-shadow:0 8px 30px rgba(0,0,0,.5);" + + "border:1px solid rgba(160,190,255,.22);-webkit-user-select:text;user-select:text;"; + box.addEventListener("click", function () { box.style.display = "none"; }); + document.body.appendChild(box); + } + box.textContent = "【七味搜索诊断】(点一下关闭)\n" + (state.qwDiagList || []).join("\n"); + box.style.display = "block"; +} + +/* 七味站请求:不带盘链 Cookie,只用浏览器 UA + 七味自身 Referer;带镜像自动切换。 + 给整条镜像级联一个总预算,别让一串连不上的镜像把这次抓取拖到失去意义。 */ +function qwSnippet(t) { return String(t == null ? "" : t).replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200); } +function qwHttpText(url) { return httpTextMemo("qw|" + url, function () { return qwHttpTextRaw(url); }); } +function qwHttpTextRaw(url) { + var cands = qwCandidateBases(), path = qwPath(url); + function tryAt(i) { + if (i >= cands.length) { qwSDiag("——— 全部 " + cands.length + " 个镜像都失败/判定无效"); return Promise.reject(new Error("qw: all mirrors failed")); } + return qwFetchOne(cands[i], path).then(function (html) { + var ok = qwLooksValid(html); + // 字节数偏小(<4000)时多半是拦截页/验证页而不是真的搜索结果页,把去标签后的文字片段打出来, + // 不管判定成有效还是无效都打,避免"判定有效"掩盖了它其实是页错误页这件事。 + qwSDiag(cands[i] + "\n → " + (html ? html.length + " 字节" : "空") + (ok ? " ✓ 判定有效" : " ✕ 判定无效,换下一个") + + ((html && html.length < 4000) ? ("\n 内容片段:" + qwSnippet(html)) : "")); + if (ok) { state.qwGoodBase = cands[i]; return html; } // 记住可用镜像 + return tryAt(i + 1); // landing/空页:换下一个 + }, function (e) { + qwSDiag(cands[i] + "\n → 请求失败:" + ((e && e.message) || "未知")); + return tryAt(i + 1); + }); + } + if (path.indexOf("/vs/") === 0) state.qwDiagList = []; // 每次新搜索前清空诊断缓冲 + var p = tryAt(0); + if (path.indexOf("/vs/") === 0) p = p.then(function (html) { + var n = qwParseList(html).length; + qwSDiag("———\n最终用的响应解析出 " + n + " 条"); + qwSDiagShow(); + return html; + }, function (e) { qwSDiag("———\n最终失败:" + ((e && e.message) || "未知")); qwSDiagShow(); throw e; }); + return qwWithBudget(p, 20000); +} +/* 给镜像级联一个总时间预算:到点还没个结果,直接判失败,不管还剩几个镜像没试完 + (不像 woWithBudget 那样"超时就当空结果",这里要保留 reject,好让上层区分 + "真的搜不到"和"没来得及搜完",避免把后者误当成前者缓存下来)。 */ +function qwWithBudget(promise, ms) { + return new Promise(function (resolve, reject) { + var done = false; + var timer = setTimeout(function () { if (!done) { done = true; reject(new Error("qw: budget exceeded")); } }, ms); + promise.then( + function (v) { if (!done) { done = true; clearTimeout(timer); resolve(v); } }, + function (e) { if (!done) { done = true; clearTimeout(timer); reject(e); } } + ); + }); +} +/* 七味图片要带七味 Referer(防盗链) */ + +function qwImgSrc(url) { + if (!url) return ""; + if (state.sdkReady && window.fm && fm.res) return fm.res(url, { headers: { "Referer": qwBase() + "/" } }); + return url; +} +function qwStripHeavy(html) { + try { return String(html).replace(//gi, "").replace(//gi, "").replace(//g, ""); } + catch (e) { return html; } +} +function qwParseDoc(html) { + try { return new DOMParser().parseFromString(qwStripHeavy(html), "text/html"); } catch (e) { return null; } +} +function qwDiskType(u) { + for (var i = 0; i < HOSTMAP.length; i++) if (HOSTMAP[i][0].test(u)) return HOSTMAP[i][1]; + if (u.indexOf("magnet:") === 0) return "magnet"; + if (u.indexOf("ed2k:") === 0) return "ed2k"; + if (u.indexOf("thunder:") === 0) return "thunder"; + return "http"; +} +/* 列表/搜索结果页 -> [{id,title,poster,meta,year}] + 七味卡片结构:ul.pic-list > li > a[href*='/mv/'] > article>img[alt] + h3>b(标题) + p(年份/地区/类型) */ +function qwParseList(html) { + var doc = qwParseDoc(html); + if (!doc) return []; + var out = [], seen = {}; + function add(id, title, poster, meta, year) { if (!id || seen[id] || !title) return; seen[id] = 1; out.push({ id: id, title: title, poster: poster, meta: meta || "", year: year || "" }); } + Array.from(doc.querySelectorAll(".pic-list li a[href*='/mv/'], ul.pic-list li a[href*='/mv/']")).forEach(function (a) { + var m = (a.getAttribute("href") || "").match(/\/mv\/(\d+)\.html/); + if (!m) return; + var img = a.querySelector("img"); + var poster = img ? (img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || "") : ""; + var tEl = a.querySelector("h3 b, h3"); + var title = ((tEl ? tEl.textContent : "") || a.getAttribute("title") || (img ? img.getAttribute("alt") : "") || "").trim(); + var pEl = a.querySelector("p"); + var meta = pEl ? pEl.textContent.trim().replace(/\s+/g, " ") : ""; + var ym = meta.match(/(19|20)\d{2}/); + add(m[1], title, poster, meta, ym ? ym[0] : ""); + }); + if (!out.length) { // 兜底:模板变了也能抓到 /mv/ 链接 + Array.from(doc.querySelectorAll("a[href*='/mv/']")).forEach(function (a) { + var m = (a.getAttribute("href") || "").match(/\/mv\/(\d+)\.html/); + if (!m) return; + var scope = a.closest("li, .item, article") || a; + var img = a.querySelector("img") || (scope ? scope.querySelector("img") : null); + var poster = img ? (img.getAttribute("data-src") || img.getAttribute("src") || "") : ""; + var title = (a.getAttribute("title") || (img ? img.getAttribute("alt") : "") || a.textContent || "").trim(); + add(m[1], title, poster, "", ""); + }); + } + return out; +} +/* 详情页 -> { title, poster, overview, resources:[{url,label,quality,type}] } + 下载区结构:#donLink 里 .nav-tabs .title 是画质分组名,.gdt.content 里每个 a[href] 是一条下载/网盘链接 */ +function qwParseDetail(html) { + var doc = qwParseDoc(html); + if (!doc) return { title: "", poster: "", overview: "", resources: [] }; + var h1 = doc.querySelector("h1"); + var title = h1 ? h1.textContent.trim() : ""; + var descEl = doc.querySelector("meta[name='description']"); + var overview = descEl ? (descEl.getAttribute("content") || "") : ""; + var poster = ""; + var imgs = Array.from(doc.querySelectorAll("img")); + for (var i = 0; i < imgs.length; i++) { + var s = imgs[i].getAttribute("data-src") || imgs[i].getAttribute("src") || ""; + if (/upload\/vod|vvmp4/.test(s)) { poster = s; break; } + } + var res = [], seen = {}; + function pushLink(url, label, quality) { + url = (url || "").trim(); + if (!url || !/^https?:|^magnet:|^ed2k:|^thunder:/.test(url)) return; + if (seen[url]) return; seen[url] = 1; + res.push({ url: url, label: (label || "").trim(), quality: quality || "", type: qwDiskType(url) }); + } + // 七味详情页有多个下载面板(磁力/BT 一个、网盘一个),且都复用 id="donLink"(重复 id), + // 不能只取第一个,必须遍历所有 .down-link 面板,各自的 tab(画质/网盘名)配各自的资源列表。 + var panels = Array.from(doc.querySelectorAll(".down-link")); + if (!panels.length) panels = [doc]; + panels.forEach(function (panel) { + var tabs = Array.from(panel.querySelectorAll(".nav-tabs .title, .tab-title .title")); + var lists = Array.from(panel.querySelectorAll(".gdt.content, ul.gdt")); + function qualOf(idx) { + var t = tabs[idx]; if (!t) return ""; + var c = t.cloneNode(true); var b = c.querySelector(".badge"); if (b && b.parentNode) b.parentNode.removeChild(b); + return (c.textContent || "").trim(); + } + lists.forEach(function (ul, idx) { + var q = qualOf(idx); + Array.from(ul.querySelectorAll("a[href]")).forEach(function (a) { pushLink(a.getAttribute("href"), a.textContent, q); }); + }); + }); + // 网盘链接全页兜底扫描:无条件执行(不再只在面板一无所获时才跑), + // 这样即便磁力面板先命中、或某些镜像的网盘区用了不同的类名/结构, + // 只要页面里出现真实网盘域名前缀的 ,都会被补进来(pushLink 已按 url 去重)。 + // 只匹配已知网盘域名 + 磁力,广告/诱导链接(非网盘域名)自然不会被收。 + var sweep = "a[href^='magnet:'], a[href*='pan.baidu.com'], a[href*='pan.quark.cn'], " + + "a[href*='pan.xunlei.com'], a[href*='drive.uc.cn'], a[href*='cloud.189.cn'], " + + "a[href*='123pan.com'], a[href*='123684.com'], a[href*='123865.com'], a[href*='123912.com'], " + + "a[href*='aliyundrive.com'], a[href*='alipan.com'], a[href*='115.com'], a[href*='115cdn.com'], " + + "a[href*='mypikpak.com'], a[href*='caiyun'], a[href*='139.com']"; + Array.from(doc.querySelectorAll(sweep)).forEach(function (a) { + // 尽量给个画质/网盘名标签:优先取所在面板 tab,其次取链接自身文字 + var q = ""; + var host = a.closest ? a.closest(".gdt.content, ul.gdt, li, p") : null; + pushLink(a.getAttribute("href"), a.textContent, q); + }); + return { title: title, poster: poster, overview: overview, resources: res }; +} +/* 七味解析出的资源 -> 盘链网盘条目(只保留 App 能处理的网盘类型 + 磁力) */ +function qwResList(resources) { + var out = [], seen = {}; + (resources || []).forEach(function (r) { + var type = r.type; + if (PANSOU_KEEP.indexOf(type) < 0 && type !== "magnet") return; + var url = r.url || ""; if (!url) return; + var k = type + "|" + normPanUrl(url); + if (seen[k]) return; seen[k] = 1; + var lab = PAN_META[type] ? PAN_META[type].label : (TYPE_LABEL[type] || type); + out.push({ + type: type, url: url, password: "", + title: r.label || "", + groupName: lab, quality: lab, + source: "七味", state: "idle", _qw: true + }); + }); + return out; +} +/* 详情页里已经并进来的七味资源(刷新/重载时保留,跟 zn4kKeep 同理) */ +function qwKeep() { return (state.panItems || []).filter(function (it) { return it._qw; }); } +/* 把一批七味资源合并进当前详情页网盘列表(去重 + 重建 tab) */ +function qwMerge(pans, token) { + if (token !== state.panToken || !pans || !pans.length) return; + var have = {}; + state.panItems.forEach(function (it) { have[it.type + "|" + normPanUrl(it.url || it.token)] = 1; }); + var add = []; + pans.forEach(function (p) { var k = p.type + "|" + normPanUrl(p.url); if (!have[k]) { have[k] = 1; add.push(p); } }); + if (!add.length) return; + renderResources(state.panItems.concat(add)); +} +/* 七味搜索:/vs/...html?wd=关键词 -> 列表 */ +function qwSearch(kw) { + return qwHttpText(qwSearchUrl(kw)).then(function (html) { return qwParseList(html); }, function () { return []; }); +} +/* 从七味搜索结果里挑与本片最匹配的一条(标题相关度优先,年份加权) */ +function qwPickBest(list, rawTitle, year) { + var key = cleanTitle(rawTitle), best = null, bestScore = 99; + (list || []).forEach(function (it) { + var ts = titleScore(it.title, key); + if (ts > 3) return; + var s = ts; + if (year && ((it.title || "").indexOf(year) >= 0 || (it.meta || "").indexOf(year) >= 0 || it.year === year)) s -= 0.3; + if (s < bestScore) { bestScore = s; best = it; } + }); + return best; +} +/* 详情页(盘链原生条目):按标题去七味搜同名,把站内网盘/磁力并进来 */ +function qwEnrich(rawTitle, token) { + var kw = cleanTitle(rawTitle); + if (!kw) return; + var year = yearOf(rawTitle); + qwSearch(kw).then(function (list) { + if (token !== state.panToken || !list.length) return null; + var best = qwPickBest(list, rawTitle, year); + if (!best) return null; + return qwHttpText(qwDetailUrl(best.id)).then(function (html) { + if (token !== state.panToken) return; + qwMerge(qwResList(qwParseDetail(html).resources), token); + }); + }).then(null, function () {}); +} +/* 详情页(七味来源条目):已知七味影片 id,直接取其站内下载资源 */ +function qwLoadById(id, token) { + if (!id) return; + qwHttpText(qwDetailUrl(id)).then(function (html) { + if (token !== state.panToken) return; + var d = qwParseDetail(html); + if (state.currentDetail && !state.currentDetail.tmdb) { + if (d.title) { state.currentDetail.rawTitle = d.title; showTitle(d.title); } + var ov = $("detailOverview"); if (ov && !ov.textContent && d.overview && !isJunkOverview(d.overview)) { ov.textContent = d.overview; state.currentDetail.overviewFromSite = true; } + if (d.poster && !state.currentDetail.poster) { state.currentDetail.poster = d.poster; setDetailHeroImages([d.poster], false); } + } + var pans = qwResList(d.resources); + if (pans.length) renderResources(sortPanDefault(pans).concat(panSouKeep())); + }, function () {}); +} +/* 七味来源条目:顺便按标题反查盘链本站,命中就升级成原生条目 */ +function qwReverseLookup(item, token) { + var title = item.title, yr = item.year || yearOf(item.title); + apiGet(apiUrl("get_videos.php") + "?wd=" + encodeURIComponent(cleanTitle(title)) + "&pg=1&_t=" + Date.now(), "").then(function (d) { + if (token !== state.panToken || state.currentItem !== item) return; + var list = ((d && (d.list || d.data)) || []).map(mapVod); + function score(it) { var s = 0; if (it.title === title) s += 8; else if (titleCloseContain(it.title, title)) s += 4; if (yr && it.year === yr) s += 3; return s; } + var good = list.filter(function (it) { return score(it) >= 4; }); + if (good.length) openDetail(good.slice().sort(function (a, b) { return score(b) - score(a); })[0]); + }, function () {}); +} +/* 抓七味分类某一页 -> 成员数组(带 _qw 标记,点开走七味详情逻辑) */ +function qwFetchPage(cat, page) { + return qwHttpText(qwListUrl(cat, page)).then(function (html) { + return qwParseList(html).map(function (m) { m._qw = true; m._qwId = m.id; return m; }); + }); +} +/* 首页「七味」一行:点标题/更多进「查看全部」(含电影/剧集/综艺/动漫/短剧分类切换) */ +function renderQwHomeRow(box) { + var shell = makeRowShell("七味", function () { openQwCategory("1", "七味"); }); + box.appendChild(shell.section); + var cached = state.qwCatCache["1"]; + var hasCache = !!(cached && cached.length); + if (hasCache) fillRow(shell.row, cached.slice(0, 12), function (it) { openDetail(it); }); + else shell.row.replaceChildren(el("div", "mh-row-empty", "加载中…")); + var load = function () { + if (!document.body.contains(shell.row)) return null; + return qwFetchPage("1", 1).then(function (members) { + if (!document.body.contains(shell.row)) return; + if (!members.length) { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "暂无数据")); return; } + state.qwCatCache["1"] = members; saveHomeDisk(); + fillRow(shell.row, members.slice(0, 12), function (it) { openDetail(it); }); + }, function () { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "加载失败")); }); + }; + if (hasCache) { if (homeCacheStale()) homePrefetch(load); return; } + rowLoad(shell.section, load); +} +/* 打开七味「查看全部」:顶部分类 chips(电影/剧集/综艺/动漫/短剧),滑到底续页(同蜗牛 wnLoadCat) */ +function openQwCategory(t, name) { + saveHomeReturn(); + state.view = "category"; + $("homeView").style.display = "none"; $("catView").style.display = ""; + $("catTitle").textContent = name || "七味"; + $("clearSearch").style.display = "none"; $("clearRecent").style.display = "none"; + setNav(null); updateHeroVisible(); window.scrollTo(0, 0); + navigate("category"); + state.searchMode = false; state.searchKw = ""; + state.activeCat = QW_CAT_SLUG; state.qwCatSel = t || "1"; state.activeGenre = ""; + $("genreChips").style.display = ""; renderQwGenreChips(); + qwLoadCat(true); +} +function renderQwGenreChips() { + var box = $("genreChips"); if (!box) return; + var frag = document.createDocumentFragment(); + QW_CATS.forEach(function (c, i) { + var b = document.createElement("button"); + b.type = "button"; + b.className = "genre-chip focusable" + (state.qwCatSel === c.t ? " active" : ""); + b.textContent = c.name; b.setAttribute("data-genre", c.name); + b.setAttribute("data-focus-row", "genreChips"); b.setAttribute("data-focus-index", String(i)); + b.onclick = (function (cc) { return function () { state.qwCatSel = cc.t; renderQwGenreChips(); window.scrollTo(0, 0); qwLoadCat(true); }; })(c); + frag.appendChild(b); + }); + box.replaceChildren(frag); +} +function qwLoadCat(reset) { + var t = state.qwCatSel || "1"; + if (reset) { + state.reqToken++; + state.qwPage = 1; state.source = []; state.rendered = 0; state.noMore = false; state.loading = false; state.autoPages = 0; + $("grid").replaceChildren(); $("grid").classList.remove("grid-recent"); $("browseNotice").replaceChildren(); + var c = state.qwCatCache[t]; + if (c && c.length) { qwPaintCat(c, state.reqToken, 1); return; } + } + if (state.loading || state.noMore) return; + var token = state.reqToken, page = reset ? 1 : (state.qwPage + 1); + state.loading = true; $("browseLoading").style.display = "block"; if (reset) $("browseSub").textContent = "加载中"; + qwFetchPage(t, page).then(function (list) { + state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + if (page === 1) state.qwCatCache[t] = list; + qwPaintCat(list, token, page); + }, function () { + state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; state.noMore = true; + if (!state.source.length) $("grid").replaceChildren(el("div", "mh-row-empty", "加载失败,返回重试")); + }); +} +function qwPaintCat(list, token, page) { + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + var have = {}; state.source.forEach(function (s) { have[s.id] = 1; }); + var fresh = (list || []).filter(function (it) { return !have[it.id]; }); + if (!fresh.length) { state.noMore = true; if (!state.source.length) { $("grid").replaceChildren(el("div", "mh-row-empty", "该分类暂无内容")); $("browseSub").textContent = ""; } return; } + state.qwPage = page; state.source = state.source.concat(fresh); appendGrid(); + $("browseSub").textContent = state.source.length + " 部"; +} + +/* ============================================================ + 枫叶4K影院(cd-zj.com)集成:这是在线点播站(MacCMS / CupFox 主题),不走网盘, + 播放页里就是 m3u8 直链,属于"即点即播"。 + - 站点结构:列表卡片 .public-list-box;详情 /detail/{id}.html; + 播放页 /play/{id}-{线路号}-{集号}.html,页面里有标准的 player_aaaa(含 m3u8 直链); + 搜索 /cupfox-search/-------------.html?wd=关键词;分类 /type/{t}.html。 + - 首页:新增「枫叶」一行 + 完整分类页(电影/剧集/综艺/动漫/短剧,可翻页)。 + - 详情页:枫叶的线路/剧集直接并进资源列表(线路做 tab、每集一行,点播放即播), + 和网盘资源共存;搜索结果同样不分站源,同名的只在详情页体现。 + ============================================================ */ +var FY_DEFAULT_BASE = "https://www.cd-zj.com"; +var FY_CAT_SLUG = "__fy__"; // 枫叶在分类视图里的内部 slug +var FY_UA = "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36"; +/* 站点各线路(至臻4k / 蓝光-2k / 自营t·y·r,data-form 都是 yhm3u8)共用的外挂播放器。 + 这类线路的 player_aaaa.url 只是个 token(形如 JD-xxxxx),要拼到这个播放器地址后面才能换到真视频地址。 + 正常情况下地址会从站点的 MacPlayerConfig 里读出来,读不到时用这个兜底。 */ +var FY_PARSE_FALLBACK = "https://fgsrg.hzqingshan.com/player/?url="; +/* 站点的播放器配置 JS(MacCMS 主题一般把 MacPlayerConfig 放在这类文件里,而不是内联在播放页) */ +var FY_CONFIG_JS = ["/static/js/playerconfig.js", "/template/e/js/playerconfig.js", "/static/js/player.js"]; +var _fyParseCache = null; // { from -> parse } +function fyBase() { return trimSlash(config.fyBase || FY_DEFAULT_BASE); } +function fyHost() { return fyBase().replace(/^https?:\/\//, "").split("/")[0]; } +/* 枫叶站分类(对应 /type/{t}.html) */ +var FY_CATS = [ + { t: "1", name: "电影" }, + { t: "2", name: "剧集" }, + { t: "3", name: "综艺" }, + { t: "4", name: "动漫" }, + { t: "5", name: "短剧" } +]; +/* 枫叶站的「来源标签」页(/label/{tag}.html)——按视频来源平台分,正好对应腾讯/优酷/B站。 + 注意:这是来源标签,不是内容分类,标签名跟里面是电影还是剧集无关,所以行标题直接用平台名。 */ +var FY_LABELS = [ + { tag: "qq", name: "腾讯" }, + { tag: "youku", name: "优酷" }, + { tag: "bli", name: "哔哩" } +]; + +function fyHttpText(url) { + if (state.sdkReady && window.fm && fm.req) { + return fm.req(url, { method: "GET", responseType: "text", timeout: 20, headers: { "User-Agent": FY_UA, "Referer": fyBase() + "/" } }) + .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.body; }); + } + return fetchTimeout(url, { headers: {} }).then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); }); +} +function fyParseDoc(html) { + try { + var s = String(html).replace(//gi, "").replace(//gi, "").replace(//g, ""); + return new DOMParser().parseFromString(s, "text/html"); + } catch (e) { return null; } +} +/* 列表页 / 搜索页卡片 -> 影片对象。海报走百度图片代理,站点自己就是 no-referrer,用通用 imgSrc 即可 */ +function fyParseList(html) { + var doc = fyParseDoc(html); + if (!doc) return []; + var out = [], seen = {}; + Array.from(doc.querySelectorAll(".public-list-box")).forEach(function (box) { + var a = box.querySelector("a.public-list-exp, a[href*='/detail/']"); + if (!a) return; + var m = (a.getAttribute("href") || "").match(/\/detail\/(\d+)\.html/); + if (!m) return; + var id = m[1]; if (seen[id]) return; + var img = box.querySelector("img"); + var poster = img ? (img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || "") : ""; + var tEl = box.querySelector(".time-title, .public-list-button a"); + var title = ((a.getAttribute("title") || "") || (tEl ? tEl.textContent : "") || (img ? img.getAttribute("alt") : "") || "").trim(); + if (!title) return; + var rk = box.querySelector(".public-list-prb i, .public-list-prb"); + var status = rk ? rk.textContent.trim() : ""; + var sub = box.querySelector(".public-list-subtitle"); + seen[id] = 1; + out.push({ + id: "fy_" + id, title: title, poster: poster, meta: status, status: status, + overview: sub ? sub.textContent.trim() : "", + _fy: true, _fyId: id + }); + }); + return out; +} +/* 详情页 -> { title, poster, overview, lines:[线路名], eps:[{sid,nid,name}] } + 剧集不依赖 DOM 里各线路区块的先后顺序(站点会按"排序"按钮改顺序), + 直接从 /play/{id}-{sid}-{nid}.html 链接里读 sid/nid,稳。 */ +function fyParseDetail(html, id) { + var doc = fyParseDoc(html); + if (!doc) return null; + var h3 = doc.querySelector(".slide-info-title, .detail-info h3, h1"); + var title = h3 ? h3.textContent.trim() : ""; + var cover = doc.querySelector(".detail-pic img"); + var poster = cover ? (cover.getAttribute("data-src") || cover.getAttribute("src") || "") : ""; + var ovEl = doc.querySelector("#height_limit, .switch-box .text"); + var overview = ovEl ? ovEl.textContent.replace(/^简介\s*[::]\s*/, "").trim() : ""; + if (!overview) { + var mt = doc.querySelector("meta[name='description']"); + overview = mt ? (mt.getAttribute("content") || "") : ""; + } + var lines = []; + Array.from(doc.querySelectorAll(".anthology-tab .swiper-slide")).forEach(function (s) { + var c = s.cloneNode(true); + var b = c.querySelector(".badge"); if (b && b.parentNode) b.parentNode.removeChild(b); // 去掉"(5)"这种集数角标 + lines.push((c.textContent || "").replace(/\s+/g, " ").trim()); + }); + var eps = [], eseen = {}; + Array.from(doc.querySelectorAll("a[href*='/play/']")).forEach(function (a) { + var mm = (a.getAttribute("href") || "").match(/\/play\/(\d+)-(\d+)-(\d+)\.html/); + if (!mm) return; + if (String(mm[1]) !== String(id)) return; // 只要本片的,排除"猜你喜欢"里别的片子 + var k = mm[2] + "-" + mm[3]; if (eseen[k]) return; eseen[k] = 1; + eps.push({ sid: parseInt(mm[2], 10), nid: parseInt(mm[3], 10), name: (a.textContent || "").trim() || ("第" + mm[3] + "集") }); + }); + eps.sort(function (a, b) { return (a.sid - b.sid) || (a.nid - b.nid); }); + return { title: title, poster: poster, overview: overview, lines: lines, eps: eps }; +} +/* 详情 -> 资源条目。按需求:只保留「至臻4k」这条线路(它对应 player_aaaa.url 直接是 m3u8, + 解析快、能秒播;自营/蓝光那几条走外挂解析、慢且常失败,全部丢掉),其余交给盘搜兜底。 */ +function fyIsZhizhen(lineName) { + var s = String(lineName || "").replace(/\s/g, "").toLowerCase(); + return s.indexOf("至臻") >= 0 || s.indexOf("4k") >= 0; // "至臻4k" +} +function fyResList(d, id) { + if (!d || !d.eps || !d.eps.length) return []; + var out = []; + d.eps.forEach(function (e) { + var line = d.lines[e.sid - 1] || ("线路" + e.sid); + if (!fyIsZhizhen(line)) return; // 只留至臻4k + out.push({ + type: "fy", url: "", password: "", + title: e.name, + groupName: line, quality: line, + source: "枫叶 · 在线", + state: "idle", + _fy: true, _fyId: id, _fySid: e.sid, _fyNid: e.nid, + _fyPlayPage: fyBase() + "/play/" + id + "-" + e.sid + "-" + e.nid + ".html" + }); + }); + return out; +} +/* ============ 枫叶播放地址破解(移植自站点通用解法,覆盖至臻4k / 蓝光-2k / 自营 t·y·r) ============ + 流程和一个能跑通的第三方实现完全对齐: + 1) 播放页里读 player_aaaa:url(可能 urlencode/base64)、encrypt、from + 2) url 本身就是 .m3u8/.mp4 直链 -> 直接用 + 3) 否则读站点 /static/js/playerconfig.js,按 from 找到该线路的 parse(解析页)地址,找不到用兜底 + 4) 请求 "parse + 待解析url" 得到解析页 iframe,从 #player-data 读 data-te(token) 和 data-bt + 5) POST 到 {解析页域名}{bt}mplayer.php,body=url=...&token=...,拿到 realUrl 和 urlmode + 6) 按 urlmode 做 1/2/3 三种解密(含 WyJ 开头的多轮 jsDecrypt3),得到真正的 m3u8/mp4 + 7) 播放:fm.play(realUrl, title, { referer: 站点 }) —— 注意是位置参数,不是对象! */ +var FY_PARSE_FALLBACK = "https://fgsrg.hzqingshan.com/player/?url="; +var FY_MD5_TEST = "098f6bcd4621d373cade4e832627b4f6"; // md5("test"),jsDecrypt1 用;避免引 CryptoJS 直接写死 + +function fyFixB64(s) { if (!s) return s; var miss = s.length % 4; if (miss) s += new Array(4 - miss + 1).join("="); return s; } +function fyB64Decode(s) { try { return atob(fyFixB64(s)); } catch (e) { return ""; } } +/* urlmode=1:base64 -> 与 md5("test") 异或 -> 再 base64 */ +function fyDecrypt1(data) { + try { + var decoded = fyB64Decode(data), key = FY_MD5_TEST, out = ""; + for (var i = 0; i < decoded.length; i++) out += String.fromCharCode(decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length)); + return atob(out); + } catch (e) { return data; } +} +/* urlmode=2:base64 -> 取每 3 个字符里的第 2 个,在固定表里 +59 位移 */ +function fyDecrypt2(data) { + var chars = "PXhw7UT1B0a9kQDKZsjIASmOezxYG4CHo5Jyfg2b8FLpEvRr3WtVnlqMidu6cN"; + try { + var dec = fyB64Decode(data), out = ""; + for (var i = 1; i < dec.length; i += 3) { + var ch = dec[i], idx = chars.indexOf(ch); + out += (idx !== -1) ? chars[(idx + 59) % 62] : ch; + } + return out; + } catch (e) { return data; } +} +/* urlmode=3:a/b/cipher 三段,用字典表逐字符映射 */ +function fyDecrypt3(data) { + try { + var parts = String(data).split("/"); + if (parts.length >= 3) { + var arr1 = JSON.parse(fyB64Decode(parts[0])); + var arr2 = JSON.parse(fyB64Decode(parts[1])); + var cipher = fyB64Decode(parts.slice(2).join("/")), result = ""; + for (var i = 0; i < cipher.length; i++) { + var c = cipher[i], idx = arr2.indexOf(c); + result += (idx !== -1) ? arr1[idx] : c; + } + return result; + } + } catch (e) {} + return data; +} +/* 通用文本请求(可带 method/headers/body),走 App 的 fm.req,退回 fetch */ +function fyReq(url, options) { + options = options || {}; + if (state.sdkReady && window.fm && fm.req) { + return fm.req(url, { + method: options.method || "GET", + responseType: "text", + timeout: options.timeout || 25, + headers: Object.assign({ "User-Agent": FY_UA, "Referer": fyBase() + "/" }, options.headers || {}), + credentials: "include", + body: options.body + }).then(function (r) { + var ok = (r.ok !== undefined) ? r.ok : (r.status >= 200 && r.status < 300); + if (!ok && r.status !== 200) throw new Error(r.error || ("HTTP " + r.status)); + return r.body || r.content || r.data || r.text || ""; + }); + } + var fo = { headers: Object.assign({ "Referer": fyBase() + "/" }, options.headers || {}) }; + if (options.method === "POST") { fo.method = "POST"; fo.body = options.body; } + return fetchTimeout(url, fo).then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); }); +} +/* 播放页 -> player_aaaa 对象 */ +function fyParsePlayerInfo(html) { + var h = String(html); + var m = h.match(/var\s+player_aaaa\s*=\s*(\{[\s\S]*?\})\s*<\/script>/); + if (!m) m = h.match(/player_aaaa\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return null; + var obj = null; + try { obj = JSON.parse(m[1]); } catch (e) { return null; } + return obj; +} +function fyIsDirect(u) { return /^https?:\/\//i.test(u) && /\.(m3u8|mp4|flv|ts)(\?|$|#)/i.test(u); } + +/* 核心:破解一集的真实播放地址(对齐第三方可用实现) */ +function fyCrackPlayUrl(playPageUrl) { + return fyReq(playPageUrl).then(function (html) { + var pd = fyParsePlayerInfo(html); + if (!pd) return null; + var durl = pd.url || ""; + var encrypt = parseInt(pd.encrypt, 10) || 0; + try { + if (encrypt === 1) durl = decodeURIComponent(durl); + else if (encrypt === 2) durl = decodeURIComponent(atob(durl)); + } catch (e) {} + // 2) 本身就是直链 + if (/^http/i.test(durl) && (durl.indexOf(".m3u8") >= 0 || durl.indexOf(".mp4") >= 0)) return durl; + // 3) 读 playerconfig.js,按 from 找 parse + return fyReq(fyBase() + "/static/js/playerconfig.js").then(function (cfg) { return cfg; }, function () { return ""; }).then(function (configJs) { + var parseApi = "", fromFlag = pd.from || ""; + if (fromFlag && configJs) { + var re = new RegExp('"' + fromFlag + '":\\{[^}]*"parse":"([^"]+)"'); + var mm = configJs.match(re); + if (mm) parseApi = mm[1].replace(/\\\//g, "/"); + } + if (!parseApi && configJs) { var m2 = configJs.match(/"parse":"(http[^"]+)"/); if (m2) parseApi = m2[1].replace(/\\\//g, "/"); } + if (!parseApi) parseApi = FY_PARSE_FALLBACK; + // 4) 请求解析页 iframe + var iframeUrl = parseApi + encodeURIComponent(durl); + return fyReq(iframeUrl, { headers: { Referer: playPageUrl } }).then(function (iframeHtml) { + var div = document.createElement("div"); + div.innerHTML = String(iframeHtml).replace(//gi, function (x) { return x; }); + var pde = div.querySelector("#player-data"); + if (!pde) { + // 没有 player-data:解析页可能直接就把地址写在里面了 + var direct = fyDeepFindUrl(iframeHtml); + return direct || iframeUrl; + } + var token = pde.getAttribute("data-te") || ""; + var bt = pde.getAttribute("data-bt") || "/player/"; + var apiHost; + try { apiHost = new URL(parseApi).origin; } catch (e) { apiHost = parseApi.replace(/(https?:\/\/[^\/]+).*/, "$1"); } + var apiUrl = apiHost + bt + "mplayer.php"; + var postData = "url=" + encodeURIComponent(durl) + "&token=" + encodeURIComponent(token); + // 5) POST mplayer.php + return fyReq(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "X-Requested-With": "XMLHttpRequest", + "Referer": iframeUrl, + "Origin": apiHost + }, + body: postData + }).then(function (apiResText) { + var apiJson = null; + try { apiJson = JSON.parse(apiResText); } catch (err) { return null; } + var realUrl = apiJson.url || (apiJson.data && apiJson.data.url) || ""; + var urlmode = String(apiJson.urlmode || (apiJson.data && apiJson.data.urlmode) || ""); + // 6) 按 urlmode 解密 + if (urlmode === "1") realUrl = fyDecrypt1(realUrl); + else if (urlmode === "2") realUrl = fyDecrypt2(realUrl); + else if (urlmode === "3") realUrl = fyDecrypt3(realUrl); + for (var i = 0; i < 3; i++) { + if (typeof realUrl === "string" && realUrl.indexOf("WyJ") === 0 && realUrl.indexOf("/") >= 0) realUrl = fyDecrypt3(realUrl); + else break; + } + if (realUrl && (realUrl.indexOf(".m3u8") >= 0 || realUrl.indexOf(".mp4") >= 0)) return realUrl; + return null; + }, function () { return null; }); + }); + }); + }).then(null, function () { return null; }); +} +/* 破解失败时的普通嗅探:video/iframe/m3u8/mp4 兜底 */ +function fyRegexUrl(t) { + var m = String(t).replace(/\\\//g, "/").match(/https?:\/\/[^\s"'\\<>]+\.(?:m3u8|mp4|flv)[^\s"'\\<>]*/i); + return m ? m[0] : ""; +} +function fyDeepFindUrl(text) { + var t = String(text || ""); if (!t) return ""; + var plain = t.replace(/\\\//g, "/").replace(/\\u002[fF]/g, "/"); + var u = fyRegexUrl(plain); if (u) return u; + try { u = fyRegexUrl(decodeURIComponent(plain)); if (u) return u; } catch (e) {} + var cands = plain.match(/[A-Za-z0-9+/=]{24,}/g) || []; + for (var i = 0; i < cands.length && i < 60; i++) { + var raw = fyB64Decode(cands[i]); if (!raw) continue; + var one = fyRegexUrl(raw); if (one) return one; + try { one = fyRegexUrl(decodeURIComponent(raw)); if (one) return one; } catch (e2) {} + } + return ""; +} +function fySniff(playPageUrl) { + return fyReq(playPageUrl).then(function (html) { + var v = String(html).match(/]+src="([^"]+)"/i); if (v) return v[1]; + var m3 = String(html).match(/https?:\/\/[^\s"'<>]+\.m3u8[^\s"'<>]*/i); if (m3) return m3[0]; + var mp = String(html).match(/https?:\/\/[^\s"'<>]+\.mp4[^\s"'<>]*/i); if (mp) return mp[0]; + var ifr = String(html).match(/]+src="([^"]+)"/i); if (ifr) return ifr[1]; + return ""; + }, function () { return ""; }); +} +/* 对外:解析一集 -> 真实地址(先破解,失败再嗅探) */ +function fyResolve(item) { + return fyCrackPlayUrl(item._fyPlayPage).then(function (real) { + if (real) { item.url = real; item._fyReferer = fyBase() + "/"; return real; } + return fySniff(item._fyPlayPage).then(function (u) { + if (u) { item.url = u; item._fyReferer = fyBase() + "/"; return u; } + throw new Error("线路「" + (item.groupName || "") + "」没解析到播放地址"); + }); + }); +} + +/* 即点即播:拿到 m3u8 直链后交给 App 播放器。 + 不同 App 版本暴露的入口不一样,按可用性依次探测,最后退回 fm.pan.play(type=http, + 跟网盘直链走同一条播放通道)。都没有就当浏览器预览,只提示地址。 */ +/* 播放:这个 App 的 fm.play 是【位置参数】签名 —— fm.play(url, title, { referer }), + 不是传一个对象!之前"暂无播放数据"就是因为把对象当第一个参数传进去,url 变成了 undefined。 + 这里以位置参数为主,另留几个常见变体做兜底(不同 App 版本命名不同)。 */ +function fyPlayEntries() { + var out = []; + if (!window.fm) return out; + if (typeof fm.play === "function") out.push("fm.play"); + if (typeof fm.playUrl === "function") out.push("fm.playUrl"); + if (fm.player && typeof fm.player.play === "function") out.push("fm.player.play"); + if (fm.video && typeof fm.video.play === "function") out.push("fm.video.play"); + if (fm.pan && typeof fm.pan.play === "function") out.push("fm.pan.play"); + return out; +} +function fyPlayDirect(url, title, pic, referer, meta) { + if (!window.fm) { toast("已解析:" + url); return Promise.resolve(); } + var ref = referer || fyBase(); + var header = { "User-Agent": FY_UA, "Referer": fyBase() + "/" }; + // 对象式 payload:带剧名、海报、整季选集——支持的 App(如 FongMi 系)会用它渲染标题和"选集" + var rich = Object.assign({ + url: url, title: title || "枫叶资源", name: (meta && meta.name) || title, pic: pic || "", + type: "http", referer: ref, header: header + }, meta || {}); + try { + // 优先走能吃对象(含 episodes)的入口 + if (fm.player && typeof fm.player.play === "function") { state.fyLastPlayApi = "fm.player.play(obj)"; return Promise.resolve(fm.player.play(rich)); } + if (fm.video && typeof fm.video.play === "function") { state.fyLastPlayApi = "fm.video.play(obj)"; return Promise.resolve(fm.video.play(rich)); } + if (typeof fm.playVod === "function") { state.fyLastPlayApi = "fm.playVod(obj)"; return Promise.resolve(fm.playVod(rich)); } + // 回退:位置参数单集播放(参考实现验证可用),标题仍是剧名+集名,不显示 url + if (typeof fm.play === "function") { state.fyLastPlayApi = "fm.play"; return Promise.resolve(fm.play(url, title || "枫叶资源", { referer: ref, name: (meta && meta.name) || title, pic: pic || "", header: header })); } + if (typeof fm.playUrl === "function") { state.fyLastPlayApi = "fm.playUrl"; return Promise.resolve(fm.playUrl(url, title || "枫叶资源", { referer: ref })); } + if (fm.pan && typeof fm.pan.play === "function") { state.fyLastPlayApi = "fm.pan.play"; return Promise.resolve(fm.pan.play(rich)); } + } catch (e) { return Promise.reject(e); } + toast("App 没有可用的播放接口"); return Promise.reject(new Error("no play api")); +} + +/* ---------- 枫叶:列表 / 搜索 / 详情 ---------- */ +/* 分类页分页 URL:站点用的是 MacCMS 那套,不同主题路径不一样, + 这里按常见的两种依次试(第 1 页固定 /type/{t}.html) */ +function fyListUrls(t, page) { + var b = fyBase(); + // 来源标签页:t 形如 "label:qq",第 1 页 /label/{tag}.html,后续 /label/{tag}/page/{N} + if (String(t).indexOf("label:") === 0) { + var tag = t.slice(6); + if (!page || page <= 1) return [b + "/label/" + tag + ".html"]; + return [b + "/label/" + tag + "/page/" + page + ".html", b + "/label/" + tag + "/page/" + page]; + } + if (!page || page <= 1) return [b + "/type/" + t + ".html"]; + return [b + "/type/" + t + "-" + page + ".html", b + "/show/" + t + "--------" + page + "---.html"]; +} +function fyFetchList(t, page) { + var urls = fyListUrls(t, page); + function tryUrl(i) { + if (i >= urls.length) return Promise.resolve([]); + return fyHttpText(urls[i]).then(function (html) { + var list = fyParseList(html); + if (list.length) return list; + return tryUrl(i + 1); + }, function () { return tryUrl(i + 1); }); + } + return tryUrl(0); +} +function fySearch(kw) { + var u = fyBase() + "/cupfox-search/-------------.html?wd=" + encodeURIComponent(kw); + return fyHttpText(u).then(function (html) { return fyParseList(html); }, function () { return []; }); +} +/* 详情页:枫叶条目自己的播放列表 */ +function fyLoadById(id, token) { + if (!id) return; + fyHttpText(fyBase() + "/detail/" + id + ".html").then(function (html) { + if (token !== state.panToken) return; + var d = fyParseDetail(html, id); + if (!d) { renderResources(panSouKeep().concat(zn4kKeep()).concat(qwKeep())); return; } + if (state.currentDetail && !state.currentDetail.tmdb) { + if (d.title) { state.currentDetail.rawTitle = d.title; showTitle(d.title); } + var ov = $("detailOverview"); if (ov && !ov.textContent && !(state.currentDetail && state.currentDetail.tmdb) && d.overview && !isJunkOverview(d.overview)) { ov.textContent = d.overview; state.currentDetail.overviewFromSite = true; } + if (d.poster && !state.currentDetail.poster && !(state.currentDetail && state.currentDetail.tmdb)) { state.currentDetail.poster = d.poster; setDetailHeroImages([d.poster], false); } + } + renderResources(fyResList(d, id).concat(panSouKeep()).concat(zn4kKeep()).concat(qwKeep())); + }, function () { + if (token !== state.panToken) return; + renderResources(panSouKeep().concat(zn4kKeep()).concat(qwKeep())); + }); +} +/* 已并入详情页的枫叶播放条目(刷新时保留,同 panSouKeep / zn4kKeep) */ +function fyKeep() { return (state.panItems || []).filter(function (it) { return it._fy; }); } +/* 盘链/指南来源的条目:按标题去枫叶搜同名,命中就把在线播放线路并进资源列表 */ +/* ============================================================ + 蜗牛4K影院(zmi.kdns.fr)集成:MacCMS 站,/voddetail/{id}/、/vodsearch/、/vodshow/ 分页。 + ⚠ 它不是在线点播站——详情页给的是 115/夸克 这类【网盘下载链接】(就在静态 HTML 里), + 所以解析出来并进「网盘资源」,走盘链同一套播放/检测/密码逻辑,不需要在线解析。 + 同样"探测可达才显示"(该站可能需要代理):首页一行 + 搜索合并 + 详情网盘资源。 + ============================================================ */ +var WN_DEFAULT_BASE = "https://zmi.kdns.fr"; +var WN_CAT_SLUG = "__wn__"; +var WN_UA = "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36"; +function wnBase() { return trimSlash(config.wnBase || WN_DEFAULT_BASE); } +var WN_CATS = [ + { t: "1", name: "电影" }, + { t: "2", name: "剧集" }, + { t: "3", name: "综艺" }, + { t: "4", name: "动漫" } +]; +function wnHttpText(url) { return httpTextMemo("wn|" + url, function () { return wnHttpTextRaw(url); }); } +function wnHttpTextRaw(url) { + if (state.sdkReady && window.fm && fm.req) { + return fm.req(url, { method: "GET", responseType: "text", timeout: 20, headers: { "User-Agent": WN_UA, "Referer": wnBase() + "/" } }) + .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.body; }); + } + return fetchTimeout(url, { headers: {} }).then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); }); +} +function wnParseDoc(html) { + try { return new DOMParser().parseFromString(String(html).replace(//gi, "").replace(//gi, ""), "text/html"); } + catch (e) { return null; } +} +function wnAbs(u) { if (!u) return ""; if (/^https?:/i.test(u)) return u; if (u.indexOf("//") === 0) return "https:" + u; return wnBase() + (u.charAt(0) === "/" ? "" : "/") + u; } +/* 列表/搜索卡片 -> 影片对象 + 2026-07 站点改版为 panlian_dark v2.3 模板:卡片外层直接是 , + 标题/分数/画质分别在 .video-title / .video-score / .video-episode / .video-meta 里; + 旧模板 .module-item 结构不再存在,这里同时兼容新旧两套选择器,任一模板都能解析出来。 */ +function wnParseList(html) { + var doc = wnParseDoc(html); + if (!doc) return []; + var out = [], seen = {}; + Array.from(doc.querySelectorAll(".video-card, .module-item")).forEach(function (b) { + var a = (b.tagName === "A") ? b : b.querySelector("a[href*='/voddetail/']"); + if (!a) return; + var m = (a.getAttribute("href") || "").match(/\/voddetail\/(\d+)/); + if (!m) return; + var id = m[1]; if (seen[id]) return; + var img = b.querySelector("img"); + var poster = img ? (img.getAttribute("data-original") || img.getAttribute("data-src") || img.getAttribute("src") || "") : ""; + var tEl = b.querySelector(".video-title, .module-item-title, .module-poster-item-title, .title"); + var title = sanitizeSiteTitle(((tEl ? tEl.textContent : "") || a.getAttribute("title") || (img ? img.getAttribute("alt") : "") || "")); + if (!title) return; + var note = b.querySelector(".video-episode, .video-meta, .module-item-note, .module-item-text"); + seen[id] = 1; + out.push({ + id: "wn_" + id, title: title, poster: wnAbs(poster), + meta: note ? note.textContent.trim() : "", status: note ? note.textContent.trim() : "", + _wn: true, _wnId: id + }); + }); + return out; +} +/* 详情页信息 + 网盘链接。蜗牛不是在线点播站——它详情页给的是 115/夸克 这类网盘下载链接, + 所以解析出来并进「网盘资源」,走盘链同一套播放/检测。 + 2026-07 站点改版为 panlian_dark v2.3 模板:资源区块变成 .pan-group(组名 .pan-group-title, + 如"百度资源"/"115离线下载")+ .pan-link-item(标题 .pan-link-title,链接在 .pan-link-actions a[href]), + 旧的 .module-row-one 结构不再存在,这里按新结构精确解析,同时保留旧结构 / 通用兜底以防再次改版。 */ +function wnParseDetail(html, id) { + var doc = wnParseDoc(html); + if (!doc) return null; + var h1 = doc.querySelector(".module-info-heading h1, .video-info-header h1, h1"); + var title = h1 ? h1.textContent.trim() : ""; + var pic = doc.querySelector(".premium-poster img, .module-item-pic img, .video-cover img, .module-info-poster img"); + var poster = pic ? (pic.getAttribute("data-original") || pic.getAttribute("data-src") || pic.getAttribute("src") || "") : ""; + var ov = doc.querySelector("#detailDescText, .detail-desc-text, .module-info-introduction-content, .video-info-content, .content"); + var overview = ov ? ov.textContent.replace(/\s+/g, " ").trim() : ""; + // 网盘链接:优先新模板 .pan-group,拿不到再退回旧 .module-row-one,最后是通用 href / data-clipboard-text 兜底 + var pans = [], seen = {}; + function pushPan(url, name) { + url = String(url || "").trim(); + var mm = url.match(/https?:\/\/[^\s"'<>]+/); + if (!mm) return; url = mm[0]; + var type = wnDiskType(url); if (!type) return; + // 密码:?password= / &pwd= / 提取码 + var pwd = ""; + var pm = url.match(/[?&](?:password|pwd|pass|code)=([0-9a-zA-Z]+)/); + if (pm) pwd = pm[1]; + var key = type + "|" + url.replace(/[?#].*$/, ""); + if (seen[key]) return; seen[key] = 1; + pans.push({ + type: type, url: url, password: pwd, + title: (name || "").trim(), + groupName: (PAN_META[type] ? PAN_META[type].label : type), + quality: (PAN_META[type] ? PAN_META[type].label : type), + source: "蜗牛", state: "idle", _wn: true + }); + } + Array.from(doc.querySelectorAll(".pan-group")).forEach(function (grp) { + Array.from(grp.querySelectorAll(".pan-link-item")).forEach(function (item) { + var nameEl = item.querySelector(".pan-link-title"); + var linkEl = item.querySelector(".pan-link-actions a[href]"); + var url = linkEl ? linkEl.getAttribute("href") : ""; + if (!url) { var cp = item.querySelector("[data-copy]"); url = cp ? cp.getAttribute("data-copy") : ""; } + if (!url) { var metaEl = item.querySelector(".pan-link-meta"); url = metaEl ? metaEl.textContent : ""; } + pushPan(url, nameEl ? nameEl.textContent : ""); + }); + }); + if (!pans.length) Array.from(doc.querySelectorAll(".module-row-one, .module-row-info")).forEach(function (row) { + var a = row.querySelector("[data-clipboard-text]"); + var url = a ? a.getAttribute("data-clipboard-text") : ""; + if (!url) { var ln = row.querySelector(".module-row-title i, a[href*='115.com'], a[href*='pan.']"); url = ln ? (ln.textContent || ln.getAttribute("href")) : ""; } + var h4 = row.querySelector(".module-row-title h4, h4"); + pushPan(url, h4 ? h4.textContent : ""); // 下载区里的链接:通用类型也收(原盘站会用小众网盘) + }); + if (!pans.length) { + // 通用兜底一:直接扫全页 ,按已知网盘域名收,避免站内链接误当资源 + Array.from(doc.querySelectorAll("a[href]")).forEach(function (a) { + var u = a.getAttribute("href") || ""; + if (!/^https?:\/\//i.test(u)) return; + if (wnDiskType(u) === "http" || !wnDiskType(u)) return; // 兜底阶段只收明确网盘域名 + var box = a.closest ? (a.closest("li") || a.closest("div")) : null; + var nameEl = box ? box.querySelector("h3, h4, strong, .title, .name") : null; + pushPan(u, (nameEl ? nameEl.textContent : "") || a.textContent || ""); + }); + } + if (!pans.length) { + // 通用兜底二:旧模板 data-clipboard-text,只认明确的网盘域名 + Array.from(doc.querySelectorAll("[data-clipboard-text]")).forEach(function (a) { + var u = a.getAttribute("data-clipboard-text") || ""; + var mm = u.match(/https?:\/\/[^\s"'<>]+/); if (!mm) return; + if (wnDiskType(mm[0]) === "http") return; // 兜底阶段不收通用链接 + pushPan(u, a.getAttribute("title") || ""); + }); + } + return { title: title, poster: wnAbs(poster), overview: overview, pans: pans }; +} +/* 网盘类型识别:已知网盘归类;原盘高清这类站还会用光雅盘等小众网盘, + 未知的也保留(归为通用 http),让用户至少能复制到链接,不要直接丢掉。 */ +function wnDiskType(u) { + for (var i = 0; i < HOSTMAP.length; i++) if (HOSTMAP[i][0].test(u)) return HOSTMAP[i][1]; + if (/guangyapan\.com/i.test(u)) return "guangya"; + if (/magnet:/i.test(u)) return "magnet"; + if (/^https?:/i.test(u)) return "http"; // 其它网盘/直链:保留为通用 + return ""; +} +function wnFetchList(t, page) { + var b = wnBase(); + var urls; + if (!page || page <= 1) { + urls = [b + "/vodtype/" + t + "/", b + "/vodshow/" + t + "-----------/"]; + } else if (state._wnPageFn) { + // 已经探到能用的分页写法,直接用它,不再逐个试 + urls = [state._wnPageFn(t, page)]; + } else { + urls = [ + b + "/vodtype/" + t + "-" + page + "/", + b + "/vodshow/" + t + "--------" + page + "---/", + b + "/vodshow/" + t + "-----" + page + "------/", + b + "/vodshow/" + t + "-" + page + "----------/", + b + "/vodshow/" + t + "-------" + page + "----/", + b + "/vodtype/" + t + "/page/" + page + "/", + b + "/vodtype/" + t + ".html?page=" + page + ]; + } + // 记住每个候选对应的"生成函数",命中后固化,后续页直接套用 + var gens = [ + function (tt, p) { return b + "/vodtype/" + tt + "-" + p + "/"; }, + function (tt, p) { return b + "/vodshow/" + tt + "--------" + p + "---/"; }, + function (tt, p) { return b + "/vodshow/" + tt + "-----" + p + "------/"; }, + function (tt, p) { return b + "/vodshow/" + tt + "-" + p + "----------/"; }, + function (tt, p) { return b + "/vodshow/" + tt + "-------" + p + "----/"; }, + function (tt, p) { return b + "/vodtype/" + tt + "/page/" + p + "/"; }, + function (tt, p) { return b + "/vodtype/" + tt + ".html?page=" + p; } + ]; + function tryUrl(i) { + if (i >= urls.length) return Promise.resolve([]); + return wnHttpText(urls[i]).then(function (html) { + var list = wnParseList(html); + if (list.length) { + if (page > 1 && !state._wnPageFn && urls.length > 1) state._wnPageFn = gens[i]; + return list; + } + return tryUrl(i + 1); + }, function () { return tryUrl(i + 1); }); + } + return tryUrl(0); +} +function wnSearch(kw) { + // MacCMS 搜索:/vodsearch/{wd}-------------/ + var u = wnBase() + "/vodsearch/" + encodeURIComponent(kw) + "-------------/"; + return wnHttpText(u).then(function (html) { return wnParseList(html); }, function () { return []; }); +} +/* 已并入详情页的蜗牛网盘条目(刷新保留) */ +function wnKeep() { return (state.panItems || []).filter(function (it) { return it._wn; }); } +/* 详情页:蜗牛条目——信息补全 + 把它的网盘链接并进「网盘资源」 */ +function wnLoadById(id, token) { + wnHttpText(wnBase() + "/voddetail/" + id + "/").then(function (html) { + if (token !== state.panToken) return; + var d = wnParseDetail(html, id); + if (d && state.currentDetail && !state.currentDetail.tmdb) { + if (d.title) { state.currentDetail.rawTitle = d.title; showTitle(d.title); } + var ovv = $("detailOverview"); if (ovv && !ovv.textContent && !(state.currentDetail && state.currentDetail.tmdb) && d.overview && !isJunkOverview(d.overview)) { ovv.textContent = d.overview; state.currentDetail.overviewFromSite = true; } + if (d.poster && !state.currentDetail.poster && !(state.currentDetail && state.currentDetail.tmdb)) { state.currentDetail.poster = d.poster; setDetailHeroImages([d.poster], false); } + } + var pans = (d && d.pans) || []; + renderResources(sortPanDefault(pans).concat(fyKeep()).concat(panSouKeep()).concat(zn4kKeep()).concat(qwKeep())); + }, function () { if (token === state.panToken) renderResources(fyKeep().concat(panSouKeep()).concat(zn4kKeep()).concat(qwKeep())); }); +} +/* 盘链/其它来源条目:按标题去蜗牛搜同名,把它的网盘链接并进「网盘资源」 */ +function wnEnrich(rawTitle, token) { + wnEnrichDo(rawTitle, token); +} +function wnEnrichDo(rawTitle, token) { + var kw = cleanTitle(rawTitle); + if (!kw) return; + var year = yearOf(rawTitle); + wnSearch(kw).then(function (list) { + if (token !== state.panToken || !list.length) return; + var best = null, bestScore = 99; + list.forEach(function (it) { + var ts = titleScore(it.title, kw); if (ts > 3) return; + var s = ts; if (year && (it.title || "").indexOf(year) >= 0) s -= 0.3; + if (s < bestScore) { bestScore = s; best = it; } + }); + if (!best) return; + wnHttpText(wnBase() + "/voddetail/" + best._wnId + "/").then(function (html) { + if (token !== state.panToken) return; + var d = wnParseDetail(html, best._wnId); + var pans = (d && d.pans) || []; + if (!pans.length) return; + var have = {}; + state.panItems.forEach(function (it) { have[it.type + "|" + normPanUrl(it.url || it.token)] = 1; }); + var add = pans.filter(function (p) { return !have[p.type + "|" + normPanUrl(p.url)]; }); + if (add.length) renderResources(state.panItems.concat(add)); + }, function () {}); // 蜗牛4K 不进反查诊断(诊断只显示 pomo) + }, function () {}); +} +/* 首页「蜗牛4K」一行:探测可达才显示 */ +/* 元素滚动到快可见时才执行 fn(懒加载)。用于首页靠下的网盘站行—— + 一进主页就并发抓十几个站会挤爆带宽、拖慢首屏,这里改成滑到附近才请求。 */ +function lazyWhenVisible(el, fn) { + if (!el || typeof IntersectionObserver === "undefined") { fn(); return; } + var done = false; + var io = new IntersectionObserver(function (entries) { + for (var i = 0; i < entries.length; i++) { + if (entries[i].isIntersecting && !done) { done = true; io.disconnect(); fn(); return; } + } + }, { rootMargin: "600px 0px" }); // 提前 600px 预加载,滑到时已就绪 + io.observe(el); +} +function renderWnRow(box) { + var shell = makeRowShell("蜗牛", function () { openWnCategory("1", "蜗牛"); }); + box.appendChild(shell.section); + var cached = state.wnCache["1"]; + var hasCache = !!(cached && cached.length); + if (hasCache) fillRow(shell.row, cached.slice(0, 12), function (it) { openDetail(it); }); + else shell.row.replaceChildren(el("div", "mh-row-empty", "加载中…")); + var load = function () { + if (!document.body.contains(shell.row)) return null; + return wnFetchList("1", 1).then(function (list) { + if (!document.body.contains(shell.row)) return; + if (!list.length) { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "暂无数据")); return; } + state.wnCache["1"] = list; saveHomeDisk(); + fillRow(shell.row, list.slice(0, 12), function (it) { openDetail(it); }); + }, function () { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "加载失败")); }); + }; + if (hasCache) { if (homeCacheStale()) homePrefetch(load); return; } + rowLoad(shell.section, load); +} +function openWnCategory(t, name) { + saveHomeReturn(); + state.view = "category"; + $("homeView").style.display = "none"; $("catView").style.display = ""; + $("catTitle").textContent = name || "蜗牛"; + $("clearSearch").style.display = "none"; $("clearRecent").style.display = "none"; + setNav(null); updateHeroVisible(); window.scrollTo(0, 0); + navigate("category"); + state.searchMode = false; state.searchKw = ""; + state.activeCat = WN_CAT_SLUG; state.wnCat = t || "1"; state.activeGenre = ""; + $("genreChips").style.display = ""; renderWnGenreChips(); + wnLoadCat(true); +} +function renderWnGenreChips() { + var box = $("genreChips"); if (!box) return; + var frag = document.createDocumentFragment(); + WN_CATS.forEach(function (c, i) { + var b = document.createElement("button"); + b.type = "button"; + b.className = "genre-chip focusable" + (state.wnCat === c.t ? " active" : ""); + b.textContent = c.name; b.setAttribute("data-genre", c.name); + b.setAttribute("data-focus-row", "genreChips"); b.setAttribute("data-focus-index", String(i)); + b.onclick = (function (cc) { return function () { state.wnCat = cc.t; renderWnGenreChips(); window.scrollTo(0, 0); wnLoadCat(true); }; })(c); + frag.appendChild(b); + }); + box.replaceChildren(frag); +} +function wnLoadCat(reset) { + var t = state.wnCat || "1"; + if (reset) { + state.reqToken++; + state.wnPage = 1; state.source = []; state.rendered = 0; state.noMore = false; state.loading = false; state.autoPages = 0; + $("grid").replaceChildren(); $("grid").classList.remove("grid-recent"); $("browseNotice").replaceChildren(); + var c = state.wnCache[t]; + if (c && c.length) { wnPaintCat(c, state.reqToken, 1); return; } + } + if (state.loading || state.noMore) return; + var token = state.reqToken, page = reset ? 1 : (state.wnPage + 1); + state.loading = true; $("browseLoading").style.display = "block"; if (reset) $("browseSub").textContent = "加载中"; + wnFetchList(t, page).then(function (list) { + state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + if (page === 1) state.wnCache[t] = list; + wnPaintCat(list, token, page); + }, function () { + state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; state.noMore = true; + if (!state.source.length) $("grid").replaceChildren(el("div", "mh-row-empty", "加载失败,返回重试")); + }); +} +function wnPaintCat(list, token, page) { + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + var have = {}; state.source.forEach(function (s) { have[s.id] = 1; }); + var fresh = (list || []).filter(function (it) { return !have[it.id]; }); + if (!fresh.length) { state.noMore = true; if (!state.source.length) { $("grid").replaceChildren(el("div", "mh-row-empty", "该分类暂无内容")); $("browseSub").textContent = ""; } return; } + state.wnPage = page; state.source = state.source.concat(fresh); appendGrid(); + $("browseSub").textContent = state.source.length + " 部"; +} + +/* ============================================================ + 森中有林 / SeedHub(sidhub.cc)集成:影视&动漫网盘分享站,跟蜗牛一个路子(网盘资源站, + 不在线播放)。区别在网盘链接是两跳——详情页 .pan-links 里给的是中转页 + /link_start/?redirect_to=pan_id_xxx&movie_title=... ,要请求它才拿到真实网盘分享链接。 + 详情 /movies/{id}/;卡片 ul.pictures>li>a[href*='/movies/']。首页一行 + 搜索合并 + 详情网盘资源。 + ============================================================ */ +var SH_DEFAULT_BASE = "https://sidhub.cc"; +var SH_CAT_SLUG = "__sh__"; +var SH_UA = "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36"; +function shBase() { return trimSlash(config.shBase || SH_DEFAULT_BASE); } +/* SeedHub 分类:/categories/{id}/movies/ */ +var SH_CATS = [ + { id: "1", name: "电影" }, + { id: "3", name: "剧集" }, + { id: "2", name: "动漫" } +]; +function shHttpText(url) { return httpTextMemo("sh|" + url, function () { return shHttpTextRaw(url); }); } +function shHttpTextRaw(url) { + if (state.sdkReady && window.fm && fm.req) { + return fm.req(url, { method: "GET", responseType: "text", timeout: 20, headers: { "User-Agent": SH_UA, "Referer": shBase() + "/" } }) + .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.body; }); + } + return fetchTimeout(url, { headers: {} }).then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); }); +} +function shParseDoc(html) { + try { return new DOMParser().parseFromString(String(html).replace(//gi, "").replace(//gi, ""), "text/html"); } + catch (e) { return null; } +} +function shAbs(u) { if (!u) return ""; if (/^https?:/i.test(u)) return u; if (u.indexOf("//") === 0) return "https:" + u; return shBase() + (u.charAt(0) === "/" ? "" : "/") + u; } +/* 列表/搜索/推荐卡片 -> 影片对象。卡片是 ul.pictures>li>a[href*='/movies/'] + img + 标题 */ +function shParseList(html) { + var doc = shParseDoc(html); + if (!doc) return []; + var out = [], seen = {}; + Array.from(doc.querySelectorAll("a[href*='/movies/']")).forEach(function (a) { + var m = (a.getAttribute("href") || "").match(/\/movies\/(\d+)\//); + if (!m) return; + var id = m[1]; if (seen[id]) return; + // 卡片里要有图才算(排除面包屑/锚点) + var scope = a.closest("li") || a.parentElement || a; + var img = a.querySelector("img") || (scope ? scope.querySelector("img") : null); + if (!img) return; + var poster = img.getAttribute("data-original") || img.getAttribute("data-src") || img.getAttribute("src") || ""; + var title = sanitizeSiteTitle((a.getAttribute("title") || img.getAttribute("alt") || "")); + if (!title) { var td = scope ? scope.querySelector("div a, .title, h2, h3") : null; title = td ? td.textContent.trim() : ""; } + title = sanitizeSiteTitle(title.replace(/\s*-\s*SeedHub.*$/i, "")); + if (!title) return; + seen[id] = 1; + out.push({ id: "sh_" + id, title: title, poster: shAbs(poster), meta: "", _sh: true, _shId: id }); + }); + return out; +} +/* 详情页 -> 信息 + 网盘中转链接(.pan-links)。真实链接需再请求 /link_start 解析。 */ +function shParseDetail(html, id) { + var doc = shParseDoc(html); + if (!doc) return null; + var mt = doc.querySelector("meta[name='description']"); + var overview = mt ? (mt.getAttribute("content") || "") : ""; + var h1 = doc.querySelector("h1"); + var title = (h1 ? h1.textContent : "").replace(/\s*-\s*SeedHub.*$/i, "").trim(); + var cover = doc.querySelector("meta[property='og:image']"); + var poster = cover ? cover.getAttribute("content") : ""; + if (!poster) { var im = doc.querySelector(".cover img, #cover img, .pictures img"); poster = im ? (im.getAttribute("src") || "") : ""; } + var links = [], perType = {}, seenHref = {}; + function addLink(href, type, label) { + if (!href || seenHref[href]) return; + perType[type] = (perType[type] || 0); + if (perType[type] >= 20) return; // 每种网盘最多 20 条,够用又不至于几百条卡死 + perType[type]++; seenHref[href] = 1; + links.push({ transit: shAbs(href), type: type, label: label }); + } + // 网盘分享链接(夸克/百度/UC/迅雷等,走 /link_start?redirect_to=pan_id_xxx 中转) + Array.from(doc.querySelectorAll("ul.pan-links li a[href*='link_start']")).forEach(function (a) { + var href = a.getAttribute("href") || ""; + var dl = a.getAttribute("data-link") || ""; + var type = shDiskType(dl) || shDiskTypeFromText(a.getAttribute("title") || "") || "http"; + addLink(href, type, (a.getAttribute("title") || a.textContent || "").trim()); + }); + // 磁力/BT 种子(.seeds 区块,走 /link_start?seed_id=xxx 中转到 magnet) + Array.from(doc.querySelectorAll("ul.seeds li a[href*='link_start'], ul.seeds li a[href^='magnet']")).forEach(function (a) { + var href = a.getAttribute("href") || ""; + addLink(href, "magnet", (a.getAttribute("title") || a.textContent || "").trim()); + }); + return { title: title, poster: shAbs(poster), overview: overview, links: links }; +} +function shDiskType(dl) { + var s = String(dl || ""); + if (/quark/i.test(s)) return "quark"; + if (/baidu/i.test(s)) return "baidu"; + if (/uc\.cn/i.test(s)) return "uc"; + if (/xunlei/i.test(s)) return "xunlei"; + if (/aliyun|alipan/i.test(s)) return "aliyun"; + if (/189/i.test(s)) return "tianyi"; + if (/123/i.test(s)) return "123"; + if (/115/i.test(s)) return "115"; + if (/139|caiyun/i.test(s)) return "mobile"; + if (/guangya/i.test(s)) return "guangya"; + return ""; +} +function shDiskTypeFromText(t) { + t = String(t || ""); + if (/夸克/.test(t)) return "quark"; if (/百度/.test(t)) return "baidu"; if (/迅雷/.test(t)) return "xunlei"; + if (/UC/i.test(t)) return "uc"; if (/阿里/.test(t)) return "aliyun"; if (/天翼/.test(t)) return "tianyi"; + if (/夸克/.test(t)) return "quark"; return ""; +} +/* 请求中转页拿真实网盘链接。SeedHub 中转页结构(已按真实页面对齐): + 真实链接在 ;提取码在 .tqm-value(百度)/.tqmxl-value(迅雷), + UC/夸克通常免提取码这些 span 为空。返回 { url, password }。 */ +function shResolveTransit(transitUrl) { + return shHttpText(transitUrl).then(function (html) { + var doc = shParseDoc(html); + var url = "", pwd = ""; + if (doc) { + var a = doc.querySelector("a.direct-pan[href], a.direct-link[href], a[href^='magnet:'], a[href*='uc.cn'], a[href*='quark'], a[href*='baidu']"); + if (a) url = a.getAttribute("href") || ""; + var pv = doc.querySelector(".tqm-value, .tqmxl-value, .tqm3-value"); + if (pv) { var t = (pv.textContent || "").trim(); if (/^[0-9a-zA-Z]{2,8}$/.test(t)) pwd = t; } + } + if (!url) { var mg = String(html).match(/magnet:\?xt=urn:[a-z0-9:]+[^\s"'<>]*/i); if (mg) url = mg[0]; } + if (!url) url = shFindPanUrl(html); // 兜底:正则扫明链 + if (!url) { + var mm = String(html).replace(/\\\//g, "/").match(/(?:location\.href\s*=\s*['"]|url=|href=['"])(https?:\/\/[^\s"'<>]+)/i); + if (mm && shDiskTypeFromUrl(mm[1])) url = mm[1]; + } + if (!pwd && url) { var pm = url.match(/[?&](?:pwd|password|code)=([0-9a-zA-Z]+)/); if (pm) pwd = pm[1]; } + return url ? { url: url, password: pwd } : ""; + }, function () { return ""; }); +} +function shDiskTypeFromUrl(u) { + if (/^magnet:/i.test(u)) return "magnet"; + for (var i = 0; i < HOSTMAP.length; i++) if (HOSTMAP[i][0].test(u)) return HOSTMAP[i][1]; + if (/guangyapan\.com/i.test(u)) return "guangya"; + return ""; +} +function shFindPanUrl(html) { + var m = String(html).replace(/\\\//g, "/").match(/https?:\/\/(?:pan\.quark\.cn|pan\.baidu\.com|drive\.uc\.cn|fast\.uc\.cn|pan\.xunlei\.com|(?:www\.)?alipan\.com|aliyundrive\.com|cloud\.189\.cn|123[0-9a-z]*\.com|115\.com|caiyun\.139\.com|yun\.139\.com|guangyapan\.com)\/[^\s"'<>]+/i); + return m ? m[0] : ""; +} +function shListUrls(cat, page) { + var b = shBase(), c = cat || "1"; + if (!page || page <= 1) return [b + "/categories/" + c + "/movies/"]; + return [b + "/categories/" + c + "/movies/?page=" + page]; +} +function shFetchList(cat, page) { + var urls = shListUrls(cat, page); + function tryUrl(i) { + if (i >= urls.length) return Promise.resolve([]); + return shHttpText(urls[i]).then(function (html) { var list = shParseList(html); return list.length ? list : tryUrl(i + 1); }, function () { return tryUrl(i + 1); }); + } + return tryUrl(0); +} +function shSearch(kw) { + var b = shBase(); + var urls = [b + "/?s=" + encodeURIComponent(kw), b + "/search/" + encodeURIComponent(kw) + "/", b + "/search/?q=" + encodeURIComponent(kw)]; + function tryUrl(i) { + if (i >= urls.length) return Promise.resolve([]); + return shHttpText(urls[i]).then(function (html) { var list = shParseList(html); return list.length ? list : tryUrl(i + 1); }, function () { return tryUrl(i + 1); }); + } + return tryUrl(0); +} +function shKeep() { return (state.panItems || []).filter(function (it) { return it._sh; }); } +/* 把 SeedHub 详情的中转链接解析成真实网盘链接,并进「网盘资源」。 + 并发解析(一次最多 6 个),每解出一条就渐进渲染进去,不必等全部解完——快且有反馈。 */ +function shResolveAndMerge(links, token, resetBase) { + var pending = (links || []).slice(); + var CONC = 8, active = 0; + function haveKey(p) { return p.type + "|" + normPanUrl(p.url); } + function mergeOne(p) { + if (token !== state.panToken) return; + var have = {}; + state.panItems.forEach(function (it) { have[it.type + "|" + normPanUrl(it.url || it.token)] = 1; }); + if (have[haveKey(p)]) return; + renderResources(state.panItems.concat([p])); // 渐进:解出一条就并进去 + } + function pump() { + if (token !== state.panToken) return; + while (active < CONC && pending.length) { + var lk = pending.shift(); active++; + shResolveTransit(lk.transit).then(function (cap) { + return function (real) { + active--; + if (token !== state.panToken) return; + var realUrl = real && real.url ? real.url : ""; + if (realUrl) { + var type = shDiskTypeFromUrl(realUrl) || cap.type || "http"; + var pwd = (real.password || "") || (realUrl.match(/[?&](?:pwd|password|code)=([0-9a-zA-Z]+)/) || [])[1] || ""; + mergeOne({ + type: type, url: realUrl, password: pwd, title: cap.label || "", + groupName: (PAN_META[type] ? PAN_META[type].label : type), quality: (PAN_META[type] ? PAN_META[type].label : type), + source: "SeedHub", state: "idle", _sh: true + }); + } + pump(); + }; + }(lk), function () { active--; pump(); }); + } + } + pump(); +} +function shLoadById(id, token) { + shHttpText(shBase() + "/movies/" + id + "/").then(function (html) { + if (token !== state.panToken) return; + var d = shParseDetail(html, id); + if (d && state.currentDetail && !state.currentDetail.tmdb) { + if (d.title) { state.currentDetail.rawTitle = d.title; showTitle(d.title); } + var ovv = $("detailOverview"); if (ovv && !ovv.textContent && !(state.currentDetail && state.currentDetail.tmdb) && d.overview && !isJunkOverview(d.overview)) { ovv.textContent = d.overview; state.currentDetail.overviewFromSite = true; } + if (d.poster && !state.currentDetail.poster && !(state.currentDetail && state.currentDetail.tmdb)) { state.currentDetail.poster = d.poster; setDetailHeroImages([d.poster], false); } + } + var links = (d && d.links) || []; + if (!links.length) { renderResources(fyKeep().concat(panSouKeep()).concat(zn4kKeep()).concat(qwKeep())); return; } + renderResources(fyKeep().concat(panSouKeep()).concat(zn4kKeep()).concat(qwKeep())); // 先出兜底,再逐条解析中转链并入 + shResolveAndMerge(links, token, true); + }, function () { if (token === state.panToken) renderResources(fyKeep().concat(panSouKeep()).concat(zn4kKeep()).concat(qwKeep())); }); +} +/* 其它来源条目:按标题去 SeedHub 搜同名,把它的网盘链接并进「网盘资源」 */ +function shEnrich(rawTitle, token) { + var kw = cleanTitle(rawTitle); if (!kw) return; + var year = yearOf(rawTitle); + shSearch(kw).then(function (list) { + if (token !== state.panToken || !list.length) return; + var best = null, bestScore = 99; + list.forEach(function (it) { var ts = titleScore(it.title, kw); if (ts > 3) return; var sc = ts; if (year && (it.title || "").indexOf(year) >= 0) sc -= 0.3; if (sc < bestScore) { bestScore = sc; best = it; } }); + if (!best) return; + shHttpText(shBase() + "/movies/" + best._shId + "/").then(function (html) { + if (token !== state.panToken) return; + var d = shParseDetail(html, best._shId); + if (d && d.links && d.links.length) shResolveAndMerge(d.links, token, false); + }, function () {}); + }, function () {}); +} +/* 首页「SeedHub」一行 */ +function renderShRow(box) { + var shell = makeRowShell("SeedHub", function () { openShCategory("1", "SeedHub"); }); + box.appendChild(shell.section); + var cached = state.shCache["1"]; + var hasCache = !!(cached && cached.length); + if (hasCache) fillRow(shell.row, cached.slice(0, 12), function (it) { openDetail(it); }); + else shell.row.replaceChildren(el("div", "mh-row-empty", "加载中…")); + var load = function () { + if (!document.body.contains(shell.row)) return null; + return shFetchList("1", 1).then(function (list) { + if (!document.body.contains(shell.row)) return; + if (!list.length) { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "暂无数据")); return; } + state.shCache["1"] = list; saveHomeDisk(); + fillRow(shell.row, list.slice(0, 12), function (it) { openDetail(it); }); + }, function () { if (!hasCache) shell.row.replaceChildren(el("div", "mh-row-empty", "加载失败")); }); + }; + if (hasCache) { if (homeCacheStale()) homePrefetch(load); return; } + rowLoad(shell.section, load); +} +function openShCategory(cat, name) { + saveHomeReturn(); + state.view = "category"; + $("homeView").style.display = "none"; $("catView").style.display = ""; + $("catTitle").textContent = name || "SeedHub"; + $("clearSearch").style.display = "none"; $("clearRecent").style.display = "none"; + setNav(null); updateHeroVisible(); window.scrollTo(0, 0); + navigate("category"); + state.searchMode = false; state.searchKw = ""; + state.activeCat = SH_CAT_SLUG; state.shCat = cat || "1"; state.activeGenre = ""; + $("genreChips").style.display = ""; renderShGenreChips(); + shLoadCat(true); +} +function renderShGenreChips() { + var box = $("genreChips"); if (!box) return; + var frag = document.createDocumentFragment(); + SH_CATS.forEach(function (c, i) { + var b = document.createElement("button"); + b.type = "button"; + b.className = "genre-chip focusable" + (state.shCat === c.id ? " active" : ""); + b.textContent = c.name; b.setAttribute("data-genre", c.name); + b.setAttribute("data-focus-row", "genreChips"); b.setAttribute("data-focus-index", String(i)); + b.onclick = (function (cc) { return function () { state.shCat = cc.id; renderShGenreChips(); window.scrollTo(0, 0); shLoadCat(true); }; })(c); + frag.appendChild(b); + }); + box.replaceChildren(frag); +} +function shLoadCat(reset) { + var cat = state.shCat || "1"; + if (reset) { + state.reqToken++; + state.shPage = 1; state.source = []; state.rendered = 0; state.noMore = false; state.loading = false; state.autoPages = 0; + $("grid").replaceChildren(); $("grid").classList.remove("grid-recent"); $("browseNotice").replaceChildren(); + var c = state.shCache[cat]; + if (c && c.length) { shPaintCat(c, state.reqToken, 1); return; } + } + if (state.loading || state.noMore) return; + var token = state.reqToken, page = reset ? 1 : (state.shPage + 1); + state.loading = true; $("browseLoading").style.display = "block"; if (reset) $("browseSub").textContent = "加载中"; + shFetchList(cat, page).then(function (list) { + state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + if (page === 1) state.shCache[cat] = list; + shPaintCat(list, token, page); + }, function () { + state.loading = false; + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; state.noMore = true; + if (!state.source.length) $("grid").replaceChildren(el("div", "mh-row-empty", "加载失败,返回重试")); + }); +} +function shPaintCat(list, token, page) { + if (token !== state.reqToken) return; + $("browseLoading").style.display = "none"; + var have = {}; state.source.forEach(function (s) { have[s.id] = 1; }); + var fresh = (list || []).filter(function (it) { return !have[it.id]; }); + if (!fresh.length) { state.noMore = true; if (!state.source.length) { $("grid").replaceChildren(el("div", "mh-row-empty", "该分类暂无内容")); $("browseSub").textContent = ""; } return; } + state.shPage = page; state.source = state.source.concat(fresh); appendGrid(); + $("browseSub").textContent = state.source.length + " 部"; +} + +function fyEnrich(rawTitle, token) { + fyEnrichDo(rawTitle, token); +} + +/* ============================================================ + 玩偶 / 木偶(MacCMS 网盘明链站,结构同原盘高清)。两站结构完全一致, + 只是域名/分类不同,用一个通用构造器生成,避免重复代码。 + 详情页网盘链接是明链(整页正则扫出夸克/百度/UC/迅雷/阿里/115/123/天翼/移动/磁力)。 + ============================================================ */ +var WO_SITES = { + wo: { + key: "wo", name: "玩偶", slug: "__wo__", + domains: ["https://wogg.xxooo.cf", "https://www.wogg.net"], + categoryUrl: "/vodshow/{cat}--------{page}---.html", // 玩偶专用伪静态 + // 玩偶站的搜索地址是已验证的固定格式(同 wogg 里的配置):直接给出,woSearch 就会走 + // "own" 分支只打这一个地址,不用先猜 ajax/suggest 接口在不在、也不用把 6 种通用格式 + // 挨个试一遍(那样跨 2 个域名 × 6 种格式,最坏情况会拖到超过 22 秒的搜索预算, + // 表现就是"搜了但一直不出玩偶结果")。 + searchUrls: ["/vodsearch/-------------.html?wd={kw}&page=1"], + cats: [["44", "臻彩"], ["1", "电影"], ["2", "电视剧"], ["3", "动漫"], ["4", "综艺"], ["6", "短剧"], ["46", "纪录片"]] + }, + mo: { + key: "mo", name: "木偶", slug: "__mo__", + domains: ["https://666.666291.xyz"], + // 木偶没有专用 categoryUrl,走下面的通用候选(MacCMS10 index.php 优先) + cats: [["25", "臻选"], ["1", "电影"], ["2", "电视剧"], ["3", "动漫"], ["4", "纪录片"], ["29", "综艺"], ["30", "原盘"]] + }, + pm: { + key: "pm", name: "pomo", slug: "__pm__", + domains: ["https://pomo.mom"], + categoryUrl: function (cat, page) { // 首页无后缀,翻页加 /page/N + var base = cat ? ("/" + cat) : ""; + return page > 1 ? [base + "/page/" + page] : [base + "/", base || "/"]; + }, + searchUrls: ["/?keyword={kw}"], + cats: [["", "最新"], ["huayurm", "华语热门"], ["jiating", "家庭影院"], + ["donghuadadiany", "动画电影"], ["lengmenjiapian", "冷门佳片"], + ["paihangbang", "TOP250"], ["dianshiju", "电视剧"]] + } +}; +/* 所有网盘明链站,按定义顺序 */ +function woAll() { var a = [], k; for (k in WO_SITES) if (WO_SITES.hasOwnProperty(k)) a.push(WO_SITES[k]); return a; } +var WO_UA = "Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36"; +var WO_PAN_RE = /https?:\/\/(?:[\w.-]*\.)?(?:pan\.baidu\.com|pan\.quark\.cn|drive\.uc\.cn|fast\.uc\.cn|aliyundrive\.com|alipan\.com|aliyunpan\.com|cloud\.189\.cn|115\.com|115cdn\.[a-z0-9]+|123[a-z0-9]*\.(?:com|cn|net)|caiyun\.139\.com|yun\.139\.com|pan\.xunlei\.com|mypikpak\.com|guangyapan\.com)\/[A-Za-z0-9\-_/?=&%.#~+]*/gi; +/* "别的片子"的区块名(整词匹配,见 inOtherBlock 的注释) */ +var WO_OTHER_BLOCK_RE = /(?:^|[-_])(?:related|relates|relate|recommend|recommends|recommended|guess|guesslike|tuijian|xgvod|sidebar|footer|comment|comments)(?:$|[-_])/i; +/* 当前生效域名:优先用设置里手填的;否则用上次探测成功的;再否则第一个候选 */ +function woBase(site) { + var custom = config.woBases && config.woBases[site.key]; + if (custom) return trimSlash(custom); + if (state.woGood[site.key]) return trimSlash(state.woGood[site.key]); + return trimSlash(site.domains[0]); +} +/* 该站所有候选域名(生效的排最前) */ +function woDomains(site) { + var list = []; + var cur = woBase(site); + list.push(cur); + site.domains.forEach(function (d) { if (trimSlash(d) !== cur) list.push(trimSlash(d)); }); + return list; +} +/* 连不上(超时/网络级失败)的域名短期黑名单。 + 同一次搜索里,没有专属搜索地址的站(玩偶/木偶)会拿 6 种猜测格式反复打同几个域名; + 若某域名根本连不通,第一种格式打一次就记下来,后面 5 种格式直接跳过它, + 避免"6 格式 × 死域名 × 15 秒"层层叠加成几分钟。 + 关键:只有连接级失败(超时/网络)才拉黑;HTTP 404 / 空页 / 人机校验挡板都说明 + 域名是活的、只是这个路径/格式不对,绝不能因此拉黑域名。 */ +var WO_BAD_TTL = 90 * 1000; +function woDomainBad(dom) { var t = state.woBad && state.woBad[dom]; return !!(t && (Date.now() - t) < WO_BAD_TTL); } +function woMarkBad(dom) { if (!state.woBad) state.woBad = {}; state.woBad[dom] = Date.now(); } +function woClearBad(dom) { if (state.woBad && state.woBad[dom]) delete state.woBad[dom]; } +/* 给一条链一个总时间预算:不管里面几种格式、几个域名怎么试,到点就收工返回兜底值 + (搜索场景通常是空数组),个别死站最多拖到预算上限,不会把"读取中"拖成几分钟。 */ +function woWithBudget(promise, ms, fallback) { + return new Promise(function (resolve) { + var done = false; + var timer = setTimeout(function () { if (!done) { done = true; resolve(fallback); } }, ms); + promise.then( + function (v) { if (!done) { done = true; clearTimeout(timer); resolve(v); } }, + function () { if (!done) { done = true; clearTimeout(timer); resolve(fallback); } } + ); + }); +} +/* 请求:给定"路径",自动在候选域名间切换,哪个通用哪个,并记住成功的域名 */ +function woHttpPath(site, path) { return httpTextMemo("wo|" + site.key + "|" + path, function () { return woHttpPathRaw(site, path); }); } +function woDiag(line) { + if (!state.woDiagList) state.woDiagList = []; + if (state.woDiagList.length < 12) state.woDiagList.push(line); +} +function woHttpPathRaw(site, path) { + var all = woDomains(site); + // 跳过近期确认连不上的域名;但若全被拉黑了,仍整批试一遍,避免误杀(比如唯一域名、或黑名单过期边界) + var live = all.filter(function (d) { return !woDomainBad(d); }); + var domains = live.length ? live : all; + function tryOne(i) { + if (i >= domains.length) return Promise.reject(new Error("all domains failed")); + var dom = domains[i]; + var url = dom + path; + var req; + if (state.sdkReady && window.fm && fm.req) { + req = fm.req(url, { method: "GET", responseType: "text", timeout: 15, credentials: "include", + headers: { "User-Agent": WO_UA, "Referer": dom + "/" } }) + .then(function (r) { if (!r.ok) { var e = new Error("HTTP " + r.status); e.woAlive = true; throw e; } return r.body; }); + // r.ok=false 是服务器有应答(域名活着,只是这个路径不对)→ 标记 woAlive,不拉黑域名 + } else { + req = fetchTimeout(url, { headers: {} }).then(function (r) { if (!r.ok) { var e = new Error("HTTP " + r.status); e.woAlive = true; throw e; } return r.text(); }); + } + return req.then(function (html) { + var guard = html && /_guard\/|easy_click|jsl_clearance|检测中|请稍候/i.test(html); + woDiag(path + " " + (html ? html.length + "字节" : "空") + (guard ? " ← 人机校验挡板页" : "") + + (html && html.length < 400 && !guard ? ("\n 内容:" + String(html).replace(/\s+/g, " ").slice(0, 200)) : "")); + if (guard) woDiag(" 该站需先在 App 内置浏览器打开一次通过校验,之后本页会复用其 Cookie"); + if (!html || html.length < 300) { var e = new Error("empty"); e.woAlive = true; throw e; } // 有应答但内容空/太短:域名活着,路径不对 + state.woGood[site.key] = dom; // 记住能用的域名 + woClearBad(dom); // 之前若误判过,这次通了就洗白 + return { html: html, base: dom }; + }, function (e) { + woDiag(path + " 失败 " + ((e && e.message) || "未知")); + // 只有连接级失败(超时 / 网络 / 无应答)才拉黑域名;有应答的 HTTP/空页错误(woAlive)不拉黑。 + if (!(e && e.woAlive)) woMarkBad(dom); + return tryOne(i + 1); + }); + } + return tryOne(0); +} +/* 兼容旧调用:woHttp(site, 完整url) -> 只取 path 再走多域名 */ +function woHttp(site, url) { + var path = url; + woDomains(site).forEach(function (d) { if (url.indexOf(d) === 0) path = url.slice(d.length); }); + if (/^https?:/i.test(path)) { var m = path.match(/^https?:\/\/[^/]+(\/.*)$/); path = m ? m[1] : "/"; } + return woHttpPath(site, path).then(function (r) { return r.html; }); +} +function woDoc(html) { + try { return new DOMParser().parseFromString(String(html).replace(//gi, "").replace(//gi, ""), "text/html"); } + catch (e) { return null; } +} +function woAbs(site, u) { if (!u) return ""; if (/^https?:/i.test(u)) return u; if (u.indexOf("//") === 0) return "https:" + u; return woBase(site) + (u.charAt(0) === "/" ? "" : "/") + u; } +function woDiskType(u) { + if (/^magnet:/i.test(u)) return "magnet"; + for (var i = 0; i < HOSTMAP.length; i++) if (HOSTMAP[i][0].test(u)) return HOSTMAP[i][1]; + if (/guangyapan\.com/i.test(u)) return "guangya"; + if (/^https?:/i.test(u)) return "http"; + return ""; +} +function woParseList(site, html, base) { + var d = woDoc(html); if (!d) return []; + var out = [], seen = {}; + var els = d.querySelectorAll(".module-item, .module-search-item, #main .module-item, .module-items .module-item, .stui-vodlist__box, ul.stui-vodlist > li, .public-list-box, .video-info, ul.pic-list > li, .sr_lists > ul > li, .bg-cardbg"); + Array.from(els).forEach(function (el) { + // 照 wogg 的 cardFrom:取任意第一个 ,不假设 /voddetail/ 或 index.php 格式 + var a = el.querySelector("a[href]") || (el.closest ? el.closest("a[href]") : null) || el; + var href = a && a.getAttribute ? (a.getAttribute("href") || "") : ""; + if (!href || href.charAt(0) === "#") return; + var abs = woAbs2(base || woBase(site), href); + if (seen[abs]) return; + var img = el.querySelector("img"); + var poster = img ? (img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || "") : ""; + var tEl = el.querySelector(".module-item-title, .module-poster-item-title, .module-search-item-title, .video-title, .title, .video-name, h3 b, h3"); + var title = sanitizeSiteTitle((a && a.getAttribute && a.getAttribute("title")) || (img && img.getAttribute("alt")) || (tEl ? tEl.textContent : "") || (a ? a.textContent : "") || ""); + if (!title) return; + seen[abs] = 1; + out.push({ id: site.key + "_" + (out.length + 1) + "_" + Date.now(), title: title.slice(0, 60), poster: woAbs2(base || woBase(site), poster), meta: "", _wo: true, _woSite: site.key, _woHref: abs }); + }); + // 兜底:pomo 这类自定义主题(纯 Tailwind 工具类,没有 module-item / stui-vodlist 这些 + // MacCMS 约定类名)上面一条都选不中。改按结构认卡片:带海报图的站内链接就是一张卡。 + if (!out.length) out = woParseListLoose(site, d, base); + return out; +} +/* 结构化兜底解析: 里包一张 就当作一张影片卡。 + 标题优先 a[title] -> img[alt] -> 卡片内的 h1~h4/.title。 */ +function woParseListLoose(site, d, base) { + var out = [], seen = {}; + var rootBase = trimSlash(base || woBase(site)); + var imgs = d.querySelectorAll("a[href] img, a[href] > img"); + Array.from(imgs).forEach(function (img) { + var a = img.closest ? img.closest("a[href]") : null; + if (!a) return; + var href = a.getAttribute("href") || ""; + if (!href || href.charAt(0) === "#" || /^(javascript|mailto|tel):/i.test(href)) return; + var abs = woAbs2(rootBase, href); + var path = ""; + try { path = new URL(abs).pathname; } catch (e) { path = abs.replace(/^https?:\/\/[^/]+/, ""); } + if (!path || path === "/") return; // 站标 / 回首页的链接 + if (/\.(jpg|jpeg|png|webp|gif|css|js)$/i.test(path)) return; + if (/^\/(?:page|tag|search|login|register|user|about)\b/i.test(path)) return; + if (seen[abs]) return; + var poster = img.getAttribute("data-src") || img.getAttribute("data-original") || img.getAttribute("src") || ""; + if (/logo|avatar|icon|placeholder/i.test(poster)) return; // 站标/头像不是海报 + var card = a.parentElement || a; + var tEl = a.querySelector("h1,h2,h3,h4,.title,.name") || card.querySelector("h1,h2,h3,h4,.title,.name"); + var title = sanitizeSiteTitle(a.getAttribute("title") || img.getAttribute("alt") || + (tEl ? tEl.textContent : "") || a.textContent || ""); + if (!title) return; + seen[abs] = 1; + out.push({ id: site.key + "_L" + (out.length + 1) + "_" + Date.now(), title: title.slice(0, 60), + poster: woAbs2(rootBase, poster), meta: "", _wo: true, _woSite: site.key, _woHref: abs }); + }); + return out; +} +function woAbs2(base, u) { if (!u) return ""; if (/^https?:/i.test(u)) return u; if (u.indexOf("//") === 0) return "https:" + u; try { return new URL(u, trimSlash(base) + "/").href; } catch (e) { return trimSlash(base) + (u.charAt(0) === "/" ? "" : "/") + u; } } +function woParseDetail(site, html) { + var d = woDoc(html); + var title = "", poster = "", overview = ""; + if (d) { + var h1 = d.querySelector(".module-info-heading h1, .video-info-header h1, h1, .movie-title, h2"); + title = h1 ? h1.textContent.trim() : ""; + var pic = d.querySelector(".module-info-poster img, .module-item-pic img, .detail-pic img"); + poster = pic ? (pic.getAttribute("data-original") || pic.getAttribute("data-src") || pic.getAttribute("src") || "") : ""; + var ov = d.querySelector(".module-info-introduction-content, .module-info-content, .video-info-content, .content"); + overview = ov ? ov.textContent.replace(/\s+/g, " ").trim() : ""; + } + // 网盘链接:只在"下载/资源区块"里扫,不整页扫——整页扫会把详情页底部"相关推荐/其他影片" + // 里别的片子的网盘链接也抓进来(就是"药神详情页里混进犬夜叉全集"的原因)。 + var pans = [], seen = {}; + function pushOne(u, rowTitle, primary) { + u = String(u || "").replace(/&/g, "&").replace(/[,。、))"'<]+$/, ""); + if (!u) return; + var type = woDiskType(u); + var k = type + "|" + normPanUrl(u); + if (seen[k]) return; seen[k] = 1; + var pwd = (u.match(/[?&](?:pwd|password|code)=([0-9a-zA-Z]+)/) || [])[1] || ""; + pans.push({ + type: type, url: u, password: pwd, title: (rowTitle || "").replace(/\s+/g, " ").trim(), + groupName: (PAN_META[type] ? PAN_META[type].label : type), quality: (PAN_META[type] ? PAN_META[type].label : type), + source: site.name, state: "idle", _wo: true, _woPrimary: !!primary + }); + } + function pushFrom(scopeEl, rowTitle) { + var raw = (scopeEl ? scopeEl.innerHTML : ""); + raw = String(raw).replace(/\\\//g, "/"); + var hits = [], mm; + WO_PAN_RE.lastIndex = 0; + while ((mm = WO_PAN_RE.exec(raw))) hits.push(mm[0]); + // 磁力:不少站写在 data-url / 文本里而不是 href,所以按文本扫 + var MAG_RE = /magnet:\?xt=urn:btih:[A-Za-z0-9]{32,40}[^"'\s<>]*/gi, mg; + while ((mg = MAG_RE.exec(raw))) hits.push(mg[0]); + for (var hi = 0; hi < hits.length; hi++) pushOne(hits[hi], rowTitle, false); + } + /* 节点是否落在"相关推荐/猜你喜欢/侧栏/页脚"这类别的片子的区块里。 + 注意必须按 class 的"整个词"判断:早先用子串匹配,Tailwind 的 relative + 会被 relat 命中,导致整页都算推荐区,头部的网盘按钮就被误杀了。 */ + function inOtherBlock(n) { + for (var p = n; p && p.tagName; p = p.parentElement) { + var tag = String(p.tagName).toLowerCase(); + if (tag === "footer" || tag === "aside") return true; + var cn = p.className; + cn = (cn && cn.baseVal !== undefined) ? cn.baseVal : cn; // SVG 元素的 className 是对象 + var toks = String(cn || "").split(/\s+/).concat(String(p.id || "").split(/\s+/)); + for (var ti = 0; ti < toks.length; ti++) { + if (toks[ti] && WO_OTHER_BLOCK_RE.test(toks[ti])) return true; + } + } + return false; + } + if (d) { + // ① 显式声明的网盘链接(href / data-url 等属性里直接写着的),无论它在页面哪儿都收。 + // pomo 的"一键转存"图标按钮就挂在标题区,不在任何资源行里 —— 只扫资源行会漏掉它。 + // 这些是页面自己声明的链接,不是正文里蒙出来的,所以可信,标记 primary 不参与片名过滤。 + var ATTRS = ["href", "data-url", "data-link", "data-href", "data-clipboard-text"]; + var linkNodes = d.querySelectorAll("a[href], [data-url], [data-link], [data-href], [data-clipboard-text]"); + Array.from(linkNodes).forEach(function (n) { + if (inOtherBlock(n)) return; + for (var ai = 0; ai < ATTRS.length; ai++) { + var v = n.getAttribute(ATTRS[ai]); + if (!v) continue; + v = String(v).replace(/\\\//g, "/").trim(); + var isMag = /^magnet:\?xt=urn:btih:[A-Za-z0-9]{32,40}/i.test(v); + WO_PAN_RE.lastIndex = 0; + var isPan = WO_PAN_RE.test(v); + if (!isMag && !isPan) continue; + var t = n.getAttribute("title") || n.textContent || ""; + if (!t.trim()) { // 图标按钮没文字,往上找一层标题 + var host = n.closest ? (n.closest(".download-item") || n.closest("li") || n.parentElement) : n.parentElement; + var th = host && host.querySelector ? host.querySelector(".x-dbjs-download-link, h4, .title, strong") : null; + t = th ? th.textContent : ""; + } + pushOne(v, t, true); + } + }); + // ② 每条资源行(含自己的标题),逐行扫——顺带拿到"这条资源叫什么",好过滤不相关的 + var rows = d.querySelectorAll(".module-row-one, .module-row-info, .module-row-text, .downlist li, .down-list li, .module-blocklist .module-row-one, .download-item, .x-dbjs-accordion-inner .download-links"); + if (rows.length) { + Array.from(rows).forEach(function (row) { + var t = row.querySelector(".module-row-title, .module-row-text h4, h4, .title, strong, .x-dbjs-download-link, a[data-url]"); + pushFrom(row, t ? t.textContent : ""); + }); + } + // ③ 资源区整块(有的模板资源不在 module-row 里,而在一个下载 tab 容器里) + if (!pans.length) { + var box = d.querySelector(".module-tab-content, .module-downlist, .module-play-list, #download, .downurl, .pan-box"); + if (box) pushFrom(box, ""); + } + } + // 实在没有资源区块结构,才退回整页扫(老模板兜底) + if (!pans.length) pushFrom(d && d.body ? d.body : { innerHTML: html }, ""); + // ④ 源码兜底:有的站(pomo 就是)把网盘链接放在