Sync all projects
This commit is contained in:
@@ -724,6 +724,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/河马剧场.py"
|
||||
},
|
||||
{
|
||||
"key": "wddj",
|
||||
"name": "🐬黄豆短剧.py[短剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/黄豆短剧.py"
|
||||
},
|
||||
{
|
||||
"key": "djjh",
|
||||
"name": "🐬短剧聚合.py[短剧]",
|
||||
|
||||
@@ -323,6 +323,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/河马剧场.py"
|
||||
},
|
||||
{
|
||||
"key": "wddj",
|
||||
"name": "🐬黄豆短剧.py[短剧]",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/黄豆短剧.py"
|
||||
},
|
||||
{
|
||||
"key": "djjh",
|
||||
"name": "🐬短剧聚合.py[短剧]",
|
||||
|
||||
@@ -678,6 +678,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/河马剧场.py"
|
||||
},
|
||||
{
|
||||
"key": "wddj",
|
||||
"name": "🐬黄豆短剧.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/黄豆短剧.py"
|
||||
},
|
||||
{
|
||||
"key": "djjh",
|
||||
"name": "🐬短剧聚合.py",
|
||||
|
||||
@@ -657,6 +657,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/河马剧场.py"
|
||||
},
|
||||
{
|
||||
"key": "wddj",
|
||||
"name": "🐬黄豆短剧.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/黄豆短剧.py"
|
||||
},
|
||||
{
|
||||
"key": "djjh",
|
||||
"name": "🐬短剧聚合.py",
|
||||
|
||||
@@ -618,6 +618,12 @@
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/河马剧场.py"
|
||||
},
|
||||
{
|
||||
"key": "wddj",
|
||||
"name": "🐬黄豆短剧.py",
|
||||
"type": 3,
|
||||
"api": "https://ghfast.top/https://raw.githubusercontent.com/FGBLH/HKL/refs/heads/main/py/黄豆短剧.py"
|
||||
},
|
||||
{
|
||||
"key": "djjh",
|
||||
"name": "🐬短剧聚合.py",
|
||||
|
||||
+119
-92
@@ -69,40 +69,41 @@ class Spider(BaseSpider):
|
||||
}
|
||||
|
||||
try:
|
||||
# 获取分类
|
||||
genres_data = self._get('/genres')
|
||||
if genres_data and isinstance(genres_data, list):
|
||||
for g in genres_data:
|
||||
gid = str(g.get('id', ''))
|
||||
if not gid:
|
||||
continue
|
||||
result["class"].append({
|
||||
"type_id": gid,
|
||||
"type_name": g.get('name', ''),
|
||||
})
|
||||
# 获取频道首页数据(包含分类和推荐)
|
||||
channel_data = self._get('/channel/home?platform=mobile&size=20')
|
||||
if channel_data and isinstance(channel_data, dict):
|
||||
# 分类:用 sections 里的 l3 分类(有实际内容的)
|
||||
sections = channel_data.get('sections', [])
|
||||
if isinstance(sections, list):
|
||||
for sec in sections:
|
||||
l3_id = sec.get('l3Id')
|
||||
name = sec.get('name', '')
|
||||
if l3_id and name:
|
||||
result["class"].append({
|
||||
"type_id": f"l3_{l3_id}",
|
||||
"type_name": name,
|
||||
})
|
||||
|
||||
# 获取首页推荐
|
||||
home_data = self._get('/home')
|
||||
if home_data and isinstance(home_data, dict):
|
||||
# guess 猜你喜欢
|
||||
guess_list = home_data.get('guess', [])
|
||||
if isinstance(guess_list, list):
|
||||
for item in guess_list:
|
||||
result["list"].append(self._parse_vod(item))
|
||||
|
||||
# feature 精选
|
||||
feature_list = home_data.get('feature', [])
|
||||
if isinstance(feature_list, list):
|
||||
for item in feature_list:
|
||||
result["list"].append(self._parse_vod(item))
|
||||
|
||||
# 如果列表为空,用首页第一页数据
|
||||
if not result["list"]:
|
||||
dramas_data = self._get('/dramas?page=1&size=20')
|
||||
if dramas_data and isinstance(dramas_data, dict):
|
||||
for item in dramas_data.get('list', []):
|
||||
# 首页推荐:把各个板块的内容合并
|
||||
for sec in sections:
|
||||
dramas = sec.get('dramas', [])
|
||||
if isinstance(dramas, list):
|
||||
for item in dramas:
|
||||
result["list"].append(self._parse_vod(item))
|
||||
|
||||
# 如果 sections 里没有数据,用 guess/feature
|
||||
if not result["list"]:
|
||||
home_data = self._get('/home')
|
||||
if home_data and isinstance(home_data, dict):
|
||||
guess_list = home_data.get('guess', [])
|
||||
if isinstance(guess_list, list):
|
||||
for item in guess_list:
|
||||
result["list"].append(self._parse_vod(item))
|
||||
feature_list = home_data.get('feature', [])
|
||||
if isinstance(feature_list, list):
|
||||
for item in feature_list:
|
||||
result["list"].append(self._parse_vod(item))
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
@@ -121,8 +122,9 @@ class Spider(BaseSpider):
|
||||
}
|
||||
|
||||
try:
|
||||
# 分类ID是genre id
|
||||
data = self._get(f'/dramas?genreId={tid}&page={pg}&size=20')
|
||||
# 分类ID格式: l3_{id}
|
||||
l3_id = tid.replace('l3_', '')
|
||||
data = self._get(f'/dramas?platform=mobile&l3Id={l3_id}&sort=最新&page={pg}&size=20')
|
||||
if data and isinstance(data, dict):
|
||||
lst = data.get('list', [])
|
||||
total = data.get('total', 0)
|
||||
@@ -159,16 +161,24 @@ class Spider(BaseSpider):
|
||||
if play_url:
|
||||
play_url_parts.append(f"{ep_title}${play_url}")
|
||||
|
||||
cover = data.get('cover', '')
|
||||
# 加密海报走本地代理解密
|
||||
if cover and ('encryptimages' in cover or '.bng' in cover):
|
||||
try:
|
||||
cover = f"{self.getProxyUrl()}&url={urllib.parse.quote(cover)}"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
vod = {
|
||||
"vod_id": str(data['id']),
|
||||
"vod_name": data.get('t', ''),
|
||||
"vod_pic": data.get('cover', ''),
|
||||
"vod_pic": cover,
|
||||
"type_name": data.get('sub', ''),
|
||||
"vod_year": '',
|
||||
"vod_area": '',
|
||||
"vod_remarks": f"{data.get('serial', '')}·{data.get('plays', '')}播放",
|
||||
"vod_actor": '',
|
||||
"vod_director": '黄豆短剧',
|
||||
"vod_director": '未知',
|
||||
"vod_content": data.get('summary', '') or data.get('t', ''),
|
||||
"vod_play_from": '黄豆短剧',
|
||||
"vod_play_url": '#'.join(play_url_parts),
|
||||
@@ -285,78 +295,95 @@ class Spider(BaseSpider):
|
||||
key_str = prefix + video_id + version
|
||||
return hashlib.md5(key_str.encode()).digest()
|
||||
|
||||
def localProxy(self, params):
|
||||
"""本地代理 - 处理海报图片解密"""
|
||||
try:
|
||||
do = params.get('do', '')
|
||||
if do == 'img':
|
||||
url = params.get('url', '')
|
||||
if not url:
|
||||
return 0
|
||||
return self._decrypt_image(url)
|
||||
except Exception as e:
|
||||
print(f"localProxy error: {e}")
|
||||
return 0
|
||||
|
||||
def _decrypt_image(self, url):
|
||||
"""解密加密的海报图片"""
|
||||
import hashlib
|
||||
import re
|
||||
def localProxy(self, param):
|
||||
"""本地代理 - 解密海报图片"""
|
||||
try:
|
||||
url = param['url']
|
||||
r = self.session.get(url, timeout=15, verify=False)
|
||||
encrypted = r.content
|
||||
|
||||
# 提取 imageId (64位哈希)
|
||||
m = re.search(r'([0-9a-f]{64})', url)
|
||||
if not m:
|
||||
return [200, "image/png", {}, encrypted]
|
||||
|
||||
image_id = m.group(1)
|
||||
|
||||
# 提取 version
|
||||
ver_match = re.search(r'[?&]version=([^&#]+)', url)
|
||||
version = ver_match.group(1) if ver_match else 'v1'
|
||||
|
||||
# 计算解密 key
|
||||
prefix = "xnaichanping"
|
||||
key_str = prefix + image_id + version
|
||||
key_bytes = hashlib.md5(key_str.encode()).digest()
|
||||
|
||||
# AES-128-CBC 解密,IV=0
|
||||
from Crypto.Cipher import AES
|
||||
iv = bytes.fromhex('00000000000000000000000000000000')
|
||||
cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
|
||||
decrypted = cipher.decrypt(encrypted)
|
||||
|
||||
# 去掉 PKCS7 padding
|
||||
pad_len = decrypted[-1]
|
||||
if 1 <= pad_len <= 16:
|
||||
decrypted = decrypted[:-pad_len]
|
||||
|
||||
decrypted = self._aes_decrypt_img(r.content, url)
|
||||
# 确定图片类型
|
||||
content_type = "image/png"
|
||||
if decrypted[:3] == b'\xff\xd8\xff':
|
||||
content_type = "image/jpeg"
|
||||
elif decrypted[:8] == b'\x89PNG\r\n\x1a\n':
|
||||
content_type = "image/jpeg"
|
||||
if decrypted[:8] == b'\x89PNG\r\n\x1a\n':
|
||||
content_type = "image/png"
|
||||
elif decrypted[:6] == b'GIF87a' or decrypted[:6] == b'GIF89a':
|
||||
elif decrypted[:6] in (b'GIF87a', b'GIF89a'):
|
||||
content_type = "image/gif"
|
||||
elif decrypted[:4] == b'RIFF' and decrypted[8:12] == b'WEBP':
|
||||
content_type = "image/webp"
|
||||
|
||||
return [200, content_type, {}, decrypted]
|
||||
return [200, content_type, decrypted]
|
||||
except Exception as e:
|
||||
print(f"_decrypt_image error: {e}")
|
||||
return 0
|
||||
print(f"localProxy error: {e}")
|
||||
return [500, 'text/html', b'']
|
||||
|
||||
def _aes_decrypt_img(self, encrypted, url):
|
||||
"""AES 解密图片 - 网站自定义 CBC 算法"""
|
||||
import hashlib
|
||||
import re
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
# 提取 imageId (64位哈希)
|
||||
m = re.search(r'([0-9a-f]{64})', url)
|
||||
if not m:
|
||||
return encrypted
|
||||
image_id = m.group(1)
|
||||
|
||||
# 提取 version
|
||||
ver_match = re.search(r'[?&]version=([^&#]+)', url)
|
||||
version = ver_match.group(1) if ver_match else 'v1'
|
||||
|
||||
# 计算解密 key
|
||||
prefix = "xnaichanping"
|
||||
key_str = prefix + image_id + version
|
||||
key_bytes = hashlib.md5(key_str.encode()).digest()
|
||||
|
||||
# 网站自定义 CBC 解密 (mC 函数)
|
||||
t = len(encrypted) // 16
|
||||
if t < 1:
|
||||
return encrypted
|
||||
|
||||
iv = bytes(16) # IV=0
|
||||
|
||||
# 取最后一块 XOR 16
|
||||
last_block = encrypted[(t - 1) * 16:t * 16]
|
||||
a = bytes([b ^ 16 for b in last_block])
|
||||
|
||||
# 加密 a
|
||||
cipher_enc = AES.new(key_bytes, AES.MODE_CBC, iv)
|
||||
o = cipher_enc.encrypt(a)[:16]
|
||||
|
||||
# 扩展密文并解密
|
||||
extended = encrypted + o
|
||||
cipher_dec = AES.new(key_bytes, AES.MODE_CBC, iv)
|
||||
c = cipher_dec.decrypt(extended)
|
||||
|
||||
# 自定义 CBC:每块 XOR 前一块密文
|
||||
u = bytearray(len(c))
|
||||
u[:16] = c[:16]
|
||||
for f in range(1, t):
|
||||
for h in range(16):
|
||||
u[f * 16 + h] = c[f * 16 + h] ^ encrypted[(f - 1) * 16 + h]
|
||||
|
||||
# 去掉 padding
|
||||
d = u[-1]
|
||||
if 1 <= d <= 16:
|
||||
u = u[:-d]
|
||||
|
||||
return bytes(u)
|
||||
|
||||
# ==================== 内部方法 ====================
|
||||
|
||||
def _parse_vod(self, item):
|
||||
"""解析视频条目"""
|
||||
"""解析视频条目 - 海报用 getProxyUrl 代理解密"""
|
||||
cover = item.get('cover', '')
|
||||
# 加密海报走本地代理解密
|
||||
if cover and ('encryptimages' in cover or '.bng' in cover):
|
||||
try:
|
||||
cover = f"{self.getProxyUrl()}&url={urllib.parse.quote(cover)}"
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"vod_id": str(item['id']),
|
||||
"vod_name": item.get('t', ''),
|
||||
"vod_pic": item.get('cover', ''),
|
||||
"vod_pic": cover,
|
||||
"vod_remarks": f"{item.get('serial', '')}·{item.get('eps', 0)}集",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user