'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', 'Accept-Language' => 'zh-CN,zh;q=0.9', ]; /** * @var HtmlParser */ protected $htmlParser; public function __construct() { $this->htmlParser = new HtmlParser(); } /** * 初始化方法 * @param string $extend 扩展参数 */ public function init($extend = '') { // 子类实现 } /** * 获取首页分类 * @param array $filter 筛选条件 * @return array */ public function homeContent($filter) { return ['class' => []]; } /** * 获取首页推荐视频 * @return array */ public function homeVideoContent() { return ['list' => []]; } /** * 获取分类详情 * @param string $tid 分类ID * @param int $pg 页码 * @param array $filter 筛选条件 * @param array $extend 扩展参数 * @return array */ public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { return ['list' => [], 'page' => $pg, 'pagecount' => 1, 'limit' => 20, 'total' => 0]; } /** * 获取视频详情 * @param array $ids 视频ID列表 * @return array */ public function detailContent($ids) { return ['list' => []]; } /** * 搜索视频 * @param string $key 关键词 * @param bool $quick 快速搜索 * @param int $pg 页码 * @return array */ public function searchContent($key, $quick = false, $pg = 1) { return ['list' => []]; } /** * 获取播放地址 * @param string $flag 播放线路 * @param string $id 视频播放ID * @param array $vipFlags VIP标识 * @return array */ public function playerContent($flag, $id, $vipFlags = []) { return ['parse' => 0, 'url' => '', 'header' => []]; } /** * 代理请求 (可选) * @param array $params * @return mixed */ public function localProxy($params) { return null; } /** * 执行 Action (可选) * @param string $action 动作名称 * @param string $value 参数值 * @return mixed */ public function action($action, $value) { return ''; } // ================== 辅助方法 ================== protected function pdfa($html, $rule) { return $this->htmlParser->pdfa($html, $rule); } protected function pdfh($html, $rule, $baseUrl = '') { return $this->htmlParser->pdfh($html, $rule, $baseUrl); } protected function pd($html, $rule, $baseUrl = '') { if (empty($baseUrl)) { $baseUrl = $this->tryGetHost(); } return $this->htmlParser->pd($html, $rule, $baseUrl); } /** * 尝试获取子类定义的 HOST 常量或属性 */ private function tryGetHost() { try { $ref = new ReflectionClass($this); // 1. 尝试获取 HOST 属性 (优先) if ($ref->hasProperty('HOST')) { $prop = $ref->getProperty('HOST'); // PHP 8.1+ 默认可访问私有属性,只有旧版本需要手动开启 if (PHP_VERSION_ID < 80100) { $prop->setAccessible(true); } $val = $prop->getValue($this); if (!empty($val)) { return $val; } } // 2. 尝试获取 const HOST 常量 if ($ref->hasConstant('HOST')) { return $ref->getConstant('HOST'); } } catch (Exception $e) { // ignore } return ''; } /** * 快速构建分页返回结果 * @param array $list 视频列表 * @param int $pg 当前页码 * @param int $total 总记录数 (可选) * @param int $limit 每页条数 (默认 20) * @return array */ protected function pageResult($list, $pg, $total = 0, $limit = 20) { $pg = max(1, intval($pg)); $count = count($list); if ($total > 0) { $pagecount = ceil($total / $limit); } else { // 如果没有提供 total,尝试根据当前列表数量估算 if ($count < $limit) { // 当前页数据少于限制,说明是最后一页 $pagecount = $pg; $total = ($pg - 1) * $limit + $count; } else { // 还有下一页,设置一个较大的页数 $pagecount = 9999; $total = 99999; } } return [ 'list' => $list, 'page' => $pg, 'pagecount' => intval($pagecount), 'limit' => intval($limit), 'total' => intval($total) ]; } /** * 封装 HTTP 请求 * @param string $url 请求地址 * @param array $options CURL 选项 * @param array $headers 请求头 * @return string|bool */ protected function fetch($url, $options = [], $headers = []) { // 支持从 options 中传递 headers if (isset($options['headers'])) { $headers = array_merge($headers, $options['headers']); unset($options['headers']); } $ch = curl_init(); // 1. 解析自定义 header 为关联数组 $customHeaders = []; foreach ($headers as $k => $v) { if (is_numeric($k)) { // 处理 "Key: Value" 格式 $parts = explode(':', $v, 2); if (count($parts) === 2) { $key = trim($parts[0]); $value = trim($parts[1]); $customHeaders[$key] = $value; } } else { $customHeaders[$k] = $v; } } // 2. 合并请求头 (自定义覆盖默认) $finalHeadersMap = array_merge($this->headers, $customHeaders); // 3. 转换回 CURL 所需的索引数组 $mergedHeaders = []; foreach ($finalHeadersMap as $k => $v) { if ($v === "") { // To send empty header in CURL, use "Header;" (no colon) $mergedHeaders[] = $k . ";"; } else { $mergedHeaders[] = "$k: $v"; } } $defaultOptions = [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_FOLLOWLOCATION => true, CURLOPT_TIMEOUT => 15, CURLOPT_ENCODING => '', // 支持 GZIP 自动解压 CURLOPT_HTTPHEADER => $mergedHeaders, ]; // 处理 POST 数据 if (isset($options['body'])) { $defaultOptions[CURLOPT_POST] = true; $defaultOptions[CURLOPT_POSTFIELDS] = $options['body']; unset($options['body']); } // 处理 Cookie if (isset($options['cookie'])) { $defaultOptions[CURLOPT_COOKIE] = $options['cookie']; unset($options['cookie']); } // 合并用户自定义选项 foreach ($options as $k => $v) { $defaultOptions[$k] = $v; } curl_setopt_array($ch, $defaultOptions); $result = curl_exec($ch); if (is_resource($ch)) { curl_close($ch); } return $result; } protected function fetchJson($url, $options = []) { $resp = $this->fetch($url, $options); return json_decode($resp, true) ?: []; } /** * 自动运行,处理路由 */ public function run() { $ac = $_GET['ac'] ?? ''; $t = $_GET['t'] ?? ''; $pg = $_GET['pg'] ?? '1'; $wd = $_GET['wd'] ?? ''; $ids = $_GET['ids'] ?? ''; $play = $_GET['play'] ?? ''; // 某些源使用 play 参数传递播放ID $flag = $_GET['flag'] ?? ''; // 播放线路 $filter = isset($_GET['filter']) && $_GET['filter'] === 'true'; // 是否过滤 $extend = $_GET['ext'] ?? ''; // 扩展参数 if (!empty($extend) && is_string($extend)) { $decoded = json_decode(base64_decode($extend), true); if (is_array($decoded)) { $extend = $decoded; } } $action = $_GET['action'] ?? ''; // Action 动作 $value = $_GET['value'] ?? ''; // Action 参数 $this->init($extend); try { // 0. Action (优先处理) if ($ac === 'action') { echo json_encode($this->action($action, $value), JSON_UNESCAPED_UNICODE); return; } // 1. 播放 (Play) // 优先检测 play 参数或 ac=play if ($ac === 'play' || !empty($play)) { $playId = !empty($play) ? $play : ($_GET['id'] ?? ''); echo json_encode($this->playerContent($flag, $playId), JSON_UNESCAPED_UNICODE); return; } // 2. 搜索 (Search) // 有 wd 则是搜索 if (!empty($wd)) { echo json_encode($this->searchContent($wd, false, $pg), JSON_UNESCAPED_UNICODE); return; } // 3. 详情 (Detail) // 有 ids 且 ac 不为空 if (!empty($ids) && !empty($ac)) { // ids 可能是逗号分隔的字符串 $idList = explode(',', $ids); echo json_encode($this->detailContent($idList), JSON_UNESCAPED_UNICODE); return; } // 4. 分类 (Category) // 有 t 且 ac 不为空 if ($t !== '' && !empty($ac)) { // 处理 filter $filterData = []; // 暂未实现复杂 filter 解析,可根据需要扩展 echo json_encode($this->categoryContent($t, $pg, $filterData, $extend), JSON_UNESCAPED_UNICODE); return; } // 5. 首页 (默认) // 通常返回 {class: [...], list: [...]} // 可以分别调用 homeContent 和 homeVideoContent 合并 $homeData = $this->homeContent($filter); $videoData = $this->homeVideoContent(); $result = [ 'class' => $homeData['class'] ?? [], ]; // 如果 homeContent 只有 class,合并 homeVideoContent 的 list if (isset($videoData['list'])) { $result['list'] = $videoData['list']; } // 如果 homeContent 也有 list,优先使用 homeContent 的 list (视具体逻辑而定,这里简单的合并) if (isset($homeData['list']) && !empty($homeData['list'])) { $result['list'] = $homeData['list']; } // 兼容:如果 homeContent 返回了 filters if (isset($homeData['filters'])) { $result['filters'] = $homeData['filters']; } echo json_encode($result, JSON_UNESCAPED_UNICODE); } catch (Exception $e) { echo json_encode(['code' => 500, 'msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { echo json_encode(['code' => 500, 'msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE); } } }