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

294 lines
15 KiB
Python

# -*- coding: utf-8 -*-
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
from Crypto.Cipher import AES
from base.spider import Spider
from Crypto.Util.Padding import unpad,pad
from Crypto.Random import get_random_bytes
import re,os,sys,uuid,time,json,zlib,base64,urllib3,hashlib
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
sys.path.append('..')
class Spider(Spider):
def_headers2,config,host,token,req_uuid = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6299.95 Safari/537.36',
'Accept': 'application/json',
'Accept-Encoding': 'gzip',
'Content-Type': 'application/json',
'uuid': '',
'client_type': 'android',
'timestamp': '',
'sign': '',
'version': '',
'appkey': '',
'api_version': 'v1'
},{},'','',str(uuid.uuid4())
def init(self, extend=''):
try:
ext = json.loads(extend)
host = ext['host']
app_key = ext['appkey']
app_name = ext['name']
build_signature = ext['buildSignature']
build_number = ext['buildNumber']
version_name = ext['versionName']
package = ext['package']
login_path = ext.get('LoginPath', '/app/userInfo')
self.token = ext.get('token')
if not (host and app_key and app_key and app_name and build_signature and build_number and version_name and package): return
self.host = f"{host}/api"
self.def_headers2['appkey'] = app_key
self.def_headers2['version'] = ext['version']
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
payload = self.encrypt(f'{{"v":"{version_name}","n":"{app_name}","s":"{build_signature}","pl":"1","apiVersion":"v2","token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
response = self.post(f'{self.host}/app/systemInit',data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False, timeout=30).text
data = json.loads(self.decrypt(response, self.req_uuid))
self.config['player'] = data['player']
self.config['parses'] = data['parser_api']
self.config['categories'] = data['categorys']['data']
if not self.token:
device_info_cache_key = f'99app_device_info_{app_key}_mPjOv6fHcE'
device_info = self.getCache(device_info_cache_key)
if not device_info:
device_info = {'did': str(uuid.uuid4()), 'install_time': int(time.time() * 1000)}
self.setCache(device_info_cache_key,device_info)
install_time,did = device_info['install_time'],device_info['did']
update_time = install_time
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
self.encrypt(f'{{"os":"android","name":"xiaomi","version":"15","sdkInt":32,"device":"xiaomi","brand":"xiaomi","manufacturer":"xiaomi","product":"b0q","hardware":"xiaomi","isPhysicalDevice":true,"androidId":"V417IR","bootloader":"unknown","display":"V417IR release-keys","host":"a11-gz01-test","tags":"release-keys","type":"user","finger":"xiaomi/b0q/b0q:15/V619IR/613:user/release-keys","app":{{"version":"{version_name}","name":"{app_name}","package":"{package}","buildNumber":"{build_number}","buildSignature":"{build_signature}","install":{install_time},"update":{update_time}}},"did":"{did}","apiVersion":"v2","channel":"","token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
response2 = self.post(f'{self.host}{login_path}', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
device_data = json.loads(self.decrypt(response2, self.req_uuid))
try:
self.token = device_data['userInfo']['user_token']
except Exception:
self.token = uuid.uuid4().hex
except Exception:
self.host = ''
def homeContent(self, filter):
if not self.host: return None
classes = []
for i in self.config['categories']:
if isinstance(i,dict):
classes.append({'type_id': i['id'], 'type_name': i['name']})
return {'class': classes}
def homeVideoContent(self):
if not self.host: return None
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
payload = self.encrypt(f'{{"kw":"","page":"1","limit":21,"pid":"1","orderBy":"time","isCategory":1,"token":"","timestamp":"{timestamp}","nonce":"{nonce}"}}')
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
data = json.loads(self.decrypt(response,self.req_uuid))
videos = self.arr2vods(data['data'])
return {'list': videos}
def categoryContent(self, tid, pg, filter, extend):
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
payload = self.encrypt(f'{{"kw":"","page":"{pg}","limit":21,"pid":"{tid}","orderBy":"time","isCategory":1,"token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
data = json.loads(self.decrypt(response))
videos = self.arr2vods(data['data'])
return {'list': videos, 'pagecount': data['page_count'], 'page': pg}
def searchContent(self, key, quick, pg='1'):
if not self.host: return None
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
payload = self.encrypt(f'{{"kw":"{key}","page":{int(pg)},"limit":21,"orderBy":"vod_hits_month","sort":"desc","token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
response = self.post(f'{self.host}/vod/search', data=payload, headers=self.headers2(nonce,timestamp,payload),verify=False).text
data = json.loads(self.decrypt(response, self.req_uuid))
videos = self.arr2vods(data['data'])
return {'list': videos, 'pagecount': data['page_count'], 'page': pg}
def detailContent(self, ids):
if not self.host: return None
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
payload = self.encrypt(f'{{"id":"{ids[0]}","eps":"1","v":"2.0.0","pl":1,"token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
response = self.post(f'{self.host}/vod/detail', data=payload, headers=self.headers2(nonce,timestamp,payload), verify=False).text
data = json.loads(self.decrypt(response))
data = data['data']
players = self.config['player']
resources = dict(zip(data['play_from'].split('$$$'), data['play_url'].split('$$$')))
player_sequence = sorted(players.keys(), key=lambda k: players[k]['sort'], reverse=True)
show2 = []
play_urls2 = []
for key in player_sequence:
if key in resources:
player = players[key]
if player['name'] != key:
show2.append(f"{player['name']}\u2005({key})")
else:
show2.append(key)
urls = resources[key].split('#')
urls2 = [f"{parts[0]}${key}@{parts[1]}" for url_part in urls if '$' in url_part and len(parts := url_part.split('$', 1)) > 1]
play_urls2.append('#'.join(urls2))
video = {
'vod_id': data['id'],
'vod_name': data['name'],
'vod_pic': data['pic'],
'vod_remarks': data['remarks'],
'vod_year': data['year'],
'vod_area': data['area'],
'vod_actor': data['actor'],
'vod_director': data['director'],
'vod_content': data['content'],
'vod_play_from': '$$$'.join(show2),
'vod_play_url': '$$$'.join(play_urls2),
'type_name': data['class']
}
return {'list': [video]}
def playerContent(self, flag, vid, vip_flags):
jx,sniff,url,play_headers = 0,0,'',{'User-Agent':'Lavf/58.12.100'}
webview_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.95 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'gzip, deflate',
'upgrade-insecure-requests': '1',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7'
}
play_from, raw_url = vid.split('@')
try:
player = self.config['player'][play_from]
try:
server_play_headers = json.loads(player['headers'])
if isinstance(server_play_headers, dict):
play_headers.update(server_play_headers)
except Exception:
pass
if player['type'] == 0:
url = raw_url
elif player['type'] == 1:
if player['isParse'] == 1:
try:
parse_rule = player['parseUrl'].split(',')
except Exception:
parse_rule = []
webview_parses = []
for i in self.config['parses']:
try:
if parse_rule and not str(i['id']) in parse_rule:
continue
elif not(i['api_supports_code']) or i['api_supports_code'] and play_from in i['api_supports_code'].split(','):
if i['api_type'] == 'webview':
if i['api_url']:
webview_parses.append(i)
continue
elif i['api_type'] == 'json' and i['is_server_parser'] == 1:
parse_id = i['id']
nonce, timestamp = self.nonce(), str(int(time.time() * 1000))
payload = self.encrypt(f'{{"id":{parse_id},"url":"{raw_url}","token":"{self.token}","timestamp":"{timestamp}","nonce":"{nonce}"}}')
response = self.post(f'{self.host}/app/vodParser', data=payload, headers=self.headers2(nonce,timestamp,payload),verify=False).text
data = json.loads(self.decrypt(response, self.req_uuid))
play_url = data['data']
else:
jx_headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6299.95 Safari/537.36'}
response = self.fetch(f"{i['api_url']}{raw_url}", headers=jx_headers, verify=False).json()
play_url = response[i['idx_play_url']]
if play_url.startswith('http'):
url = play_url
break
except Exception:
continue
if not url:
for k in webview_parses:
try:
if parse_rule and not str(k['id']) in parse_rule:
continue
else:
if k['api_url'] and k['api_url'].startswith('http'):
return { 'jx': 0, 'parse': 1, 'url': f"{k['api_url']}{url}", 'header': play_headers}
except Exception:
continue
elif player['type'] == 2:
url, sniff = raw_url, 1
play_headers = webview_headers
except Exception:
pass
if not url:
url = vid
if re.search(r'(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com', url):
jx = 1
return { 'jx': jx, 'parse': sniff, 'url': url, 'header': play_headers}
def decrypt(self, data, key=''):
try:
if not key: key = self.req_uuid
encrypted_bytes = base64.b64decode(data)
if len(encrypted_bytes) < 16: raise ValueError
cipher = AES.new(key.replace('-', '').encode('utf-8'), AES.MODE_CBC, encrypted_bytes[:16])
decrypted_padded = cipher.decrypt(encrypted_bytes[16:])
decrypted_raw = unpad(decrypted_padded, AES.block_size)
deflated_data = zlib.decompress(decrypted_raw)
return deflated_data.decode('utf-8')
except zlib.error:
raise ValueError
except Exception:
raise ValueError
def encrypt(self, data, key=''):
try:
if not key: key = self.req_uuid
data_bytes = data.encode('utf-8')
padded_data = pad(data_bytes, AES.block_size)
iv = get_random_bytes(AES.block_size)
key_bytes = key.replace('-', '').encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
encrypted_bytes = cipher.encrypt(padded_data)
return base64.b64encode(iv + encrypted_bytes).decode('utf-8')
except Exception:
raise ValueError
def arr2vods(self, arr):
videos = []
for i in arr:
videos.append({
'vod_id': i['id'],
'vod_name': i['name'],
'vod_pic': i['pic'],
'vod_remarks': i['remarks'],
'vod_year': i['year'],
'vod_content': i.get('blurb'),
'type_name': i.get('class'),
'vod_area': i.get('area'),
'vod_actor': i.get('actor'),
'vod_director': i.get('director')
})
return videos
def headers2(self,nonce, timestamp, payload=''):
return {
**self.def_headers2,
'uuid': self.req_uuid,
'timestamp': timestamp,
'sign': self.sign(payload, timestamp, self.req_uuid,nonce, self.def_headers2['appkey']),
'nonce': nonce
}
def sign(self, body, timestamp, nonce, req_uuid, app_key):
combined = f'{body}:{timestamp}:{nonce}:{req_uuid}:{app_key}'
sha256_hash = hashlib.sha256(combined.encode('utf-8'))
return sha256_hash.hexdigest()
def nonce(self):
return base64.b64encode(os.urandom(16)).decode('utf-8')
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
pass