Files
tvbox_collect/tbapi2/py/app/Hmys.py
T
2026-07-05 04:58:38 +00:00

342 lines
14 KiB
Python

# -*- coding: utf-8 -*-
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
from base.spider import Spider
from Crypto.Cipher import DES3
from Crypto.Util.Padding import unpad
import re,sys,time,json,base64,secrets,hashlib,urllib3
from urllib.parse import quote,unquote,urljoin,urlparse
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class Spider(Spider):
def __init__(self):
self.headers = {
'User-Agent': 'Android',
'Connection': 'Keep-Alive',
'Accept': 'application/vnd.yourapi.v1.full+json',
'Accept-Encoding': 'gzip',
'Device-Id': '',
'Screen-Width': '2670',
'Channel': 'guan',
'Cur-Time': '',
'Mob-Mfr': 'xiaomi',
'prefersex': '1',
'Mob-Model': 'xiaomi',
'token': '',
'Sys-Release': '15',
'appid': '',
'Version-Code': '',
'Sys-Platform': 'Android',
'Screen-Height': '1200',
'timestamp': ''
}
self.play_header = {
'User-Agent': 'Mozi'
}
self.host = ''
self.play_domain = ''
self.proxyurl = ''
self.encrypt_domain = ''
def init(self, extend=''):
try:
ext = json.loads(extend.strip())
host = ext.get('host').rstrip('/')
if not re.match(r'^https?://[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?/?$', host): return
appid = ext['app_id']
deviceid = ext.get('deviceid')
version_code = ext['versionCode']
channel = ext['UMENG_CHANNEL']
self.encrypt_domain = ext.get('EncryptDomain','vT1RQRz8YzlzTgN26pIXNJ7Mi65juwSP')
if not(appid and version_code and channel): return
if deviceid:
if not self.is_valid_android_id(deviceid): return
else:
deviceid_cache_key = f'Hmys_deviceid_{appid}_{channel}'
deviceid = self.getCache(deviceid_cache_key)
if not deviceid:
deviceid = self.device_id()
self.setCache(deviceid_cache_key,deviceid)
self.host = host
self.headers['appid'] = appid
self.headers['Channel'] = channel
self.headers['Device-Id'] = deviceid
self.headers['Version-Code'] = version_code
try:
self.proxyurl = f'{self.getProxyUrl(True)}&type=hmys'
except Exception:
self.proxyurl = 'http://127.0.0.1:9978/proxy?do=py&type=hmys'
self.login(host)
except Exception:
return
def homeContent(self, filter):
if not self.host: return None
headers = self.headers.copy()
headers['timestamp'] = self.timestamp()
response = self.post(f'{self.host}/api/block/category_type', headers=headers, verify=False).json()
data = json.loads(self.des3(response['data']))
class_list = []
for item in data['result']:
class_list.append({ 'type_id': item['type_pid'], 'type_name': item['type_name']})
filters_dict = {}
for item in data['result']:
type_id = item['type_pid']
cate_list = [cate.strip() for cate in item['cate'].split(',')]
cate_values = [{'n': cate, 'v': cate} for cate in cate_list]
area_list = [area.strip() for area in item['area'].split(',')]
area_values = [{'n': area, 'v': area} for area in area_list]
year_list = [year.strip() for year in item['year'].split(',')]
year_values = [{'n': year, 'v': year} for year in year_list]
order_list = [order.strip() for order in item['order'].split(',')]
order_values = [{'n': order, 'v': order} for order in order_list]
filters_dict[str(type_id)] = [
{'key': 'class', 'name': '类型', 'value': cate_values},
{'key': 'area', 'name': '地区', 'value': area_values},
{'key': 'year', 'name': '年份', 'value': year_values},
{'key': 'sort', 'name': '排序', 'value': order_values}
]
return {'class': class_list, 'filters': filters_dict}
def homeVideoContent(self):
if not self.host: return None
headers = self.headers.copy()
headers['timestamp'] = self.timestamp()
response = self.post(f'{self.host}/api/nav/list', headers=headers,verify=False).json()
data = self.des3(response['data'])
result = json.loads(data)['result']
if not result: return None
classes, videos, recommend_id = [], [], ''
for i in result:
if isinstance(i, dict):
if i.get('nav_name') == '推荐':
recommend_id = i.get('nav_id')
break
headers['timestamp'] = self.timestamp()
response2 = self.post(f'{self.host}/api/nav/index',data={'nav_id': recommend_id or '253'}, headers=headers, verify=False).json()
data2 = self.des3(response2['data'])
result2 = json.loads(data2)['result']
for item in result2:
if not isinstance(item, dict):
continue
for block in item.get('block_list', []):
if not isinstance(block, dict):
continue
for vod in block.get('vod_list', []):
if vod.get('type_pid') != 1:
remark = f"{vod['serial']}集全" if vod.get('is_end') == 1 else f"更新至{vod['serial']}集"
else:
remark = f"评分:{vod['score']}"
videos.append({
'vod_id': vod['vod_id'],
'vod_name': vod['title'],
'vod_pic': vod['pic'],
'vod_remarks': remark
})
return {'list': videos}
def categoryContent(self, tid, pg, filter, ext):
if not self.host: return None
headers = self.headers.copy()
headers['timestamp'] = self.timestamp()
payload = {
'area': ext.get('area','全部'),
'cate': ext.get('class','全部'),
'type_pid': tid,
'year': ext.get('year','全部'),
'length': '12',
'page': pg,
'order': ext.get('sort','最热')
}
response = self.post(f'{self.host}/api/block/category', data=payload, headers=headers, verify=False).json()
data = json.loads(self.des3(response['data']))
videos = []
for i in data.get('result',[]):
if i.get('type_pid') != 1:
remark = f"{i['serial']}集全" if i.get('is_end') == 1 else f"更新至{i['serial']}集"
else:
remark = f"评分:{i['score']}"
videos.append({
'vod_id': i['vod_id'],
'vod_name': i['title'],
'vod_pic': i['pic'],
'vod_remarks': remark
})
return {'list': videos, 'page': pg}
def searchContent(self, key, quick, pg='1'):
if not self.host: return None
headers = self.headers.copy()
headers['timestamp'] = self.timestamp()
payload = {'type_pid': "0", 'kw': key, 'pn': pg}
response = self.post(f'{self.host}/api/search/result', data=payload, headers=headers, verify=False).json()
data = json.loads(self.des3(response['data']))
videos = []
for i in data.get('result',[]):
vod_remarks = f"{i['serial']}集" if i['type_pid'] != '1' else i['tags']
if i['short_video'] == 1:
vod_remarks += ',短剧'
videos.append({
'vod_id': i['vod_id'],
'vod_name': i['title'],
'vod_pic': i['pic'],
'vod_remarks': vod_remarks,
'vod_year': i['year']
})
return {'list': videos, 'page': pg}
def detailContent(self, ids):
if not self.host: return None
headers = self.headers.copy()
headers['timestamp'] = self.timestamp()
payload = {'vod_id': ids[0]}
response = self.post(f'{self.host}/api/vod/info', data=payload, headers=headers, verify=False).json()
data = self.des3(response['data'])
result = json.loads(data)['result']
play_urls = []
for i in result['map_list']:
if isinstance(i, dict):
play_urls.append(f"{i['title']}${ids[0]}@{i['id']}@{i['collection']}")
video = {
'vod_id': result['vod_id'],
'vod_name': result['title'],
'vod_pic': result['pic'],
'vod_remarks': result['remarks'],
'vod_year': result['year'],
'vod_area': result['area'],
'vod_actor': result['actor'],
'vod_director': result['director'],
'vod_content': result['intro'],
'vod_play_from': '河马',
'vod_play_url': '#'.join(play_urls),
'type_name': result['tags']
}
return {'list': [video]}
def playerContent(self, flag, id, vipflags):
video_id, vod_map_id, collection = id.split('@', 2)
headers = self.headers.copy()
headers['timestamp'] = self.timestamp()
payload = {
'xz': "0",
'vod_map_id': vod_map_id,
'vod_id': video_id,
'collection': collection
}
response = self.post(f'{self.host}/api/vod/play_url', data=payload, headers=headers, verify=False).json()
data = self.des3(response['data'])
result = json.loads(data)['result']
try:
ck = base64.b64decode(result['ck']).decode('utf-8')
except Exception:
ck = result['ck']
vod_url = result['vod_url']
check_url = result.get('check_url')
raw_url = f"{vod_url}?{ck}"
if check_url:
url = check_url
else:
url = self.proxyurl + '&url=' + quote(raw_url, safe='') if '.m3u8' in raw_url else vod_url
return {'jx': '0', 'parse': '0', 'url': url, 'header': self.play_header}
def localProxy(self, params):
if params['type'] == "hmys":
return self.hema_m3u8_proxy(params)
return None
def login(self, host):
if self.headers['token'] and self.play_domain: return
self.headers['Cur-Time'] = self.timestamp()
headers = self.headers.copy()
headers['timestamp'] = self.timestamp()
response = self.post(f'{host}/api/user/init', data={'password': '','account': ''}, headers=headers, verify=False).json()
data = self.des3(response['data'])
result = json.loads(data)['result']
self.headers['token'] = result['user_info']['token']
self.play_domain = result['sys_conf']['play_domain']
self.host = result['sys_conf']['host_main']
if not(self.headers['token'] or self.play_domain or self.play_domain):
self.host = ''
return
headers['timestamp'] = self.timestamp()
self.post(f'{self.host}/api/stats/login', data={'action': '6'}, headers=headers, verify=False)
def hema_m3u8_proxy(self, params):
url = unquote(params['url'])
if params.get('format') == "ts":
data = {"Location": self.hls_sign(url), "Content-Length": "0"}
return [302, "text/html; charset=utf-8", None, data]
else:
data = self.hema_modify_m3u8(url)
return [200, "application/vnd.apple.mpegurl", data]
def hema_modify_m3u8(self, raw_url):
ck = raw_url.split('?')[1]
m3u8_url = self.hls_sign(raw_url)
m3u8_content = self.fetch(m3u8_url, headers=self.play_header, verify=False).text
content = m3u8_content if m3u8_content is not None else self.m3u8_content
if content is None: raise ValueError("M3U8为空")
parsed = urlparse(raw_url)
base = f"{parsed.scheme}://{parsed.netloc}{parsed.path.rsplit('/', 1)[0]}/"
output_lines = []
for line in content.splitlines():
stripped_line = line.strip()
if stripped_line and not stripped_line.startswith('#'):
if not stripped_line.startswith(('http://', 'https://')):
full_url = urljoin(base, stripped_line)
else:
full_url = stripped_line
signed_url = self.proxyurl + f'&format=ts&url=' + quote(full_url + '&' + ck,safe='')
output_lines.append(signed_url)
else:
output_lines.append(line)
return '\n'.join(output_lines)
def des3(self, base64_ciphertext):
try:
ciphertext = base64.b64decode(base64_ciphertext)
key_bytes = 'ZT8g6QH2kS3Xj7G5wG4JtU1F'.encode('utf-8')
iv_bytes = '51518888'.encode('utf-8')
cipher = DES3.new(key_bytes, DES3.MODE_CBC, iv_bytes)
plaintext_bytes = unpad(cipher.decrypt(ciphertext), DES3.block_size)
return plaintext_bytes.decode('utf-8')
except Exception as e:
return None
def hls_sign(self, url):
replaceDomain = self.play_domain
hex_time = self.hex_time()
clean_url = url.split('?')[0]
sign_data = clean_url.replace(replaceDomain, self.encrypt_domain) + hex_time
token = hashlib.md5(sign_data.encode('utf-8')).hexdigest()
separator = '&' if '?' in url else '?'
return f"{url}{separator}wsSecret={token}&wsTime={hex_time}"
def is_valid_android_id(self, android_id):
if not isinstance(android_id, str): return False
return bool(re.fullmatch(r'^[0-9a-f]{16}$', android_id))
def device_id(self):
return ''.join(secrets.choice('0123456789abcdef') for _ in range(16))
def timestamp(self):
return str(int(time.time() * 1000))
def hex_time(self):
return hex(int(time.time()))[2:]
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass