Files
2026-07-03 14:44:27 +00:00

518 lines
17 KiB
Python

# coding=utf-8
# !/usr/bin/python
"""
作者 丢丢喵推荐 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
====================Diudiumiao====================
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from datetime import datetime
from bs4 import BeautifulSoup
from base64 import b64decode
import urllib.request
import urllib.parse
import datetime
import binascii
import requests
import hashlib
import base64
import json
import time
import sys
import re
import os
sys.path.append('..')
xurl = "https://api-store.qmplaylet.com"
xurl1 = "https://api-read.qmplaylet.com"
keys = "d3dGiJc651gSQ8w1"
data = {
"static_score": "0.8",
"uuid": "00000000-7fc7-08dc-0000-000000000000",
"device-id": "20250220125449b9b8cac84c2dd3d035c9052a2572f7dd0122edde3cc42a70",
"mac": "",
"sourceuid": "aa7de295aad621a6",
"refresh-type": "0",
"model": "22021211RC",
"wlb-imei": "",
"client-id": "aa7de295aad621a6",
"brand": "Redmi",
"oaid": "",
"oaid-no-cache": "",
"sys-ver": "12",
"trusted-id": "",
"phone-level": "H",
"imei": "",
"wlb-uid": "aa7de295aad621a6",
"session-id": str(int(time.time() * 1000)),
}
json_str = json.dumps(data, separators=(',', ':'))
encoded = base64.b64encode(json_str.encode()).decode()
char_map = {
'+': 'P', '/': 'X', '0': 'M', '1': 'U', '2': 'l', '3': 'E', '4': 'r',
'5': 'Y', '6': 'W', '7': 'b', '8': 'd', '9': 'J', 'A': '9', 'B': 's',
'C': 'a', 'D': 'I', 'E': '0', 'F': 'o', 'G': 'y', 'H': '_', 'I': 'H',
'J': 'G', 'K': 'i', 'L': 't', 'M': 'g', 'N': 'N', 'O': 'A', 'P': '8',
'Q': 'F', 'R': 'k', 'S': '3', 'T': 'h', 'U': 'f', 'V': 'R', 'W': 'q',
'X': 'C', 'Y': '4', 'Z': 'p', 'a': 'm', 'b': 'B', 'c': 'O', 'd': 'u',
'e': 'c', 'f': '6', 'g': 'K', 'h': 'x', 'i': '5', 'j': 'T', 'k': '-',
'l': '2', 'm': 'z', 'n': 'S', 'o': 'Z', 'p': '1', 'q': 'V', 'r': 'v',
's': 'j', 't': 'Q', 'u': '7', 'v': 'D', 'w': 'w', 'x': 'n', 'y': 'L',
'z': 'e'
}
qm_params = ''
for c in encoded:
qm_params += char_map.get(c, c)
params_str = (
"AUTHORIZATION=" +
"app-version=10001" +
"application-id=com.duoduo.read" +
"channel=unknown" +
"is-white=" +
"net-env=5" +
"platform=android" +
f"qm-params={qm_params}" +
f"reg={keys}"
)
signs = hashlib.md5(params_str.encode()).hexdigest()
headerx = {
'net-env': '5',
'reg': '',
'channel': 'unknown',
'is-white': '',
'platform': 'android',
'application-id': 'com.duoduo.read',
'authorization': '',
'app-version': '10001',
'user-agent': 'webviewversion/0',
'qm-params': qm_params,
'sign': signs
}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
# 全局变量用于缓存百度跳转信息
baidu_name_cache = ""
baidu_jump_cache = ""
class Spider(Spider):
global xurl
global xurl1
global keys
global headerx
global headers
global baidu_name_cache
global baidu_jump_cache
def getName(self):
return "首页"
def init(self, extend):
global baidu_name_cache, baidu_jump_cache
# 初始化时获取百度跳转信息并缓存
try:
response = requests.get(url='https://m.baidu.com/', headers=headers, timeout=5)
response.encoding = 'utf-8'
code = response.text
baidu_name_cache = self.extract_middle_text(code, "s1='", "'", 0)
baidu_jump_cache = self.extract_middle_text(code, "s2='", "'", 0)
except:
baidu_name_cache = ""
baidu_jump_cache = ""
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
if pl == 3:
plx = []
while True:
start_index = text.find(start_str)
if start_index == -1:
break
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
break
middle_text = text[start_index + len(start_str):end_index]
plx.append(middle_text)
text = text.replace(start_str + middle_text + end_str, '')
if len(plx) > 0:
purl = ''
for i in range(len(plx)):
matches = re.findall(start_index1, plx[i])
output = ""
for match in matches:
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
if match3:
number = match3.group(1)
else:
number = 0
if 'http' not in match[0]:
output += f"#{match[1]}${number}{xurl}{match[0]}"
else:
output += f"#{match[1]}${number}{match[0]}"
output = output[1:]
purl = purl + output + "$$$"
purl = purl[:-3]
return purl
else:
return ""
else:
start_index = text.find(start_str)
if start_index == -1:
return ""
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
return ""
if pl == 0:
middle_text = text[start_index + len(start_str):end_index]
return middle_text.replace("\\", "")
if pl == 1:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
jg = ' '.join(matches)
return jg
if pl == 2:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
new_list = [f'{item}' for item in matches]
jg = '$$$'.join(new_list)
return jg
def homeContent(self, filter):
result = {"class": []}
sign_string = f"operation=1playlet_privacy=1tag_id=0{keys}"
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
url = f"{xurl}/api/v1/playlet/index?tag_id=0&playlet_privacy=1&operation=1&sign={sign}"
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
data = detail.json()
duoxuan = ['0', '1', '2', '3', '4']
for duo in duoxuan:
js = data['data']['tag_categories'][int(duo)]['tags']
for vod in js:
name = vod['tag_name']
if "推荐" in name:
continue
id = vod['tag_id']
result["class"].append({"type_id": id, "type_name": "" + name})
return result
def homeVideoContent(self):
videos = []
sign_string = f"operation=1playlet_privacy=1tag_id=0{keys}"
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
url = f"{xurl}/api/v1/playlet/index?tag_id=0&playlet_privacy=1&operation=1&sign={sign}"
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
data = detail.json()
data = data['data']['list']
for vod in data:
# 获取标题,确保不为空
name = vod.get('title', '')
if not name:
name = vod.get('name', '未知标题')
name = str(name).strip()
# 获取ID
id = vod.get('playlet_id', '')
if not id:
id = vod.get('id', '')
# 获取封面
pic = vod.get('image_link', '')
if not pic:
pic = vod.get('cover', '')
# 获取热度值作为备注
remark = vod.get('hot_value', '')
if not remark:
remark = vod.get('view_count', '')
video = {
"vod_id": str(id),
"vod_name": name,
"vod_pic": pic,
"vod_remarks": str(remark) if remark else ''
}
videos.append(video)
result = {'list': videos}
return result
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
if pg:
page = int(pg)
else:
page = 1
if page == 1:
sign_string = f"operation=1playlet_privacy=1tag_id={cid}{keys}"
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
url = f'{xurl}/api/v1/playlet/index?tag_id={cid}&playlet_privacy=1&operation=1&sign={sign}'
else:
sign_string = f"next_id={str(page)}operation=1playlet_privacy=1tag_id={cid}{keys}"
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
url = f'{xurl}/api/v1/playlet/index?tag_id={cid}&next_id={str(page)}&playlet_privacy=1&operation=1&sign={sign}'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
data = detail.json()
data = data['data']['list']
for vod in data:
name = vod.get('title', '')
if not name:
name = vod.get('name', '未知标题')
name = str(name).strip()
id = vod.get('playlet_id', '')
if not id:
id = vod.get('id', '')
pic = vod.get('image_link', '')
if not pic:
pic = vod.get('cover', '')
remark = vod.get('hot_value', '')
if not remark:
remark = vod.get('view_count', '')
video = {
"vod_id": str(id),
"vod_name": name,
"vod_pic": pic,
"vod_remarks": str(remark) if remark else ''
}
videos.append(video)
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
result = {}
videos = []
xianlu = '七猫专线' # 统一使用七猫专线
bofang = ''
sign_string = f"playlet_id={did}{keys}"
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
urls = f'{xurl1}/player/api/v1/playlet/info?playlet_id={did}&sign={sign}'
detail = requests.get(url=urls, headers=headerx)
detail.encoding = "utf-8"
detail = detail.json()
# 获取标题(使用API返回的标题)
title = detail.get('data', {}).get('title', '')
if not title:
title = detail.get('data', {}).get('name', '未知标题')
blurb = detail.get('data', {}).get('intro') or "暂无剧情简介"
content = '剧情📢' + str(blurb)
jisu = detail.get('data', {}).get('total_episode_num', '未知')
jisu = str(jisu) + '全集'
leixing = detail.get('data', {}).get('tags', '未知')
remarks = str(leixing) + " " + str(jisu)
# 使用七猫的播放列表
soup = detail.get('data', {}).get('play_list', [])
if soup:
for sou in soup:
video_url = sou.get('video_url', '')
sort_name = sou.get('sort', '')
if video_url and sort_name:
bofang = bofang + str(sort_name) + '$' + str(video_url) + '#'
bofang = bofang[:-1] if bofang else ''
else:
# 如果没有播放列表,使用跳转链接
global baidu_jump_cache
bofang = baidu_jump_cache
videos.append({
"vod_id": str(did),
"vod_name": str(title),
"vod_remarks": remarks,
"vod_content": content,
"vod_play_from": xianlu,
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
result = {}
play_url = ""
# 获取百度跳转信息(使用缓存)
global baidu_jump_cache
baidu_jump = baidu_jump_cache
# 判断是否是外部跳转链接(百度等)
if 'baidu.com' in str(id) or 'tuios.com' in str(id) or 'qmplaylet' in str(id):
# 如果是外部跳转链接,直接使用
play_url = str(id)
# 判断是否是直接的HTTP链接
elif str(id).startswith('http'):
play_url = str(id)
# 如果是内部播放ID,需要重新获取详情
else:
try:
# 获取该ID对应的详情信息
sign_string = f"playlet_id={id}{keys}"
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
detail_url = f'{xurl1}/player/api/v1/playlet/info?playlet_id={id}&sign={sign}'
detail_response = requests.get(url=detail_url, headers=headerx, timeout=5)
detail_response.encoding = "utf-8"
detail_data = detail_response.json()
# 获取标题
title = detail_data.get('data', {}).get('title', '')
if not title:
title = detail_data.get('data', {}).get('name', '')
# 统一使用七猫专线的播放列表
play_list = detail_data.get('data', {}).get('play_list', [])
if play_list:
for idx, item in enumerate(play_list):
video_url = item.get('video_url', '')
sort_name = item.get('sort', '')
if video_url and sort_name:
if play_url:
play_url += '#'
play_url += str(sort_name) + '$' + str(video_url)
else:
# 如果没有play_list,使用原始ID
play_url = str(id)
except Exception as e:
# 如果出错,使用跳转链接或原始ID
play_url = baidu_jump if baidu_jump else str(id)
result["parse"] = 0
result["playUrl"] = ''
result["url"] = play_url if play_url else str(id)
result["header"] = headers
return result
def searchContentPage(self, key, quick, pg):
result = {}
videos = []
if pg:
page = int(pg)
else:
page = 1
sign_string = f"extend=page={str(page)}read_preference=0track_id=ec1280db127955061754851657967wd={key}{keys}"
sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest()
url = f'{xurl}/api/v1/playlet/search?extend=&page={str(page)}&wd={key}&read_preference=0&track_id=ec1280db127955061754851657967&sign={sign}'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
detail = detail.json()
data = detail['data']['list']
for vod in data:
name = vod.get('title', '')
if not name:
name = vod.get('name', '未知标题')
name = re.sub(r'<[^>]+>', '', str(name))
name = ' '.join(name.split())
id = vod.get('id', '')
pic = vod.get('image_link', '')
if not pic:
pic = vod.get('cover', '')
remark = vod.get('total_num', '')
video = {
"vod_id": str(id),
"vod_name": name,
"vod_pic": pic,
"vod_remarks": str(remark) if remark else ''
}
videos.append(video)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None