Files
2026-07-02 17:50:05 +00:00

948 lines
36 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
import re
import ssl
import json
import html
import base64
import urllib3
import threading
import time
import sys
from urllib.parse import quote, unquote, urljoin, urlparse
import requests
from requests.adapters import HTTPAdapter
from pyquery import PyQuery as pq
sys.path.append("..")
from base.spider import Spider as BaseSpider
urllib3.disable_warnings()
class SSLAdapter(HTTPAdapter):
def init_poolmanager(self, connections, maxsize, block=False, **kwargs):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
kwargs["ssl_context"] = ctx
return super().init_poolmanager(connections, maxsize, block=block, **kwargs)
def proxy_manager_for(self, proxy, **kwargs):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
kwargs["ssl_context"] = ctx
return super().proxy_manager_for(proxy, **kwargs)
class Spider(BaseSpider):
hosts = [
"https://www.qwmkv.com",
"https://www.qwnull.com",
"https://www.qwfilm.com",
"https://www.qnmp4.com",
"https://www.qn63.com"
]
host = hosts[0]
headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 12; M2012K11AC) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Accept-Encoding": "gzip, deflate",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1"
}
CATEGORY_IDS = {"电影": 1, "剧集": 2, "综艺": 3, "动漫": 4, "短剧": 30}
KEYWORDS = ["杜比", "dolby", "原盘", "高码", "remux", "蓝光", "hdr10+", "hdr10", "hdr", "4k", "2160p", "uhd"]
QUARK_CHECK_LIMIT = 100
CHECK_TIME_BUDGET = 12.0 # 检测最多12秒
def getName(self):
return "七味-最终稳定版(含大屏分组排序+防串位修复)"
def init(self, extend=""):
self.session = requests.Session()
adapter = SSLAdapter(max_retries=2)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
self.session.verify = False
self.session.headers.update(dict(self.headers))
self.last_vod_pic = ""
self.vod_pic_cache = {}
self.pan_115_cookie = ""
self.ack_mp4 = "https://vd2.bdstatic.com/mda-nj5kxa8kr7wgq6ie/sc/cae_h264_nowatermark/1653272065989267185/mda-nj5kxa8kr7wgq6ie.mp4"
if extend:
try:
ext = json.loads(extend)
self.pan_115_cookie = ext.get("pan_115_cookie", "")
self.ack_mp4 = ext.get("ack_mp4", self.ack_mp4)
except Exception as e:
print(f"init extend error: {e}")
self._probe_host()
def destroy(self):
try:
self.session.close()
except:
pass
# ---------------- utils ----------------
def _probe_host(self):
for h in self.hosts:
try:
r = self.session.get(h + "/", timeout=6, headers=self.headers, verify=False)
if r.status_code == 200:
self.host = h
return
except:
pass
def _full_url(self, path):
if not path:
return ""
path = html.unescape(str(path)).strip()
if path.startswith("//"):
return "https:" + path
if path.startswith(("http://", "https://", "magnet:?")):
return path
return urljoin(self.host + "/", path)
def _full_url_by_host(self, host, path):
if not path:
return ""
path = html.unescape(str(path)).strip()
if path.startswith("//"):
return "https:" + path
if path.startswith(("http://", "https://", "magnet:?")):
return path
return urljoin(host.rstrip("/") + "/", path.lstrip("/"))
def _fetch(self, url, timeout=10):
tries = [self._full_url(url)]
if isinstance(url, str) and not url.startswith(("http://", "https://", "magnet:?")):
for h in self.hosts:
u = urljoin(h + "/", url)
if u not in tries:
tries.append(u)
for u in tries:
try:
h = dict(self.headers)
h["Referer"] = self.host + "/"
r = self.session.get(u, timeout=timeout, headers=h, verify=False)
r.encoding = r.apparent_encoding or "utf-8"
if r.status_code == 200 and len(r.text or "") > 30:
for hh in self.hosts:
if u.startswith(hh):
self.host = hh
break
return r
except:
continue
return None
def _pq(self, url, timeout=10):
r = self._fetch(url, timeout=timeout)
return pq(r.text if r else "")
def _clean_text(self, s):
return re.sub(r"\s+", " ", html.unescape(s or "")).strip()
def _clean_name(self, s, max_len=120):
s = html.unescape(s or "").replace("#", "").replace("$", "")
s = re.sub(r"\s+", " ", s).strip()
return s[:max_len]
def _img_src(self, img):
return img.attr("data-src") or img.attr("data-original") or img.attr("src") or ""
def _is_pan(self, u):
u = (u or "").lower()
return any(k in u for k in [
"pan.quark.cn/s/", "pan.baidu.com/s/", "drive.uc.cn/s/",
"pan.xunlei.com/s/", "aliyundrive.com/s/", "alipan.com/s/",
"cloud.189.cn/", "caiyun.139.com/", "123pan.com/s/",
"115.com/s/", "lanzou", "lanzoui", "lanzoux", "lanzoub"
])
def _b64e(self, obj):
txt = json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
return base64.urlsafe_b64encode(txt.encode()).decode().rstrip("=")
def _b64d(self, s):
try:
s += "=" * (-len(s) % 4)
return json.loads(base64.urlsafe_b64decode(s.encode()).decode())
except:
return {}
def _get_mid(self, tid):
if str(tid).isdigit():
return int(tid)
m = re.search(r"/vt/(\d+)", str(tid))
if m:
return int(m.group(1))
return 1
def _score_name(self, name):
n = (name or "").lower()
score = 0
for i, kw in enumerate(self.KEYWORDS):
if kw.lower() in n:
score += (len(self.KEYWORDS) - i)
return score
def _extract_video_list(self, doc):
videos, seen = [], set()
selectors = ["ul.pic-list li", "ul.content-list li", ".pic-list li", ".content-list li"]
nodes = []
for sel in selectors:
n = list(doc(sel).items())
if n:
nodes = n
break
for li in nodes:
a = li("a[href]").eq(0)
href = a.attr("href")
if not href:
continue
vid = self._full_url(href)
if vid in seen:
continue
seen.add(vid)
img = li("img").eq(0)
pic = self._full_url(self._img_src(img))
title = a.attr("title") or img.attr("alt") or li("h3 b").text() or li("h3").text() or ""
remark = self._clean_text(li("span.s1").text() or li("span.s2").text() or li("p").text() or li(".tag").text())
if pic:
self.vod_pic_cache[vid] = pic
videos.append({
"vod_id": vid,
"vod_name": self._clean_name(title, 80),
"vod_pic": pic,
"vod_remarks": remark
})
return videos
def _is_bad_cover(self, u):
if not u:
return True
s = u.lower()
return ("logo.png" in s) or ("loading" in s) or ("/template/piankuwap/image/logo" in s)
def _normalize_magnet(self, href):
try:
if not href:
return ""
href = str(href).strip().replace("&", "&")
if href.startswith("push://"):
href = href.replace("push://", "", 1).replace("#0agent", "")
if "%3A" in href or "%3F" in href or "%26" in href:
href = unquote(href)
href = re.sub(r"\s+", "", href)
if not href.startswith("magnet:") or "urn:btih:" not in href:
return ""
return href
except:
return ""
def _magnet_btih(self, magnet):
m = self._normalize_magnet(magnet)
if not m:
return ""
g = re.search(r"xt=urn:btih:([a-zA-Z0-9]+)", m, re.I)
return g.group(1).lower() if g else ""
def _is_verify_page(self, text):
t = (text or "").lower()
return (
("系统安全验证" in t) or
("verify_check" in t) or
("mac_verify_img" in t) or
("请输入验证码" in t)
)
def _mk_vod_id(self, h, raw_id, raw_url=""):
if raw_url:
u = self._full_url_by_host(h, raw_url)
if "/mv/" in u and u.endswith(".html"):
return u
if str(raw_id).isdigit():
return f"{h}/mv/{raw_id}.html"
m = re.search(r"/mv/(\d+)\.html", u)
if m:
return f"{h}/mv/{m.group(1)}.html"
return u
rid = str(raw_id or "").strip()
if rid.isdigit():
return f"{h}/mv/{rid}.html"
if rid.startswith(("http://", "https://", "/")):
return self._full_url_by_host(h, rid)
return f"{h}/mv/{rid}.html" if rid else ""
# ---------------- only check quark/115 ----------------
def _check_pan_valid(self, url, provider, timeout=3):
if not url:
return False
if provider not in ("quark", "115"):
return True
try:
h = dict(self.headers)
h["Referer"] = self.host + "/"
r = requests.get(url, headers=h, timeout=timeout, verify=False, allow_redirects=True)
if r.status_code >= 400:
return False
text = (r.text or "").lower()
if provider == "quark":
keys = ["分享已失效", "不存在", "已被取消", "取消", "删除", "已被删除", "来晚了", "违规", "无法访问"]
else:
keys = ["分享已失效", "不存在", "404", "已取消", "链接错误"]
return not any(k in text for k in keys)
except:
return False
# ---------------- home ----------------
def homeContent(self, filter):
classes = [
{"type_name": "大陆电影", "type_id": "https://www.qwmkv.com/ms/1-大陆-time---------.html"},
{"type_name": "大陆剧集", "type_id": "https://www.qwmkv.com/ms/2-大陆-time---------.html"},
{"type_name": "大陆综艺", "type_id": "https://www.qwmkv.com/ms/3-大陆-time---------.html"},
{"type_name": "大陆动漫", "type_id": "https://www.qwmkv.com/ms/4-大陆-time---------.html"},
{"type_name": "电影", "type_id": "/vt/1.html"},
{"type_name": "综艺", "type_id": "/vt/3.html"},
{"type_name": "剧集", "type_id": "/vt/2.html"},
{"type_name": "动漫", "type_id": "/vt/4.html"},
{"type_name": "短剧", "type_id": "/vt/30.html"},
]
return {"class": classes}
def homeVideoContent(self):
doc = self._pq(self.host + "/")
videos = self._extract_video_list(doc)
return {"list": videos, "page": 1, "pagecount": 1, "limit": len(videos), "total": len(videos)}
# ---------------- category ----------------
def _build_category_url(self, tid, pg, fdict):
if isinstance(tid, str) and tid.startswith(("http://", "https://")):
if pg <= 1:
return tid
if "---------.html" in tid:
return tid.replace("---------.html", f"------{pg}---.html")
return tid.replace(".html", f"-{pg}.html")
mid = self._get_mid(tid)
if not fdict:
return f"{self.host}/vt/{mid}.html" if pg <= 1 else f"{self.host}/vt/{mid}-{pg}.html"
area = quote(fdict.get("地区", ""), safe="")
sort = ""
sv = fdict.get("排序", "")
if sv == "按时间":
sort = "time"
elif sv == "按人气":
sort = "hits"
elif sv == "按评分":
sort = "score"
typ = quote(fdict.get("类型", ""), safe="")
lang = quote(fdict.get("语言", ""), safe="")
year = fdict.get("年代", "")
fields = [area, sort, typ, lang, "", "", "", "", year]
base = f"{self.host}/ms/{mid}-" + "-".join(fields)
return base + ".html" if pg <= 1 else base + f"-{pg}.html"
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg) if str(pg).isdigit() else 1
fdict = extend if isinstance(extend, dict) else {}
url = self._build_category_url(tid, pg, fdict)
doc = self._pq(url)
if len(doc("ul.pic-list li")) == 0 and len(doc("ul.content-list li")) == 0 and pg > 1:
doc = self._pq(url.replace(".html", f".html?page={pg}"))
videos = self._extract_video_list(doc)
page_count = pg
for a in doc(".pages a").items():
t = (a.text() or "").strip()
href = a.attr("href") or ""
if t.isdigit():
page_count = max(page_count, int(t))
else:
m = re.search(r"-(\d+)\.html", href)
if m:
page_count = max(page_count, int(m.group(1)))
return {
"list": videos,
"page": pg,
"pagecount": max(page_count, pg),
"limit": 30,
"total": max(page_count, pg) * 30
}
# ---------------- detail ----------------
def detailContent(self, ids):
try:
vod_id = ids[0] if isinstance(ids, list) and ids else ids
vod_id = self._full_url(vod_id)
doc = self._pq(vod_id)
raw = doc.html() or ""
# 1. 抓取基本影视信息
title = self._clean_text(doc("h1").eq(0).text())
if not title:
tt = self._clean_text(doc("title").text())
title = tt.split("在线观看")[0] if tt else "七味资源"
cover = ""
og = self._full_url(doc('meta[property="og:image"]').attr("content") or "")
if og and not self._is_bad_cover(og):
cover = og
if not cover:
c1 = self._full_url(self._img_src(doc(".main-left .img img").eq(0)))
if c1 and not self._is_bad_cover(c1):
cover = c1
if not cover:
for im in doc("img").items():
src = self._full_url(self._img_src(im))
if src and not self._is_bad_cover(src):
cover = src
break
if not cover:
cover = self.vod_pic_cache.get(vod_id, "")
if not cover:
cover = self._full_url("/template/piankuwap/image/logo.png")
self.last_vod_pic = cover
content = self._clean_text(
doc(".movie-introduce .sqjj_a").text() or
doc(".movie-introduce .zkjj_a").text() or
doc(".content").text()
)
# =====【优化修复】2. 多线路合并遍历抓取在线资源(彻底杜绝选择器冲突覆盖) =====
online_routes = {} # 格式: {"在线线路1": ["第1集$payload", "第2集$payload"]}
player_uls = doc("div#url .bd ul.player, ul.player")
line_no = 1
for ul_node in player_uls.items():
links = list(ul_node("a[href]").items())
if not links:
continue
route_name = f"📺在线播放-线路{line_no}"
episodes = []
for a in links:
href = a.attr("href")
if not href:
continue
src_name = self._clean_name(self._clean_text(a.text()) or "播放", 50)
payload = self._b64e({"type": "py", "url": self._full_url(href), "pic": cover})
episodes.append(f"{src_name}${payload}")
if episodes:
online_routes[route_name] = episodes
line_no += 1
# ===== 3. 规整网盘与磁力资源链接 =====
pan_resources = []
magnet_raw = []
seen_pan = set()
for a in doc("a[href]").items():
u = html.unescape(a.attr("href") or "").strip()
if not u:
continue
txt = self._clean_text(a.text())
if u.lower().startswith("magnet:?"):
magnet_raw.append((u, self._clean_name(txt or "磁力资源", 60)))
continue
if self._is_pan(u):
low = u.lower()
pv = "other"
if "pan.quark" in low: pv = "quark"
elif "115.com" in low: pv = "115"
elif "pan.baidu" in low: pv = "baidu"
elif "drive.uc.cn" in low: pv = "uc"
elif "pan.xunlei" in low: pv = "xunlei"
elif "aliyundrive" in low or "alipan" in low: pv = "ali"
elif "cloud.189" in low: pv = "189"
elif "123pan" in low: pv = "pan123"
if u not in seen_pan:
seen_pan.add(u)
pan_resources.append({
"provider": pv,
"url": u,
"name": txt or "网盘资源",
"checked_valid": False
})
for m in re.finditer(r"magnet:\?[^\s\"'<>]+", raw, re.I):
magnet_raw.append((html.unescape(m.group(0)), "磁力资源"))
# 磁力资源精简去重并打分排序
magnet_unified = []
btih_seen = set()
for u, n in magnet_raw:
mu = self._normalize_magnet(u)
if not mu:
continue
btih = self._magnet_btih(mu)
key = btih if btih else mu.lower()
if key in btih_seen:
continue
btih_seen.add(key)
magnet_unified.append({
"url": mu,
"name": self._clean_name(n or "磁力资源", 60)
})
magnet_unified.sort(key=lambda x: -self._score_name(x.get("name", "")))
# ===== 4. 网盘有效性探针(保持原有超时/计数策略) =====
check_begin = time.monotonic()
quark_checked = 0
valid_pan = []
for p in pan_resources:
if time.monotonic() - check_begin >= self.CHECK_TIME_BUDGET:
valid_pan.append(p)
continue
pv = p["provider"]
if pv == "quark":
if quark_checked >= self.QUARK_CHECK_LIMIT:
valid_pan.append(p)
continue
quark_checked += 1
if self._check_pan_valid(p["url"], "quark"):
p["checked_valid"] = True
valid_pan.append(p)
elif pv == "115":
if self._check_pan_valid(p["url"], "115"):
p["checked_valid"] = True
valid_pan.append(p)
else:
valid_pan.append(p)
# =====【功能修复】5. 网盘资源渠道完全独立隔离,防止错乱混杂 =====
pan_routes = {
"quark": {"name": "🟢夸克网盘", "list": []},
"ali": {"name": "☁️阿里云盘", "list": []},
"115": {"name": "固定115网盘", "list": []},
"baidu": {"name": "📘百度网盘", "list": []},
"uc": {"name": "📱UC网盘", "list": []},
"xunlei": {"name": "⚡迅雷网盘", "list": []},
"189": {"name": "☎️天翼云盘", "list": []},
"pan123": {"name": "📦123网盘", "list": []},
"other": {"name": "📦其它网盘", "list": []}
}
for r in valid_pan:
prov = r["provider"]
if prov not in pan_routes:
prov = "other"
ep_name = self._clean_name(r.get('name', '网盘提取资源'), 60)
payload = self._b64e({"type": "pan", "url": r["url"], "pic": cover})
pan_routes[prov]["list"].append(f"{ep_name}${payload}")
# =====【核心修复】6. 按标准大屏壳子1:1规则完美有序组装,防串位 =====
play_from = []
play_url = []
# 分支 A:写入网盘分类线路(依照预设的网盘体验优先级高低呈现)
drive_order = ["quark", "ali", "115", "baidu", "uc", "xunlei", "189", "pan123", "other"]
for d_key in drive_order:
route_info = pan_routes[d_key]
if route_info["list"]:
play_from.append(route_info["name"])
play_url.append("#".join(route_info["list"]))
# 分支 B:写入磁力解析相关线路(保持互相隔离)
if magnet_unified:
lines_115 = []
lines_play = []
for i, m in enumerate(magnet_unified, start=1):
nm = self._clean_name(f"磁力源-{i:02d} {m['name']}", 60)
encoded_mag = base64.urlsafe_b64encode(m['url'].encode()).decode().rstrip("=")
p_payload = self._b64e({"type": "magnet", "url": m['url'], "pic": cover})
lines_115.append(f"{nm}${encoded_mag}")
lines_play.append(f"{nm}${p_payload}")
# 独立线路一:115离线专用线
play_from.append("📥115云下载")
play_url.append("#".join(lines_115))
# 独立一条空白ACK确认交互线
play_from.append("0")
play_url.append("已提交请到115离线任务查看$__ACK__")
# 独立线路二:自带流播或通过本地壳嗅探弹磁力
play_from.append("🧲磁力播放")
play_url.append("#".join(lines_play))
# 分支 C:写入在线直连/网页采集线路
for r_name, r_eps in online_routes.items():
play_from.append(r_name)
play_url.append("#".join(r_eps))
# ===== 当前站搜索入口 =====
try:
search_payload = self._b64e({
"type": "search",
"wd": title,
"pic": cover
})
play_from.insert(0, "🔍点击选择")
play_url.insert(0, f"当前站搜索${search_payload}")
except Exception as e:
print(f"search line add error: {e}")
# 兜底处理
if not play_from:
play_from.append("🌐原网页查看")
play_url.append(f"点击跳转原详情页${self._b64e({'type': 'web', 'url': vod_id, 'pic': cover})}")
# 7. 构建标准影视输出字典
vod = {
"vod_id": vod_id,
"vod_name": self._clean_name(title, 100),
"vod_pic": cover,
"vod_content": content,
"vod_play_from": "$$$".join(play_from),
"vod_play_url": "$$$".join(play_url)
}
return {"list": [vod]}
except Exception as e:
print(f"detailContent error: {e}")
return {"list": []}
# ---------------- player ----------------
def _parse_py_page(self, py_url):
r = self._fetch(py_url, timeout=10)
if not r:
return ""
txt = r.text or ""
m = re.search(r"player_aaaa\s*=\s*(\{.*?\})\s*<", txt, re.S)
if not m:
m = re.search(r"player_aaaa\s*=\s*(\{.*?\})\s*;", txt, re.S)
if not m:
return ""
js = m.group(1)
try:
js2 = re.sub(r"(\w+)\s*:", r'"\1":', js)
obj = json.loads(js2)
except:
try:
obj = json.loads(js)
except:
return ""
u = obj.get("url", "") or ""
enc = str(obj.get("encrypt", "0"))
if enc == "1":
u = unquote(u)
elif enc == "2":
try:
u = unquote(base64.b64decode(u).decode("utf-8", "ignore"))
except:
pass
if u.startswith("//"):
u = "https:" + u
elif u.startswith("/"):
u = self._full_url(u)
return u
def _return_ack_video(self):
ret = {
"parse": 0,
"playUrl": "",
"url": self.ack_mp4,
"header": {
"User-Agent": self.headers.get("User-Agent", ""),
"Referer": self.host + "/"
}
}
if self.last_vod_pic:
ret["pic"] = self.last_vod_pic
ret["poster"] = self.last_vod_pic
return ret
def _add_to_115(self, magnet):
if not self.pan_115_cookie:
print("115添加失败: 未配置 pan_115_cookie")
return
magnet = self._normalize_magnet(magnet)
if not magnet:
print("115添加失败: 非法磁力")
return
headers = {
"User-Agent": self.headers.get("User-Agent", ""),
"Cookie": self.pan_115_cookie,
"Origin": "https://115.com",
"Referer": "https://115.com/web/lixian/",
"Accept": "application/json, text/javascript, */*; q=0.01",
"X-Requested-With": "XMLHttpRequest"
}
try:
pan_sess = requests.Session()
pan_sess.verify = False
pan_sess.mount("http://", SSLAdapter(max_retries=2))
pan_sess.mount("https://", SSLAdapter(max_retries=2))
space_resp = pan_sess.get("https://115.com/?ct=offline&ac=space", headers=headers, timeout=10)
try:
space_json = space_resp.json()
except:
print(f"115获取签名失败(非JSON): {space_resp.text[:200]}")
return
if not space_json.get("state"):
print(f"115获取签名失败(可能Cookie过期): {space_json}")
return
sign = space_json.get("sign", "")
req_time = space_json.get("time", "")
if not sign or not req_time:
print(f"115签名数据异常: {space_json}")
return
uid_match = re.search(r'UID=(\d+)', self.pan_115_cookie)
uid = uid_match.group(1) if uid_match else ""
add_url = "https://115.com/web/lixian/?ct=lixian&ac=add_task_url"
post_data = {"url": magnet, "uid": uid, "sign": sign, "time": req_time}
headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
add_resp = pan_sess.post(add_url, data=post_data, headers=headers, timeout=10)
try:
add_json = add_resp.json()
except:
print(f"115添加失败(非JSON): {add_resp.text[:200]}")
return
if add_json.get("state") or add_json.get("errcode") == 0:
print(f"115离线添加成功: {magnet[:100]}...")
else:
err = add_json.get("error_msg") or add_json.get("msg") or add_json.get("error") or str(add_json)
print(f"115添加失败: {err}")
except Exception as e:
print(f"115离线网络异常: {e}")
def playerContent(self, flag, id, vipFlags):
if flag == "0" or id == "__ACK__":
return self._return_ack_video()
if flag == "📥115云下载":
try:
decoded = base64.urlsafe_b64decode(id.encode() + b"==").decode()
magnet = self._normalize_magnet(decoded)
if not magnet:
return self._return_ack_video()
if not self.pan_115_cookie:
print("115未配置Cookie")
return self._return_ack_video()
threading.Thread(target=self._add_to_115, args=(magnet,), daemon=True).start()
return self._return_ack_video()
except Exception as e:
print(f"115云下载处理异常: {e}")
return self._return_ack_video()
if flag == "🧲磁力播放":
data = self._b64d(id)
if data and data.get("type") == "magnet":
mu = self._normalize_magnet(data.get("url", ""))
if mu:
pic = data.get("pic") or self.last_vod_pic
return {"parse": 0, "url": "push://" + mu, "pic": pic, "poster": pic}
if isinstance(id, str) and id.startswith("push://"):
return {"parse": 0, "url": id, "pic": self.last_vod_pic, "poster": self.last_vod_pic}
mu = self._normalize_magnet(id)
if mu:
return {"parse": 0, "url": "push://" + mu, "pic": self.last_vod_pic, "poster": self.last_vod_pic}
return {"parse": 1, "url": id, "pic": self.last_vod_pic, "poster": self.last_vod_pic}
data = self._b64d(id)
if not data:
if isinstance(id, str) and id.startswith("push://"):
return {"parse": 0, "url": id, "pic": self.last_vod_pic, "poster": self.last_vod_pic}
return {"parse": 1, "url": id, "pic": self.last_vod_pic, "poster": self.last_vod_pic}
typ = data.get("type", "")
url = data.get("url", "")
pic = data.get("pic") or self.last_vod_pic
if not url:
return {"parse": 1, "url": id, "pic": pic, "poster": pic}
if typ == "search":
wd = data.get("wd", "").strip()
if not wd:
return {
"parse": 1,
"url": self.host,
"pic": pic,
"poster": pic
}
search_url = f"{self.host}/vodsearch/{quote(wd)}----------1---.html"
return {
"parse": 0,
"url": "push://" + search_url,
"pic": pic,
"poster": pic
}
if typ == "pan":
return {"parse": 0, "url": "push://" + url, "pic": pic, "poster": pic}
if typ == "magnet":
mu = self._normalize_magnet(url)
if mu:
return {"parse": 0, "url": "push://" + mu, "pic": pic, "poster": pic}
return {"parse": 1, "url": url, "pic": pic, "poster": pic}
if typ == "py":
real = self._parse_py_page(url)
if real:
return {"parse": 0, "url": real, "pic": pic, "poster": pic}
return {"parse": 1, "url": url, "pic": pic, "poster": pic}
if typ == "web":
return {"parse": 1, "url": url, "pic": pic, "poster": pic}
return {"parse": 1, "url": url, "pic": pic, "poster": pic}
# ---------------- search ----------------
def searchContent(self, key, quick, pg="1"):
pg = int(pg) if str(pg).isdigit() else 1
wd = quote(key)
for h in self.hosts:
suggest_api = f"{h}/index.php/ajax/suggest?mid=1&limit=20&wd={wd}"
try:
r = self.session.get(suggest_api, timeout=8, headers=self.headers, verify=False)
txt = r.text or ""
if not self._is_verify_page(txt) and r.status_code == 200:
data = r.json()
lst = data.get("list") or []
videos = []
for it in lst:
vid = it.get("id") or it.get("vod_id")
name = it.get("name") or it.get("vod_name") or ""
pic = self._full_url_by_host(h, it.get("pic") or it.get("vod_pic") or "")
remarks = self._clean_text(it.get("en") or it.get("remark") or "")
jump_url = it.get("url") or it.get("link") or ""
vid_url = self._mk_vod_id(h, vid, jump_url)
if not vid_url:
continue
if pic:
self.vod_pic_cache[vid_url] = pic
videos.append({
"vod_id": vid_url,
"vod_name": self._clean_name(name, 80),
"vod_pic": pic,
"vod_remarks": remarks
})
if videos:
self.host = h
return {
"list": videos,
"page": pg,
"pagecount": pg + 1 if len(videos) >= 20 else pg,
"limit": len(videos),
"total": len(videos)
}
except:
pass
api_list = [
f"{h}/api.php/provide/vod/?ac=detail&wd={wd}&pg={pg}",
f"{h}/api.php/provide/vod?ac=detail&wd={wd}&pg={pg}",
]
for api in api_list:
try:
r = self.session.get(api, timeout=8, headers=self.headers, verify=False)
txt = r.text or ""
if self._is_verify_page(txt):
continue
if r.status_code != 200:
continue
data = r.json()
lst = data.get("list") or data.get("data") or []
videos = []
for it in lst:
vid = it.get("vod_id") or it.get("id")
name = it.get("vod_name") or it.get("name") or ""
pic = self._full_url_by_host(h, it.get("vod_pic") or it.get("pic") or "")
remarks = self._clean_text(it.get("vod_remarks") or it.get("remarks") or "")
jump_url = it.get("vod_play_url") or it.get("url") or it.get("link") or ""
vid_url = self._mk_vod_id(h, vid, jump_url)
if not vid_url:
continue
if pic:
self.vod_pic_cache[vid_url] = pic
videos.append({
"vod_id": vid_url,
"vod_name": self._clean_name(name, 80),
"vod_pic": pic,
"vod_remarks": remarks
})
if videos:
self.host = h
return {
"list": videos,
"page": int(data.get("page", pg) or pg),
"pagecount": int(data.get("pagecount", 1) or 1),
"limit": len(videos),
"total": int(data.get("total", len(videos)) or len(videos))
}
except:
pass
return {
"list": [],
"page": pg,
"pagecount": pg,
"limit": 0,
"total": 0
}