Files
tvboxtg/小米/api/getapp.py
T
2026-03-07 01:13:15 +08:00

2354 lines
91 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# coding = utf-8
#!/usr/bin/python
# 新时代青年 2025.06.25 getApp第三版 后续平凡哥 七月大姐等大佬魔改 最后更新2026.01.14 增加特殊搜索签名模式
# 2026.01.15 增加搜索验证码自动和使用指定值功能
# 2026.01.17 优化外置签名逻辑:当signature_name和signature_value都为空时,不启用签名算法
# 修复:搜索请求中认证参数只放在header中,不在body中重复
# 2026.02.15 增加请求方法开关:可配置init请求使用GET或POST
# 2026.03.03 增加滑块验证功能:自动判断滑块验证,支持滑块+验证码组合验证
# 2026.03.04 增加验证码重试机制,支持外置配置重试次数
import re,sys,uuid,json,base64,urllib3,random,time,hashlib,requests
from Crypto.Cipher import AES
from base.spider import Spider
from Crypto.Util.Padding import pad,unpad
sys.path.append('..')
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class Spider(Spider):
xurl,key,iv,init_data,search_verify = '','','','',''
username, password, device_id = '', '', ''
header = {}
sort_rule = []
playua = ''
playcookie = ''
playreferer = ''
line_specific_settings = {}
vip_duration = ''
vip_config = {}
enable_delay = False
current_domain_index = 0
available_domains = []
host_ua = ''
config_ua = ''
home_ua = ''
category_ua = ''
search_ua = ''
parse_ua = ''
player_ua = ''
auto_params_level = 0
ext_timestamp = ''
ext_sign = ''
ext_android_id = ''
ext_device_id = ''
app_version_code = ''
app_verify_sign = ''
user_token = ''
auth_token = ''
login_timestamp = ''
login_sign = ''
login_device_id = ''
login_uuid = ''
category_sort_rule = []
login_verify = False
sign_mode = '1'
header_mode = 'simple'
custom_headers = {}
search_suffix = 'searchList'
external_search_headers = {}
external_sign_enabled = False
external_sign_name = ''
external_sign_value = ''
external_value_process = ''
external_algorithm_order = ''
external_sign_format = ''
external_request_order = ''
external_search_suffix = ''
init_api_suffix = 'initV119'
use_box_config = False
box_config_data = {}
box_api_headers = {}
box_search_suffix = ''
box_signature_name = ''
box_signature_value = ''
search_captcha_auto_value = ''
search_captcha_auto_mode = False
init_request_method = 'GET'
slider_id = ''
slider_target_x = 0
slider_attempted = False
captcha_attempted = False
temp_captcha_data = None
captcha_retry_count = 0
max_captcha_retries = 3
def __init__(self):
self.header = {'User-Agent': 'okhttp/3.14.9'}
self.current_domain_index = 0
self.available_domains = []
self.login_timestamp = ''
self.login_sign = ''
self.login_device_id = ''
self.login_uuid = ''
self.category_sort_rule = []
self.login_verify = False
self.sign_mode = '1'
self.header_mode = 'simple'
self.custom_headers = {}
self.search_suffix = 'searchList'
self.external_sign_enabled = False
self.external_sign_name = ''
self.external_sign_value = ''
self.external_value_process = ''
self.external_algorithm_order = ''
self.external_sign_format = ''
self.external_request_order = ''
self.external_search_suffix = ''
self.external_search_headers = {}
self.init_api_suffix = 'initV119'
self.use_box_config = False
self.box_config_data = {}
self.box_api_headers = {}
self.box_search_suffix = ''
self.box_signature_name = ''
self.box_signature_value = ''
self.search_captcha_auto_value = ''
self.search_captcha_auto_mode = False
self.init_request_method = 'GET'
self.slider_id = ''
self.slider_target_x = 0
self.slider_attempted = False
self.captcha_attempted = False
self.temp_captcha_data = None
self.captcha_retry_count = 0
self.max_captcha_retries = 3
def getName(self):
return "首页"
def init(self, extend=''):
chinese_param_map = {
'域名': 'host',
'指定域名': 'host_index',
'数据密钥': 'datakey',
'数据向量': 'dataiv',
'接口版本': 'api',
'用户代理': 'ua',
'设备标识': 'deviceid',
'登录类型': 'login_type',
'登录账号': 'username',
'登录密码': 'password',
'令牌': 'token',
'认证令牌': 'auth_token',
'用户id': 'user_id',
'刷新令牌': 'refresh_token',
'令牌过期': 'token_expire',
'host': 'host',
'host_index': 'host_index',
'datakey': 'datakey',
'dataiv': 'dataiv',
'api': 'api',
'ua': 'ua',
'deviceid': 'deviceid',
'login_type': 'login_type',
'username': 'username',
'password': 'password',
'token': 'token',
'auth_token': 'auth_token',
'user_id': 'user_id',
'refresh_token': 'refresh_token',
'token_expire': 'token_expire',
'appversioncode': 'app_version_code',
'appverifySign': 'app_verify_sign',
'AppVersionCode': 'app_version_code',
'AppverifySign': 'app_verify_sign',
'签名': 'sign',
'时间戳': 'timestamp',
'安卓ID': 'android_id',
'app版本号': 'app_version_code',
'app验证签名': 'app_verify_sign',
'version': 'app_version_code',
'Version': 'app_version_code',
'版本': 'app_version_code',
'分类排序': 'category_sort',
'登录地址': 'login_url',
'登录验证': 'login_verify',
'签名模式': 'sign_mode',
'请求头模式': 'header_mode',
'请求头': 'header_mode',
'自定义请求头': 'custom_headers',
'自定义头': 'custom_headers',
'搜索后缀': 'search_suffix',
'搜索接口': 'search_suffix',
'searchsuffix': 'search_suffix',
'searchSuffix': 'search_suffix',
'初始化后缀': 'init_suffix',
'init后缀': 'init_suffix',
'initSuffix': 'init_suffix',
'启用搜索签名': 'external_sign',
'搜索签名': 'external_sign',
'外置签名值': 'external_sign_value',
'密钥处理': 'external_value_process',
'值处理方式': 'external_value_process',
'第一层': 'external_value_process',
'第1层': 'external_value_process',
'算法顺序': 'external_algorithm_order',
'第二层': 'external_algorithm_order',
'第2层': 'external_algorithm_order',
'算法输入': 'external_algorithm_order',
'算法排序': 'external_algorithm_order',
'签名格式': 'external_sign_format',
'最终签名格式': 'external_sign_format',
'请求顺序': 'external_request_order',
'参数顺序': 'external_request_order',
'第三层': 'external_request_order',
'第3层': 'external_request_order',
'外置搜索后缀': 'external_search_suffix',
'external_sign': 'external_sign',
'external_sign_value': 'external_sign_value',
'external_value_process': 'external_value_process',
'external_algorithm_order': 'external_algorithm_order',
'external_sign_format': 'external_sign_format',
'external_request_order': 'external_request_order',
'external_search_suffix': 'external_search_suffix',
'搜索自定义请求头': 'external_search_headers',
'外置搜索请求头': 'external_search_headers',
'搜索请求头': 'external_search_headers',
'external_search_headers': 'external_search_headers',
'搜索验证码': 'search_captcha_value',
'搜索验证码自动': 'search_captcha_auto',
'search_captcha_value': 'search_captcha_value',
'search_captcha_auto': 'search_captcha_auto',
'请求方法': 'init_method',
'init_method': 'init_method',
'初始化请求方法': 'init_method',
'验证码重试次数': 'captcha_retry',
'captcha_retry': 'captcha_retry',
'验证码重试': 'captcha_retry'
}
ua_chinese_map = {
'首页ua': 'homeua',
'首页UA': 'homeua',
'配置ua': 'configua',
'配置UA': 'configua',
'分类ua': 'categoryua',
'分类UA': 'categoryua',
'搜索ua': 'searchua',
'搜索UA': 'searchua',
'解析ua': 'parseua',
'解析UA': 'parseua',
'播放ua': 'playerua',
'播放UA': 'playerua',
'hostua': 'hostua',
'hostUA': 'hostua',
'configua': 'configua',
'configUA': 'configua',
'homeua': 'homeua',
'homeUA': 'homeua',
'categoryua': 'categoryua',
'categoryUA': 'categoryua',
'searchua': 'searchua',
'searchUA': 'searchua',
'parseua': 'parseua',
'parseUA': 'parseua',
'playerua': 'playerua',
'playerUA': 'playerua'
}
ext = json.loads(extend.strip())
english_ext = {}
for key, value in ext.items():
lower_key = key.lower()
if lower_key in chinese_param_map:
english_ext[chinese_param_map[lower_key]] = value
elif key in ua_chinese_map:
english_ext[ua_chinese_map[key]] = value
else:
english_ext[key] = value
init_method_raw = english_ext.get('init_method', 'GET')
if init_method_raw.upper() in ['GET', 'POST']:
self.init_request_method = init_method_raw.upper()
else:
self.init_request_method = 'GET'
host = english_ext['host']
self.enable_delay = english_ext.get('延迟', english_ext.get('enable_delay', '0')) == '1'
auto_params_str = english_ext.get('auto_params', english_ext.get('自动参数', '0'))
try:
self.auto_params_level = int(auto_params_str)
except ValueError:
self.auto_params_level = 0
self.ext_timestamp = english_ext.get('timestamp', english_ext.get('时间戳', ''))
self.ext_sign = english_ext.get('sign', english_ext.get('签名', ''))
self.ext_android_id = english_ext.get('android_id', english_ext.get('安卓ID', ''))
self.ext_device_id = english_ext.get('deviceid', english_ext.get('devideid', ''))
app_version_raw = english_ext.get('app_version_code', english_ext.get('AppVersionCode', ''))
self.app_version_code = self._format_version_code(app_version_raw) if app_version_raw else ''
app_verify_raw = english_ext.get('app_verify_sign', english_ext.get('AppverifySign', ''))
self.app_verify_sign = self._format_verify_sign(app_verify_raw) if app_verify_raw else ''
self.sign_mode = english_ext.get('sign_mode', english_ext.get('签名模式', '1'))
header_mode_raw = english_ext.get('header_mode', english_ext.get('请求头模式', english_ext.get('请求头', '简单')))
if header_mode_raw in ['简单', '简易', 'simple']:
self.header_mode = 'simple'
elif header_mode_raw in ['完整', '完全', 'complete']:
self.header_mode = 'complete'
else:
self.header_mode = 'simple'
custom_headers_str = english_ext.get('custom_headers', english_ext.get('自定义请求头', ''))
self.custom_headers = self._parse_custom_headers(custom_headers_str)
search_suffix_raw = english_ext.get('search_suffix', 'searchList')
self.search_suffix = str(search_suffix_raw).strip()
if not self.search_suffix:
self.search_suffix = 'searchList'
init_suffix_raw = english_ext.get('init_suffix', '')
if init_suffix_raw:
self.init_api_suffix = str(init_suffix_raw).strip()
else:
self.init_api_suffix = self._determine_init_suffix(english_ext)
external_search_headers_str = english_ext.get('external_search_headers', '')
self.external_search_headers = self._parse_external_search_headers(external_search_headers_str)
external_sign_raw = english_ext.get('external_sign', '0')
self.external_sign_enabled = external_sign_raw == '1'
search_captcha_value = english_ext.get('search_captcha_value', '')
search_captcha_auto_raw = english_ext.get('search_captcha_auto', '0')
self.search_captcha_auto_mode = search_captcha_auto_raw == '1'
self.search_captcha_auto_value = search_captcha_value
captcha_retry_raw = english_ext.get('captcha_retry', '3')
try:
self.max_captcha_retries = int(captcha_retry_raw)
except ValueError:
self.max_captcha_retries = 3
if self.external_sign_enabled:
self.external_sign_name = ''
self.external_sign_value = english_ext.get('external_sign_value', '')
self.external_value_process = english_ext.get('external_value_process', '')
self.external_algorithm_order = english_ext.get('external_algorithm_order', '/{name}-{timestamp}-sb-0-{value}')
self.external_sign_format = english_ext.get('external_sign_format', '{timestamp}-sb-0-{hash}')
self.external_request_order = english_ext.get('external_request_order', 'keywords>type_id>{name}>page')
self.external_search_suffix = english_ext.get('external_search_suffix', '')
if self.external_search_suffix:
self.search_suffix = self.external_search_suffix
host_index_str = str(english_ext.get('host_index', '1')).strip()
try:
host_index = int(host_index_str) if host_index_str else 1
except ValueError:
host_index = 1
self.available_domains = self._get_available_domains(host, host_index)
if not self.available_domains:
return
host = self.available_domains[0]
ua = english_ext.get('ua')
if ua:
self.header['User-Agent'] = ua
else:
api = english_ext.get('api', '/api.php/getappapi')
if str(api) == '2' or '/api.php/qijiappapi' in str(api):
self.header['User-Agent'] = 'okhttp/3.10.0'
else:
self.header['User-Agent'] = 'okhttp/3.14.9'
self.host_ua = self._get_ua_param(english_ext, ['hostua', 'hostUA', '首页ua', '首页UA'])
self.config_ua = self._get_ua_param(english_ext, ['configua', 'configUA', '配置ua', '配置UA'])
self.home_ua = self._get_ua_param(english_ext, ['homeua', 'homeUA', '首页ua', '首页UA'])
self.category_ua = self._get_ua_param(english_ext, ['categoryua', 'categoryUA', '分类ua', '分类UA'])
self.search_ua = self._get_ua_param(english_ext, ['searchua', 'searchUA', '搜索ua', '搜索UA'])
self.parse_ua = self._get_ua_param(english_ext, ['parseua', 'parseUA', '解析ua', '解析UA'])
self.player_ua = self._get_ua_param(english_ext, ['playerua', 'playUA', '播放ua', '播放UA'])
login_verify_str = english_ext.get('login_verify', english_ext.get('登录验证', '0'))
self.login_verify = login_verify_str == '1'
self.vip_config = {
'type': english_ext.get('login_type', 'token'),
'duration': english_ext.get('会员时长', ''),
'username': english_ext.get('username', ''),
'password': english_ext.get('password', ''),
'token': english_ext.get('token', ''),
'auth_token': english_ext.get('auth_token', ''),
'user_id': english_ext.get('user_id', ''),
'refresh_token': english_ext.get('refresh_token', ''),
'token_expire': english_ext.get('token_expire', '0'),
'login_url': english_ext.get('login_url', '')
}
self._handle_device_id_generation()
if self.device_id:
self.header['app-user-device-id'] = self.device_id
api = english_ext.get('api', '/api.php/getappapi')
if str(api) == '2':
api = '/api.php/qijiappapi'
self.xurl = host + api
datakey = english_ext.get('datakey', '')
dataiv = english_ext.get('dataiv', '')
if datakey:
self.key = datakey
self.iv = dataiv if dataiv else datakey
else:
self.key = ''
self.iv = ''
sort_rule_str = english_ext.get('排序', '')
if sort_rule_str:
self.sort_rule = [s.strip().lower() for s in sort_rule_str.split('>')]
else:
self.sort_rule = []
category_sort_str = english_ext.get('category_sort', english_ext.get('分类排序', ''))
if category_sort_str:
self.category_sort_rule = [s.strip() for s in category_sort_str.split('>')]
else:
self.category_sort_rule = []
self._init_login()
self._handle_vip_verification()
self.playua = self._get_ua_param(english_ext, ['playua', 'playUA', '播放ua', '播放UA'], 'Dalvik/2.1.0 (Linux; U; Android 14; 23113RK12C Build/SKQ1.231004.001)')
self.playcookie = self._get_param(english_ext, ['playcookie', 'playCookie', '播放cookie', '播放Cookie'], '')
self.playreferer = self._get_param(english_ext, ['playreferer', 'playReferer', '播放referer', '播放Referer'], '')
self.line_specific_settings = {}
for key, value in english_ext.items():
if key.startswith('line_'):
line_key = key.replace('line_', '')
if '|' in value:
parts = value.split('|', 1)
ua_part = ''
referer_part = ''
for part in parts:
part = part.strip()
if part:
if part.startswith('http://') or part.startswith('https://'):
referer_part = part
else:
ua_part = part
if line_key not in self.line_specific_settings:
self.line_specific_settings[line_key] = {}
if ua_part:
self.line_specific_settings[line_key]['ua'] = ua_part
if referer_part:
self.line_specific_settings[line_key]['referer'] = referer_part
elif '_ua' in key or '_UA' in key:
line_name = key.replace('line_', '').replace('_ua', '').replace('_UA', '')
if line_name not in self.line_specific_settings:
self.line_specific_settings[line_name] = {}
self.line_specific_settings[line_name]['ua'] = value
elif '_referer' in key or '_Referer' in key:
line_name = key.replace('line_', '').replace('_referer', '').replace('_Referer', '')
if line_name not in self.line_specific_settings:
self.line_specific_settings[line_name] = {}
self.line_specific_settings[line_name]['referer'] = value
else:
if line_key not in self.line_specific_settings:
self.line_specific_settings[line_key] = {}
if value.startswith('http://') or value.startswith('https://'):
self.line_specific_settings[line_key]['referer'] = value
else:
self.line_specific_settings[line_key]['ua'] = value
init_headers = self.header.copy()
if self.config_ua:
init_headers['User-Agent'] = self.config_ua
init_payload = {}
init_payload, init_headers = self._add_all_params_to_request(init_payload, init_headers)
success = False
for domain in self.available_domains:
try:
self.xurl = domain + api
init_headers = self._build_headers(init_headers)
init_url = f"{self.xurl}.index/{self.init_api_suffix}"
if self.init_request_method == 'GET':
res = self.fetch(init_url, headers=init_headers, params=init_payload, verify=False)
else:
res = self.post(init_url, headers=init_headers, data=init_payload, verify=False)
if res.status_code != 200:
continue
res_data = res.json()
encrypted_data = res_data['data']
response = self.decrypt(encrypted_data)
init_data = json.loads(response)
if init_data:
self.init_data = init_data
self.search_verify = init_data['config'].get('system_search_verify_status', False)
success = True
self._auto_extract_and_judge_signature()
self._extract_search_captcha_config()
break
except Exception as e:
continue
if not success:
pass
def _extract_search_captcha_config(self):
if not self.init_data:
return
if self.search_captcha_auto_mode and not self.search_captcha_auto_value:
if 'hot_search_list' in self.init_data:
hot_search_list = self.init_data.get('hot_search_list', [])
for item in hot_search_list:
if isinstance(item, str) and '验证码' in item:
import re
captcha_patterns = [
r'验证码都是?(\d{4})',
r'验证码[::]\s*(\d{4})',
r'验证码(\d{4})',
r'(\d{4})\s*验证码'
]
for pattern in captcha_patterns:
match = re.search(pattern, item)
if match:
extracted_captcha = match.group(1)
if extracted_captcha and len(extracted_captcha) == 4 and extracted_captcha.isdigit():
self.search_captcha_auto_value = extracted_captcha
return
if 'notice' in self.init_data:
notice_data = self.init_data.get('notice', {})
if isinstance(notice_data, dict):
content_fields = ['content', 'intro', 'title']
for field in content_fields:
if field in notice_data:
content = notice_data[field]
if content and self.search_captcha_auto_mode and not self.search_captcha_auto_value:
import re
clean_text = re.sub(r'<[^>]+>', ' ', content)
clean_text = re.sub(r'\s+', ' ', clean_text).strip()
captcha_patterns = [
r'验证码都是?(\d{4})',
r'验证码[::]\s*(\d{4})',
r'验证码(\d{4})',
r'(\d{4})\s*验证码',
r'搜索.*?验证码.*?(\d{4})',
r'求片.*?验证码.*?(\d{4})',
r'反馈.*?验证码.*?(\d{4})'
]
for pattern in captcha_patterns:
match = re.search(pattern, clean_text)
if match:
extracted_captcha = match.group(1)
if extracted_captcha and len(extracted_captcha) == 4 and extracted_captcha.isdigit():
self.search_captcha_auto_value = extracted_captcha
return
elif isinstance(notice_data, str):
if self.search_captcha_auto_mode and not self.search_captcha_auto_value:
import re
clean_text = re.sub(r'<[^>]+>', ' ', notice_data)
clean_text = re.sub(r'\s+', ' ', clean_text).strip()
for pattern in [r'验证码都是?(\d{4})', r'验证码[::]\s*(\d{4})', r'验证码(\d{4})']:
match = re.search(pattern, clean_text)
if match:
extracted_captcha = match.group(1)
if extracted_captcha and len(extracted_captcha) == 4 and extracted_captcha.isdigit():
self.search_captcha_auto_value = extracted_captcha
return
if 'config' in self.init_data:
config = self.init_data['config']
if 'search_captcha' in config:
search_captcha_config = config['search_captcha']
if isinstance(search_captcha_config, str):
self.search_captcha_auto_value = search_captcha_config
elif isinstance(search_captcha_config, dict):
self.search_captcha_auto_value = search_captcha_config.get('value', '')
def _auto_extract_and_judge_signature(self):
if self.external_sign_enabled and self.external_sign_value:
return
if not self.box_config_data and self.init_data and 'box_config' in self.init_data:
self._process_box_config()
if self.box_config_data:
signature_name = self.box_config_data.get('signature_name', '')
signature_value = self.box_config_data.get('signature_value', '')
search_name = self.box_config_data.get('search_name', '')
if search_name:
self.external_search_suffix = search_name
if signature_name and signature_value:
if not self.external_sign_value:
self.external_sign_value = signature_value
if not self.external_sign_name:
self.external_sign_name = signature_name
self._auto_judge_signature_algorithm(signature_value)
self.external_sign_enabled = True
else:
self.external_sign_enabled = False
self.external_sign_name = ''
self.external_sign_value = ''
def _auto_judge_signature_algorithm(self, signature_value):
if not signature_value:
return
length = len(signature_value)
take_from_end = length - 8
self.external_value_process = f'reverse+{take_from_end}+8'
if not self.external_sign_enabled:
self.external_sign_enabled = True
if not self.external_sign_name and 'signature_name' in self.box_config_data:
self.external_sign_name = self.box_config_data['signature_name']
if not self.external_algorithm_order:
self.external_algorithm_order = '/{name}-{timestamp}-sb-0-{value}'
if not self.external_sign_format:
self.external_sign_format = '{timestamp}-sb-0-{hash}'
if not self.external_request_order:
self.external_request_order = 'keywords>type_id>{name}>page'
if not self.external_search_suffix and 'search_name' in self.box_config_data:
self.external_search_suffix = self.box_config_data['search_name']
def _universal_signature_processing(self, signature_value):
if not signature_value:
return ''
length = len(signature_value)
reversed_value = signature_value[::-1]
if length < 8:
return ''
take_from_end = length - 8
if take_from_end > 0:
part_from_end = reversed_value[8:]
else:
part_from_end = ""
part_from_start = reversed_value[:8]
result = part_from_end + part_from_start
return result
def _process_signature_value_layer1(self, value):
if not value:
return ''
if not self.external_value_process:
self.external_value_process = self._get_signature_processing_mode()
process_method = self.external_value_process
if not process_method:
process_method = 'reverse+8+8'
if '+' in process_method:
parts = process_method.split('+')
if len(parts) == 3 and parts[0] == 'reverse' and parts[2] == '8':
try:
expected_take_from_end = int(parts[1])
actual_length = len(value)
if expected_take_from_end == actual_length - 8:
return self._universal_signature_processing(value)
except ValueError:
pass
try:
parts = process_method.split('+')
should_reverse = False
if 'reverse' in parts[0].lower():
should_reverse = True
parts = parts[1:]
if should_reverse:
value = value[::-1]
if not parts or parts[0] == 'full':
return value
if len(parts) >= 2:
try:
part1_len = int(parts[0].strip())
part2_len = int(parts[1].strip())
total_len = part1_len + part2_len
if len(value) >= total_len:
part1 = value[-part1_len:] if part1_len > 0 else ''
part2 = value[:part2_len] if part2_len > 0 else ''
result = part1 + part2
return result
else:
return value
except ValueError:
pass
if process_method == 'first8':
result = value[:8] if len(value) >= 8 else value
return result
elif process_method == 'last8':
result = value[-8:] if len(value) >= 8 else value
return result
elif process_method == 'middle8':
if len(value) >= 16:
result = value[4:12]
return result
else:
return value
else:
return self._universal_signature_processing(value if not should_reverse else value[::-1])
except Exception:
return self._universal_signature_processing(value)
def _determine_init_suffix(self, english_ext):
suffix = 'initV119'
api = english_ext.get('api', '/api.php/getappapi')
is_qijiapp = str(api) == '2' or '/api.php/qijiappapi' in str(api)
external_sign_raw = english_ext.get('external_sign', '0')
is_external_sign = external_sign_raw == '1'
if is_external_sign:
suffix = 'initV122'
elif is_qijiapp:
suffix = 'initV120'
return suffix
def _process_box_config(self):
if not self.init_data or 'box_config' not in self.init_data:
return
encrypted_box = self.init_data['box_config']
if not encrypted_box:
return
try:
raw_key = self.key
if not raw_key:
return
box_key = raw_key[::-1]
key_md5 = hashlib.md5(box_key.encode()).hexdigest()
box_iv = key_md5[:16]
decrypted = self._decrypt_aes(encrypted_box, box_key, box_iv)
if decrypted:
self.box_config_data = json.loads(decrypted)
self.use_box_config = True
self._apply_box_config()
except Exception:
pass
def _decrypt_aes(self, encrypted_b64, key_str, iv_str):
try:
key_bytes = key_str.encode('utf-8')
iv_bytes = iv_str.encode('utf-8')
if len(key_bytes) < 16:
key_bytes = key_bytes.ljust(16, b'\x00')
if len(iv_bytes) < 16:
iv_bytes = iv_bytes.ljust(16, b'\x00')
encrypted_data = base64.b64decode(encrypted_b64)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
decrypted_padded = cipher.decrypt(encrypted_data)
decrypted = unpad(decrypted_padded, AES.block_size)
return decrypted.decode('utf-8')
except Exception:
return None
def _apply_box_config(self):
if not self.box_config_data:
return
config = self.box_config_data
if 'api_header' in config and isinstance(config['api_header'], dict):
api_header = config['api_header']
header_key = api_header.get('key', '')
header_value = api_header.get('value', '')
if header_key and header_value:
self.box_api_headers[header_key] = header_value
if 'search_name' in config:
self.box_search_suffix = config['search_name']
if not self.external_search_suffix:
self.external_search_suffix = config['search_name']
if 'signature_name' in config:
self.external_sign_name = config['signature_name']
self.box_signature_name = config['signature_name']
if 'signature_value' in config:
box_sig_value = config['signature_value']
self.box_signature_value = box_sig_value
signature_name = config.get('signature_name', '')
if signature_name and box_sig_value:
if not self.external_sign_value:
self.external_sign_value = box_sig_value
def _parse_external_search_headers(self, headers_str):
search_headers = {}
if not headers_str:
return search_headers
try:
if headers_str.strip().startswith('{'):
headers_dict = json.loads(headers_str)
for key, value in headers_dict.items():
if isinstance(value, (str, int, float)):
search_headers[str(key)] = str(value)
return search_headers
lines = []
if '\n' in headers_str:
lines = headers_str.split('\n')
elif '$' in headers_str:
lines = headers_str.split('$')
else:
lines = [headers_str]
for line in lines:
line = line.strip()
if not line:
continue
found_separator = False
for sep in [':', '=', '=>']:
if sep in line:
key_value = line.split(sep, 1)
if len(key_value) == 2:
key = key_value[0].strip()
value = key_value[1].strip()
if key and value:
if key.lower() == 'user-agent':
key = 'User-Agent'
search_headers[key] = value
found_separator = True
break
if not found_separator and line:
search_headers[line] = line
except Exception:
pass
return search_headers
def _get_signature_processing_mode(self):
if self.external_value_process:
return self.external_value_process
signature_value = self.external_sign_value or self.box_signature_value
if signature_value:
length = len(signature_value)
take_from_end = length - 8
auto_mode = f'reverse+{take_from_end}+8'
return auto_mode
return 'reverse+8+8'
def _generate_external_signature_payload(self, keyword, page=1):
try:
if not self.external_sign_name or not self.external_sign_value:
return {
'keywords': keyword,
'type_id': '0',
'page': str(page)
}
timestamp = str(int(time.time()))
processed_value = self._process_signature_value_layer1(self.external_sign_value)
final_signature = self._generate_signature_layer2(timestamp, processed_value, keyword, page)
request_params = self._build_request_params_layer3(keyword, page, timestamp, final_signature)
return request_params
except Exception:
return {
'keywords': keyword,
'type_id': '0',
'page': str(page)
}
def _generate_signature_layer2(self, timestamp, processed_value, keyword, page):
try:
algorithm_template = self.external_algorithm_order
if not algorithm_template:
algorithm_template = '/{name}-{timestamp}-sb-0-{value}'
algorithm_input = algorithm_template
algorithm_input = algorithm_input.replace('{name}', self.external_sign_name)
algorithm_input = algorithm_input.replace('{timestamp}', timestamp)
algorithm_input = algorithm_input.replace('{value}', processed_value)
algorithm_input = algorithm_input.replace('{salt}', 'sb-0')
algorithm_input = algorithm_input.replace('{keywords}', keyword)
algorithm_input = algorithm_input.replace('{page}', str(page))
algorithm_input = algorithm_input.replace('{type_id}', '0')
md5_hash = hashlib.md5(algorithm_input.encode()).hexdigest()
sign_format = self.external_sign_format
if not sign_format:
sign_format = '{timestamp}-sb-0-{hash}'
final_signature = sign_format
final_signature = final_signature.replace('{timestamp}', timestamp)
final_signature = final_signature.replace('{hash}', md5_hash)
final_signature = final_signature.replace('{name}', self.external_sign_name)
final_signature = final_signature.replace('{value}', processed_value)
final_signature = final_signature.replace('{keywords}', keyword)
final_signature = final_signature.replace('{page}', str(page))
return final_signature
except Exception:
raise
def _build_request_params_layer3(self, keyword, page, timestamp, final_signature):
try:
request_order = self.external_request_order
if not request_order:
request_order = 'keywords>type_id>{name}>page'
param_names = request_order.split('>')
request_params = {}
for param in param_names:
param = param.strip()
if not param:
continue
if param == '{name}':
request_params[self.external_sign_name] = final_signature
elif param == 'keywords':
request_params['keywords'] = keyword
elif param == 'type_id':
request_params['type_id'] = '0'
elif param == 'page':
request_params['page'] = str(page)
elif param == self.external_sign_name:
request_params[self.external_sign_name] = final_signature
elif param == 'timestamp':
request_params['timestamp'] = timestamp
elif param == 'sign':
request_params['sign'] = final_signature
elif param == 'signature':
request_params['signature'] = final_signature
else:
if '=' in param:
key, value = param.split('=', 1)
request_params[key] = value
return request_params
except Exception:
return {
'keywords': keyword,
'type_id': '0',
'page': str(page),
self.external_sign_name: final_signature
}
def get_slider_data(self):
timestamp = str(int(time.time()))
sign = self._generate_api_sign(timestamp)
headers = self._build_headers()
headers.update({
'app-api-verify-time': timestamp,
'app-api-verify-sign': sign,
'Accept-Encoding': 'gzip',
'app-ui-mode': 'light'
})
url = f"{self.xurl}.index/getSlider"
try:
session = requests.Session()
response = session.get(url, headers=headers, verify=False, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get('code') == 1 and 'data' in data:
encrypted_data = data['data']
decrypted = self.decrypt(encrypted_data)
if decrypted:
slider_data = json.loads(decrypted)
self.slider_id = slider_data.get('slider_id', '')
self.slider_target_x = slider_data.get('target_x', 0)
return slider_data
return None
except Exception as e:
return None
def verify_slider(self, slider_id, target_x):
timestamp = str(int(time.time()))
sign = self._generate_api_sign(timestamp)
headers = self._build_headers()
headers.update({
'app-api-verify-time': timestamp,
'app-api-verify-sign': sign,
'Accept-Encoding': 'gzip',
'app-ui-mode': 'light'
})
url = f"{self.xurl}.index/verifySlider"
data = {
"slider_id": slider_id,
"pos_x": target_x,
"timestamp": timestamp
}
try:
session = requests.Session()
response = session.post(url, headers=headers, data=data, verify=False, timeout=10)
if response.status_code == 200:
result = response.json()
if result.get('code') == 1 and '成功' in result.get('msg', ''):
return True
return False
except Exception as e:
return False
def reset_verification_flags(self):
self.slider_attempted = False
self.captcha_attempted = False
self.temp_captcha_data = None
self.captcha_retry_count = 0
def get_search_verification_code(self):
random_uuid = str(uuid.uuid4())
if self.search_captcha_auto_mode or self.search_captcha_auto_value:
try:
self.fetch(f'{self.xurl}.verify/create?key={random_uuid}',
headers=self.header, verify=False)
except Exception:
pass
if self.search_captcha_auto_value:
return {
'uuid': random_uuid,
'code': self.search_captcha_auto_value
}
elif self.search_captcha_auto_mode:
return self.verification()
else:
return None
else:
return self.verification()
def add_captcha_to_payload(self, payload, verification_data):
if not verification_data:
return payload
payload['code'] = verification_data['code']
payload['key'] = verification_data['uuid']
payload['captcha'] = verification_data['code']
payload['captcha_key'] = verification_data['uuid']
return payload
def searchContent(self, key, quick, pg=1):
self.reset_verification_flags()
max_attempts = 10
attempt = 0
while attempt < max_attempts:
attempt += 1
headers = self.header.copy()
if self.search_ua:
headers['User-Agent'] = self.search_ua
if self.external_search_headers:
for key_name, value in self.external_search_headers.items():
headers[key_name] = value
if self.use_box_config and self.box_api_headers:
for header_key, header_value in self.box_api_headers.items():
headers[header_key] = header_value
videos = []
has_valid_signature = (self.external_sign_name and self.external_sign_value)
search_suffix_to_use = self.search_suffix
if self.external_search_suffix:
search_suffix_to_use = self.external_search_suffix
elif self.use_box_config and self.box_search_suffix:
search_suffix_to_use = self.box_search_suffix
if has_valid_signature:
payload = self._generate_external_signature_payload(key, pg)
else:
if 'xiaohys.com' in self.xurl:
host = self.xurl.split('api.php')[0]
data = self.fetch(f'{host}index.php/ajax/suggest?mid=1&wd={key}').json()
for i in data['list']:
videos.append({
"vod_id": i['id'],
"vod_name": i['name'],
"vod_pic": i.get('pic')
})
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
payload = {
'keywords': key,
'type_id': "0",
'page': str(pg)
}
if self.temp_captcha_data:
payload = self.add_captcha_to_payload(payload, self.temp_captcha_data)
self.temp_captcha_data = None
headers = self._add_all_params_to_search_request(headers)
url = f'{self.xurl}.index/{search_suffix_to_use}'
try:
response = self.post(url=url, data=payload, headers=headers, verify=False)
res = response.json()
need_slider = False
need_captcha = False
msg = res.get('msg', '')
if res.get('code') == 1001 and res.get('need_slider'):
need_slider = True
elif res.get('code') == 1002:
need_captcha = True
elif res.get('code') == 0:
if '滑块' in msg:
need_slider = True
if '验证码' in msg:
need_captcha = True
if need_slider or need_captcha:
if need_slider:
if self.slider_attempted:
return {'list':[], 'msg': f'滑块验证失败: {msg}'}
self.slider_attempted = True
slider_data = self.get_slider_data()
if not slider_data or not slider_data.get('slider_id') or not slider_data.get('target_x'):
return {'list':[], 'msg': '获取滑块数据失败'}
slider_id = slider_data.get('slider_id')
target_x = slider_data.get('target_x')
if self.verify_slider(slider_id, target_x):
continue
else:
return {'list':[], 'msg': '滑块验证失败'}
if need_captcha:
if self.captcha_attempted:
if self.captcha_retry_count < self.max_captcha_retries:
self.captcha_retry_count += 1
new_captcha = self.get_search_verification_code()
if new_captcha:
self.temp_captcha_data = new_captcha
continue
return {'list':[], 'msg': f'验证码错误,已重试{self.captcha_retry_count}次: {msg}'}
self.captcha_attempted = True
captcha_data = self.get_search_verification_code()
if not captcha_data:
return {'list':[], 'msg': '获取验证码失败'}
self.temp_captcha_data = captcha_data
continue
if not res.get('data'):
return {'list':[], 'msg': res.get('msg', '搜索失败')}
encrypted_data = res['data']
kjson = self.decrypt(encrypted_data)
kjson1 = json.loads(kjson)
for i in kjson1.get('search_list', []):
id = i['vod_id']
name = i['vod_name']
pic = i['vod_pic']
remarks = i.get('vod_year', '') + ' ' + i.get('vod_class', '')
videos.append({
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remarks
})
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
except Exception as e:
continue
return {'list':[], 'msg': '搜索失败,多次尝试后无法完成验证'}
def _add_all_params_to_search_request(self, headers=None):
if headers is None:
headers = self.header.copy()
headers = self._add_search_auth_headers(headers)
headers = self._add_app_params_to_headers(headers)
return headers
def _add_search_auth_headers(self, headers):
if self.ext_device_id:
self.device_id = self.ext_device_id
headers['app-user-device-id'] = self.device_id
elif self.device_id:
headers['app-user-device-id'] = self.device_id
has_external_ts = bool(self.ext_timestamp)
has_external_sign = bool(self.ext_sign)
if has_external_ts or has_external_sign:
if self.ext_timestamp:
timestamp = self.ext_timestamp
else:
timestamp = str(int(time.time()))
if self.ext_sign:
sign = self.ext_sign
else:
sign = self._generate_api_sign(timestamp)
headers.update({
'app-api-verify-time': timestamp,
'app-api-verify-sign': sign,
'Accept-Encoding': "gzip",
'app-ui-mode': "light"
})
if self.ext_android_id:
headers['app-android-id'] = self.ext_android_id
return headers
def _parse_custom_headers(self, headers_str):
custom_headers = {}
if not headers_str:
return custom_headers
try:
if headers_str.strip().startswith('{'):
headers_dict = json.loads(headers_str)
for key, value in headers_dict.items():
if isinstance(value, (str, int, float)):
custom_headers[str(key)] = str(value)
else:
lines = headers_str.split('\n')
for line in lines:
line = line.strip()
if not line:
continue
for sep in [':', '=', '=>']:
if sep in line:
parts = line.split(sep, 1)
if len(parts) == 2:
key = parts[0].strip()
value = parts[1].strip()
if key and value:
custom_headers[key] = value
break
except Exception:
pass
return custom_headers
def _build_simple_headers(self, headers=None):
if headers is None:
headers = self.header.copy()
base_headers = {
'Accept-Encoding': 'gzip',
'Connection': 'Keep-Alive',
}
for key, value in base_headers.items():
key_exists = False
for existing_key in headers.keys():
if existing_key.lower() == key.lower():
key_exists = True
break
if not key_exists:
headers[key] = value
if self.app_version_code:
headers['app-version-code'] = self.app_version_code
if self.app_verify_sign:
headers['app-verify-sign'] = self.app_verify_sign
if self.device_id:
headers['app-user-device-id'] = self.device_id
if self.user_token:
headers['app-user-token'] = self.user_token
elif self.auth_token:
headers['app-user-token'] = self.auth_token
if self.ext_timestamp:
headers['app-api-verify-time'] = self.ext_timestamp
if self.ext_sign:
headers['app-api-verify-sign'] = self.ext_sign
return headers
def _build_complete_headers(self, headers=None):
if headers is None:
headers = self.header.copy()
base_headers = {
'Accept-Encoding': 'gzip',
'User-Agent': self.header.get('User-Agent', 'okhttp/3.14.9'),
'app-ui-mode': 'light'
}
for key, value in base_headers.items():
key_exists = False
for existing_key in headers.keys():
if existing_key.lower() == key.lower():
key_exists = True
break
if not key_exists:
headers[key] = value
if self.app_version_code:
headers['app-version-code'] = self.app_version_code
if self.app_verify_sign:
headers['app-verify-sign'] = self.app_verify_sign
if self.device_id:
headers['app-user-device-id'] = self.device_id
if self.user_token:
headers['app-user-token'] = self.user_token
elif self.auth_token:
headers['app-user-token'] = self.auth_token
if self.ext_timestamp:
headers['app-api-verify-time'] = self.ext_timestamp
if self.ext_sign:
headers['app-api-verify-sign'] = self.ext_sign
return headers
def _build_headers(self, headers=None):
base_headers = self.header.copy()
if headers is not None:
base_headers.update(headers)
if self.header_mode == 'complete':
base_headers = self._build_complete_headers(base_headers)
else:
base_headers = self._build_simple_headers(base_headers)
if self.custom_headers:
protected_headers = [
'User-Agent', 'user-agent',
'app-version-code', 'app-verify-sign',
'app-user-token', 'app-user-device-id',
'app-api-verify-time', 'app-api-verify-sign',
'app-android-id'
]
custom_copy = self.custom_headers.copy()
for protected in protected_headers:
if protected in base_headers:
custom_copy.pop(protected, None)
protected_variants = [protected.lower(), protected.upper(), protected.title()]
for variant in protected_variants:
custom_copy.pop(variant, None)
base_headers.update(custom_copy)
return base_headers
def _init_login(self):
if self.vip_config['type'] == 'login' and self.vip_config['username'] and self.vip_config['password']:
success = self.login()
if success:
return True
if self.vip_config.get('token'):
self.header['app-user-token'] = self.vip_config['token']
self.user_token = self.vip_config['token']
return True
if self.vip_config.get('auth_token'):
self.header['app-user-token'] = self.vip_config['auth_token']
self.auth_token = self.vip_config['auth_token']
return True
return False
def _generate_aes_timestamp_signature(self, timestamp):
if self.ext_sign:
return self.ext_sign
if not self.key:
return ''
try:
key_bytes = self.key.encode('utf-8')
iv_bytes = self.iv.encode('utf-8')
if len(key_bytes) < 16:
key_bytes = key_bytes.ljust(16, b'\x00')
else:
key_bytes = key_bytes[:16]
if len(iv_bytes) < 16:
iv_bytes = iv_bytes.ljust(16, b'\x00')
else:
iv_bytes = iv_bytes[:16]
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
data = pad(timestamp.encode('utf-8'), AES.block_size)
encrypted = cipher.encrypt(data)
return base64.b64encode(encrypted).decode('utf-8')
except Exception:
return ''
def _generate_api_sign(self, timestamp):
if self.ext_sign:
return self.ext_sign
if not timestamp:
return ''
if self.sign_mode == '2' or self.sign_mode.lower() == 'aes':
return self._generate_aes_timestamp_signature(timestamp)
sign_data = f"{timestamp}"
sign_hash = hashlib.md5(sign_data.encode()).digest()
sign = base64.b64encode(sign_hash).decode()
return sign
def _format_version_code(self, version_raw):
if not version_raw:
return ''
version_clean = re.sub(r'[^\d]', '', str(version_raw))
if not version_clean:
return ''
if len(version_clean) > 4:
version_clean = version_clean[:4]
return version_clean
def _format_verify_sign(self, verify_raw):
if not verify_raw:
return ''
verify_str = str(verify_raw).strip()
if verify_str.endswith('.'):
verify_str = verify_str[:-1]
return verify_str
def _add_all_params_to_request(self, payload=None, headers=None):
if headers is None:
headers = self.header.copy()
if payload is None:
payload = {}
headers, payload = self._add_external_params_to_request(headers, payload)
payload, headers = self._add_auto_params_to_request(payload, headers)
headers = self._add_app_params_to_headers(headers)
return payload, headers
def _add_external_params_to_request(self, headers, payload):
if self.ext_device_id:
self.device_id = self.ext_device_id
headers['app-user-device-id'] = self.device_id
if payload is not None:
payload['device_id'] = self.device_id
elif self.device_id:
headers['app-user-device-id'] = self.device_id
if payload is not None:
payload['device_id'] = self.device_id
has_external_ts = bool(self.ext_timestamp)
has_external_sign = bool(self.ext_sign)
if has_external_ts or has_external_sign:
if self.ext_timestamp:
timestamp = self.ext_timestamp
else:
timestamp = str(int(time.time()))
if self.ext_sign:
sign = self.ext_sign
else:
sign = self._generate_api_sign(timestamp)
headers.update({
'app-api-verify-time': timestamp,
'app-api-verify-sign': sign,
'Accept-Encoding': "gzip",
'app-ui-mode': "light"
})
if payload is not None:
payload.update({
'timestamp': timestamp,
'sign': sign
})
if self.ext_android_id:
headers['app-android-id'] = self.ext_android_id
if payload is not None:
payload['android_id'] = self.ext_android_id
return headers, payload
def _add_app_params_to_headers(self, headers):
if self.app_version_code:
headers['app-version-code'] = self.app_version_code
if self.app_verify_sign:
headers['app-verify-sign'] = self.app_verify_sign
return headers
def _get_available_domains(self, host, host_index=1):
domains = []
if re.match(r'^https?:\/\/[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(:\d+)?(\/)?$', host):
domains.append(host.rstrip('/'))
return domains
try:
response = self.fetch(host, headers=self.header, timeout=10, verify=False)
text = response.text.strip()
domain_lines = [line.strip() for line in text.splitlines() if line.strip()]
for line in domain_lines:
domain = line.rstrip('/')
if not domain.startswith(('http://', 'https://')):
domain = 'https://' + domain
domains.append(domain)
if domains:
try:
index = host_index - 1
if 0 <= index < len(domains):
selected_domain = domains[index]
return [selected_domain]
else:
return [domains[0]]
except ValueError:
return [domains[0]]
else:
return []
except Exception:
return []
def _switch_domain(self):
if len(self.available_domains) <= 1:
return False
self.current_domain_index = (self.current_domain_index + 1) % len(self.available_domains)
new_domain = self.available_domains[self.current_domain_index]
api = self.xurl.split('/api.php')[1] if '/api.php' in self.xurl else '/api.php/getappapi'
self.xurl = new_domain + api
return True
def _handle_device_id_generation(self):
if self.ext_device_id:
return
if self.auto_params_level >= 1:
self.device_id = str(uuid.uuid4()).replace('-', '')
else:
self.device_id = ''
def _get_ua_param(self, ext, param_names, default=''):
for name in param_names:
value = ext.get(name)
if value:
return value
return default
def _get_param(self, ext, param_names, default=''):
for name in param_names:
value = ext.get(name)
if value:
return value
return default
def _apply_request_delay(self):
if self.enable_delay:
delay_time = random.uniform(1, 3)
time.sleep(delay_time)
def fetch(self, url, **kwargs):
if 'playerContent' not in sys._getframe(2).f_code.co_name:
self._apply_request_delay()
max_retries = 2
response = None
for retry in range(max_retries + 1):
try:
if 'headers' in kwargs:
kwargs['headers'] = self._build_headers(kwargs['headers'])
else:
kwargs['headers'] = self._build_headers()
response = super().fetch(url, **kwargs)
if response.status_code == 200:
return response
elif response.status_code >= 500 and retry < max_retries:
self._switch_domain()
time.sleep(1)
except Exception as e:
if retry < max_retries:
self._switch_domain()
time.sleep(1)
else:
raise e
return response
def post(self, url, **kwargs):
if 'playerContent' not in sys._getframe(2).f_code.co_name:
self._apply_request_delay()
max_retries = 2
response = None
for retry in range(max_retries + 1):
try:
if 'headers' in kwargs:
kwargs['headers'] = self._build_headers(kwargs['headers'])
else:
kwargs['headers'] = self._build_headers()
response = super().post(url, **kwargs)
if response.status_code == 200:
return response
elif response.status_code >= 500 and retry < max_retries:
self._switch_domain()
time.sleep(1)
except Exception as e:
if retry < max_retries:
self._switch_domain()
time.sleep(1)
else:
raise e
return response
def get(self, url, **kwargs):
if 'playerContent' not in sys._getframe(2).f_code.co_name:
self._apply_request_delay()
max_retries = 2
response = None
for retry in range(max_retries + 1):
try:
if 'headers' in kwargs:
kwargs['headers'] = self._build_headers(kwargs['headers'])
else:
kwargs['headers'] = self._build_headers()
response = super().get(url, **kwargs)
if response.status_code == 200:
return response
elif response.status_code >= 500 and retry < max_retries:
self._switch_domain()
time.sleep(1)
except Exception as e:
if retry < max_retries:
self._switch_domain()
time.sleep(1)
else:
raise e
return response
def _handle_vip_verification(self):
vip_type = self.vip_config['type']
if vip_type == 'token':
if self.vip_config['token']:
self.header['app-user-token'] = self.vip_config['token']
self.user_token = self.vip_config['token']
self.vip_duration = self.vip_config['duration']
else:
self.vip_duration = ''
elif vip_type == 'login':
if self.vip_config['username'] and self.vip_config['password']:
if not (self.user_token or self.auth_token):
self.vip_duration = ''
else:
self.vip_duration = self.vip_config['duration']
else:
self.vip_duration = ''
else:
self.vip_duration = ''
def get_login_verification(self, verify_url):
try:
verify_payload = {
'device_id': self.login_device_id,
'timestamp': self.login_timestamp,
'sign': self.login_sign,
'key': self.login_uuid
}
verify_headers = self._build_headers()
response = self.fetch(verify_url, headers=verify_headers, params=verify_payload, verify=False)
if response.status_code == 200:
return response.content
else:
return None
except Exception:
return None
def login(self):
try:
if not self.vip_config['username'] or not self.vip_config['password']:
return False
if self.ext_device_id:
self.login_uuid = self.ext_device_id
else:
self.login_uuid = str(uuid.uuid4())
self.login_device_id = self.device_id
if self.ext_timestamp:
self.login_timestamp = self.ext_timestamp
elif self.ext_device_id or self.auto_params_level >= 1:
self.login_timestamp = str(int(time.time()))
else:
self.login_timestamp = ''
if self.ext_sign:
self.login_sign = self.ext_sign
elif self.ext_device_id or self.auto_params_level >= 1:
if self.login_timestamp:
self.login_sign = self._generate_api_sign(self.login_timestamp)
else:
self.login_sign = ''
else:
self.login_sign = ''
payload = {
'password': self.vip_config['password'],
'code': "",
'device_id': self.login_device_id,
'user_name': self.vip_config['username'],
'invite_code': "",
'is_emulator': "0",
'timestamp': self.login_timestamp,
'sign': self.login_sign,
'version': '1.0.0',
'platform': 'android',
'key': self.login_uuid
}
if not (self.ext_device_id or self.auto_params_level >= 1):
if 'timestamp' in payload:
del payload['timestamp']
if 'sign' in payload:
del payload['sign']
if self.vip_config.get('login_url'):
login_url = self.vip_config['login_url']
if '.index/' in login_url:
base_verify_url = login_url.split('.index/')[0]
else:
base_verify_url = login_url.rsplit('/', 1)[0]
else:
base_verify_url = self.xurl
login_url = f'{self.xurl}.index/appLogin'
verify_url = f"{base_verify_url}.verify/create"
if self.login_verify:
dat = self.get_login_verification(verify_url)
if not dat:
return False
base64_img = base64.b64encode(dat).decode('utf-8')
code = self.ocr(base64_img)
if not code:
return False
code = self.replace_code(code)
if not (len(code) == 4 and code.isdigit()):
return False
payload['code'] = code
headers = self._build_headers()
headers.update({'Content-Type': 'application/x-www-form-urlencoded'})
response = self.post(login_url, data=payload, headers=headers, verify=False)
if response.status_code == 200:
response_data = response.json()
if not self.login_verify and response_data.get('code') == 0 and '验证码' in response_data.get('msg', ''):
dat = self.get_login_verification(verify_url)
if not dat:
return False
base64_img = base64.b64encode(dat).decode('utf-8')
code = self.ocr(base64_img)
if not code:
return False
code = self.replace_code(code)
if not (len(code) == 4 and code.isdigit()):
return False
payload['code'] = code
response = self.post(login_url, data=payload, headers=headers, verify=False)
if response.status_code == 200:
response_data = response.json()
else:
return False
if response_data.get('code') == 1:
encrypted_data = response_data['data']
decrypted_data = self.decrypt(encrypted_data)
user_info = json.loads(decrypted_data)
if 'user' in user_info and 'auth_token' in user_info['user']:
auth_token = user_info['user']['auth_token']
self.header['app-user-token'] = auth_token
self.user_token = auth_token
self.auth_token = auth_token
user_id = user_info['user'].get('user_id', '')
vip_days = user_info['user'].get('vip_days', 0)
vip_data = {
'user_id': user_id,
'auth_token': auth_token,
'vip_days': vip_days,
'login_time': int(time.time()),
'is_vip': user_info['user'].get('is_vip', False)
}
self.vip_duration = base64.b64encode(json.dumps(vip_data).encode()).decode()
return True
else:
return False
else:
return False
else:
return False
except Exception:
return False
def homeContent(self, filter):
kjson = self.init_data
result = {"class": [], "filters": {}}
all_categories = []
for i in kjson['type_list']:
if not(i['type_name'] in {'全部', 'QQ', 'juo.one'} or '企鹅群' in i['type_name']):
all_categories.append({
"type_id": i['type_id'],
"type_name": i['type_name']
})
if self.category_sort_rule:
def category_sort_key(item):
type_name = item['type_name']
for idx, rule in enumerate(self.category_sort_rule):
if rule == type_name:
return idx
for idx, rule in enumerate(self.category_sort_rule):
if rule in type_name or type_name in rule:
return idx + 100
return 999 + hash(type_name) % 100
all_categories.sort(key=category_sort_key)
result['class'] = all_categories
name_mapping = {'class': '类型', 'area': '地区', 'lang': '语言', 'year': '年份', 'sort': '排序'}
for i in kjson['type_list']:
filter_items = []
for filter_type in i.get('filter_type_list', []):
filter_name = filter_type.get('name')
values = filter_type.get('list', [])
if not values:
continue
value_list = [{"n": value, "v": value} for value in values]
display_name = name_mapping.get(filter_name, filter_name)
key = 'by' if filter_name == 'sort' else filter_name
filter_items.append({
"key": key,
"name": display_name,
"value": value_list
})
type_id = i.get('type_id')
if filter_items:
result["filters"][str(type_id)] = filter_items
return result
def homeVideoContent(self):
headers = self.header.copy()
if self.home_ua:
headers['User-Agent'] = self.home_ua
videos = []
kjson = self.init_data
for i in kjson['type_list']:
for item in i['recommend_list']:
vod_id = item['vod_id']
name = item['vod_name']
pic = item['vod_pic']
remarks = item['vod_remarks']
video = {
"vod_id": vod_id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remarks
}
videos.append(video)
return {'list': videos}
def categoryContent(self, cid, pg, filter, ext):
headers = self.header.copy()
if self.category_ua:
headers['User-Agent'] = self.category_ua
videos = []
payload = {
'area': ext.get('area','全部'),
'year': ext.get('year','全部'),
'type_id': cid,
'page': str(pg),
'sort': ext.get('sort','最新'),
'lang': ext.get('lang','全部'),
'class': ext.get('class','全部')
}
payload, headers = self._add_all_params_to_request(payload, headers)
url = f'{self.xurl}.index/typeFilterVodList'
res = self.post(url=url, headers=headers, data=payload, verify=False).json()
encrypted_data = res['data']
kjson = self.decrypt(encrypted_data)
kjson1 = json.loads(kjson)
for i in kjson1['recommend_list']:
id = i['vod_id']
name = i['vod_name']
pic = i['vod_pic']
remarks = i['vod_remarks']
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remarks
}
videos.append(video)
return {'list': videos, 'page': pg, 'pagecount': 9999, 'limit': 90, 'total': 999999}
def detailContent(self, ids):
did = ids[0]
payload = {'vod_id': did}
payload, headers = self._add_all_params_to_request(payload)
api_endpoints = ['vodDetail', 'vodDetail2']
for endpoint in api_endpoints:
url = f'{self.xurl}.index/{endpoint}'
response = self.post(url=url, headers=headers, data=payload, verify=False)
if response.status_code == 200:
response_data = response.json()
if '到期' in response_data.get('msg', '') or response_data.get('code', 1) == 0:
return None
encrypted_data = response_data['data']
kjson1 = self.decrypt(encrypted_data)
kjson = json.loads(kjson1)
break
videos = []
play_form = ''
play_url = ''
lineid = 1
name_count = {}
if self.sort_rule:
def sort_key(line):
line_name = line['player_info']['show'].lower()
for idx, rule in enumerate(self.sort_rule):
if rule in line_name:
return idx
return len(self.sort_rule)
kjson['vod_play_list'].sort(key=sort_key)
for line in kjson['vod_play_list']:
keywords = {'防走丢', '群', '防失群', '官网'}
player_show = line['player_info']['show']
if any(keyword in player_show for keyword in keywords):
import re
match = re.search(r'^([a-zA-Z0-9\u4e00-\u9fa5]+)', player_show)
if match:
new_name = match.group(1)
else:
new_name = f'{lineid}线'
line['player_info']['show'] = new_name
player_show = new_name
count = name_count.get(player_show, 0) + 1
name_count[player_show] = count
if count > 1:
line['player_info']['show'] = f"{player_show}{count}"
play_form += line['player_info']['show'] + '$$$'
parse = line['player_info']['parse']
parse_type = line['player_info']['parse_type']
player_parse_type = line['player_info']['player_parse_type']
kurls = ""
for vod in line['urls']:
token = 'token+' + vod['token']
kurls += f"{str(vod['name'])}${parse},{vod['url']},{token},{player_parse_type},{parse_type}#"
kurls = kurls.rstrip('#')
play_url += kurls + '$$$'
lineid += 1
play_form = play_form.rstrip('$$$')
play_url = play_url.rstrip('$$$')
videos.append({
"vod_id": did,
"vod_name": kjson['vod']['vod_name'],
"vod_actor": kjson['vod']['vod_actor'].replace('演员', ''),
"vod_director": kjson['vod'].get('vod_director', '').replace('导演', ''),
"vod_content": kjson['vod']['vod_content'],
"vod_remarks": kjson['vod']['vod_remarks'],
"vod_year": kjson['vod']['vod_year'] + '年',
"vod_area": kjson['vod']['vod_area'],
"vod_play_from": play_form,
"vod_play_url": play_url
})
return {'list': videos}
def playerContent(self, flag, id, vipFlags):
line_name = flag
play_header = {'User-Agent': self.player_ua or self.playua}
line_settings = self.line_specific_settings.get(line_name, {})
if 'ua' in line_settings:
play_header['User-Agent'] = line_settings['ua']
else:
for line_key, settings in self.line_specific_settings.items():
if line_key in line_name or line_name in line_key:
if 'ua' in settings:
play_header['User-Agent'] = settings['ua']
break
if 'referer' in line_settings:
play_header['Referer'] = line_settings['referer']
else:
for line_key, settings in self.line_specific_settings.items():
if line_key in line_name or line_name in line_key:
if 'referer' in settings:
play_header['Referer'] = settings['referer']
break
if self.playcookie:
play_header['Cookie'] = self.playcookie
if self.playreferer and 'Referer' not in play_header:
play_header['Referer'] = self.playreferer
if self.vip_duration:
play_header['vip-duration'] = self.vip_duration
payload, play_header = self._add_all_params_to_request(None, play_header)
url = ''
aid = id.split(',')
uid = aid[0]
kurl = aid[1]
token = aid[2].replace('token+', '')
player_parse_type = aid[3]
parse_type = aid[4]
if parse_type == '0':
res = {"parse": 0, "url": kurl, "header": play_header}
elif parse_type == '2':
res = {"parse": 1, "url": uid+kurl, "header": play_header}
elif player_parse_type == '2':
response = self.fetch(url=f'{uid}{kurl}', headers=play_header, verify=False)
if response.status_code == 200:
kjson1 = response.json()
res = {"parse": 0, "url": kjson1['url'], "header": play_header}
else:
id1 = self.encrypt(kurl)
payload = {
'parse_api': uid,
'url': id1,
'player_parse_type': player_parse_type,
'token': token
}
if self.vip_duration:
payload['vip_duration'] = self.vip_duration
payload, parse_headers = self._add_all_params_to_request(payload, self.header.copy())
if self.parse_ua:
parse_headers['User-Agent'] = self.parse_ua
url1 = f"{self.xurl}.index/vodParse"
response = self.post(url=url1, headers=parse_headers, data=payload, verify=False)
if response.status_code == 200:
response_data = response.json()
encrypted_data = response_data['data']
kjson = self.decrypt(encrypted_data)
kjson1 = json.loads(kjson)
kjson2 = kjson1['json']
kjson3 = json.loads(kjson2)
url = kjson3['url']
res = {"parse": 0, "playUrl": '', "url": url, "header": play_header}
return res
def verification(self):
random_uuid = str(uuid.uuid4())
dat = self.fetch(f'{self.xurl}.verify/create?key={random_uuid}', headers=self.header, verify=False).content
base64_img = base64.b64encode(dat).decode('utf-8')
if not dat:
return None
code = self.ocr(base64_img)
if not code:
return None
code = self.replace_code(code)
if not (len(code) == 4 and code.isdigit()):
return None
return {'uuid': random_uuid, 'code': code}
def replace_code(self, text):
replacements = {'y': '9', '口': '0', 'q': '0', 'u': '0', 'o': '0', '>': '1', 'd': '0', 'b': '8', '已': '2','D': '0', '五': '5'}
if len(text) == 3:
text = text.replace('566', '5066')
text = text.replace('066', '1666')
return ''.join(replacements.get(c, c) for c in text)
def ocr(self, base64img):
dat2 = self.post("https://api.nn.ci/ocr/b64/text", data=base64img, headers=self.header, verify=False).text
if dat2:
return dat2
else:
return None
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
def proxyM3u8(self, params):
action = {
'url': params['url'],
'header': params.get('header', {}),
'param': params.get('param', ''),
'type': 'm3u8'
}
return action
def proxyMedia(self, params):
action = {
'url': params['url'],
'header': params.get('header', {}),
'param': params.get('param', ''),
'type': 'media'
}
return action
def proxyTs(self, params):
action = {
'url': params['url'],
'header': params.get('header', {}),
'param': params.get('param', ''),
'type': 'ts'
}
return action
def isVideoFormat(self, url):
video_formats = ['.m3u8', '.mp4', '.avi', '.mkv', '.flv', '.ts']
return any(url.lower().endswith(fmt) for fmt in video_formats)
def manualVideoCheck(self):
pass
def decrypt(self, encrypted_data_b64):
key_bytes = self.key.encode('utf-8')
iv_bytes = self.iv.encode('utf-8')
encrypted_data = base64.b64decode(encrypted_data_b64)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
decrypted_padded = cipher.decrypt(encrypted_data)
decrypted = unpad(decrypted_padded, AES.block_size)
return decrypted.decode('utf-8')
def encrypt(self, sencrypted_data):
key_bytes = self.key.encode('utf-8')
iv_bytes = self.iv.encode('utf-8')
data_bytes = sencrypted_data.encode('utf-8')
padded_data = pad(data_bytes, AES.block_size)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
encrypted_bytes = cipher.encrypt(padded_data)
encrypted_data_b64 = base64.b64encode(encrypted_bytes).decode('utf-8')
return encrypted_data_b64
def _generate_random_ua(self):
return 'okhttp/3.14.9'
def _generate_android_id(self):
return ''.join(random.choice('0123456789abcdef') for _ in range(16))
def _add_auto_params_to_request(self, payload=None, headers=None):
if headers is None:
headers = self.header.copy()
if payload is None:
payload = {}
if self.auto_params_level > 0:
if self.auto_params_level >= 1 and not self.ext_device_id and not self.device_id:
self.device_id = str(uuid.uuid4()).replace('-', '')
headers['app-user-device-id'] = self.device_id
if payload is not None:
payload['device_id'] = self.device_id
if self.auto_params_level >= 2 and not self.ext_timestamp and not self.ext_sign:
timestamp = str(int(time.time()))
sign = self._generate_api_sign(timestamp)
headers.update({
'app-api-verify-time': timestamp,
'app-api-verify-sign': sign,
'Accept-Encoding': "gzip",
'app-ui-mode': "light"
})
if payload is not None:
payload.update({
'timestamp': timestamp,
'sign': sign
})
if (self.vip_config['type'] == 'login' and
self.user_token and
self.login_timestamp and self.login_sign and
not self.ext_timestamp and not self.ext_sign and
(self.auto_params_level < 2 or (self.auto_params_level >= 2 and not self.ext_timestamp and not self.ext_sign))):
headers.update({
'app-api-verify-time': self.login_timestamp,
'app-api-verify-sign': self.login_sign,
'Accept-Encoding': "gzip",
'app-ui-mode': "light"
})
if payload is not None:
payload.update({
'timestamp': self.login_timestamp,
'sign': self.login_sign
})
return payload, headers
if __name__ == '__main__':
pass