From a530a8c99bb9d1934fc70a545581623996230714 Mon Sep 17 00:00:00 2001
From: Harold <8866033@gmail.com>
Date: Mon, 20 Apr 2026 11:00:59 +0800
Subject: [PATCH] feat: add zxzj player parsing
---
py/tests/test_在线之家.py | 43 ++++++++++++++++++++++++++
py/在线之家.py | 65 +++++++++++++++++++++++++++++++++++++++
2 files changed, 108 insertions(+)
diff --git a/py/tests/test_在线之家.py b/py/tests/test_在线之家.py
index fe4031a..1e51e71 100644
--- a/py/tests/test_在线之家.py
+++ b/py/tests/test_在线之家.py
@@ -164,6 +164,49 @@ class TestZXZJSpider(unittest.TestCase):
)
self.assertEqual(groups, [])
+ def test_decrypt_url_round_trip_with_fixture(self):
+ self.assertEqual(
+ self.spider._decrypt_url("d6f636e256c607d6168756e2f67464544434241456469667f2f2a33707474786"),
+ "https://video.example.com",
+ )
+
+ def test_extract_iframe_url_reads_jx_target(self):
+ html = ''
+ self.assertEqual(
+ self.spider._extract_iframe_url(html),
+ "https://jx.zxzj.example/player?id=1",
+ )
+
+ @patch.object(Spider, "_request_html")
+ def test_player_content_returns_decoded_direct_url_for_zxzj(self, mock_request_html):
+ mock_request_html.side_effect = [
+ '',
+ '',
+ ]
+ result = self.spider.playerContent("zxzj", "vodplay/999-1-1.html", {})
+ self.assertEqual(result["parse"], 0)
+ self.assertEqual(result["jx"], 0)
+ self.assertEqual(result["url"], "https://video.example.com")
+ self.assertEqual(result["header"]["Referer"], "https://jx.zxzj.example/player?id=1")
+
+ @patch.object(Spider, "_request_html")
+ def test_player_content_falls_back_to_play_page_when_decrypt_fails(self, mock_request_html):
+ mock_request_html.side_effect = [
+ '',
+ '',
+ ]
+ result = self.spider.playerContent("zxzj", "vodplay/999-1-1.html", {})
+ self.assertEqual(result["parse"], 1)
+ self.assertEqual(result["jx"], 1)
+ self.assertEqual(result["url"], "https://www.zxzjhd.com/vodplay/999-1-1.html")
+
+ def test_player_content_returns_direct_netdisk_link(self):
+ result = self.spider.playerContent("quark", "https://pan.quark.cn/s/demo", {})
+ self.assertEqual(
+ result,
+ {"parse": 0, "jx": 0, "playUrl": "", "url": "https://pan.quark.cn/s/demo", "header": {}},
+ )
+
if __name__ == "__main__":
unittest.main()
diff --git a/py/在线之家.py b/py/在线之家.py
index f14b8cf..d39f23c 100644
--- a/py/在线之家.py
+++ b/py/在线之家.py
@@ -379,3 +379,68 @@ class Spider(BaseSpider):
"vod_play_url": "$$$".join(play_url),
}
return {"list": [vod]}
+
+ def _decrypt_url(self, encrypted_data):
+ raw = str(encrypted_data or "").strip()
+ if not raw or len(raw) % 2 != 0:
+ return ""
+ try:
+ reversed_hex = raw[::-1]
+ decoded = []
+ for index in range(0, len(reversed_hex), 2):
+ decoded.append(chr(int(reversed_hex[index : index + 2], 16)))
+ text = "".join(decoded)
+ split_len = max((len(text) - 7) // 2, 0)
+ candidate = text[:split_len] + text[split_len + 7 :]
+ return candidate if candidate.startswith("http") else ""
+ except Exception:
+ return ""
+
+ def _extract_iframe_url(self, html):
+ matched = re.search(r'"url"\s*:\s*"(https:[^"]*?jx\.zxzj[^"]*?)"', str(html or ""))
+ if matched:
+ return matched.group(1).replace("\\/", "/")
+ matched = re.search(r"player_[a-z0-9_]+\s*=\s*(\{[\s\S]*?\})\s*;?", str(html or ""), re.I)
+ if not matched:
+ return ""
+ try:
+ payload = json.loads(matched.group(1))
+ except Exception:
+ return ""
+ value = str(payload.get("url") or "").replace("\\/", "/")
+ return value if "jx.zxzj" in value else ""
+
+ def _extract_result_v2_data(self, html):
+ matched = re.search(r"result_v2\s*=\s*(\{[\s\S]*?\})\s*;", str(html or ""))
+ if not matched:
+ return ""
+ try:
+ payload = json.loads(matched.group(1))
+ except Exception:
+ return ""
+ return str(payload.get("data") or payload.get("url") or "").strip()
+
+ def playerContent(self, flag, id, vipFlags):
+ raw_flag = str(flag or "").lower()
+ raw_id = str(id or "").strip()
+ if raw_flag in ("baidu", "quark", "uc", "aliyun", "xunlei"):
+ return {"parse": 0, "jx": 0, "playUrl": "", "url": raw_id, "header": {}}
+
+ play_url = raw_id if raw_id.startswith("http") else self._build_url("/" + raw_id.lstrip("/"))
+ play_html = self._request_html(play_url, referer=self.headers["Referer"])
+ iframe_url = self._extract_iframe_url(play_html)
+ if not iframe_url:
+ return {"parse": 1, "jx": 1, "playUrl": "", "url": play_url, "header": self.headers}
+
+ iframe_html = self._request_html(iframe_url, referer=play_url)
+ encrypted = self._extract_result_v2_data(iframe_html)
+ final_url = self._decrypt_url(encrypted)
+ if not final_url:
+ return {"parse": 1, "jx": 1, "playUrl": "", "url": play_url, "header": self.headers}
+ return {
+ "parse": 0,
+ "jx": 0,
+ "playUrl": "",
+ "url": final_url,
+ "header": {"Referer": iframe_url, "User-Agent": self.headers["User-Agent"]},
+ }