xxxx
This commit is contained in:
300
wwwroot/batch_test.php
Normal file
300
wwwroot/batch_test.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
// batch_test.php
|
||||
// 批量测试目录下所有 Spider 插件
|
||||
// 访问: http://127.0.0.1:9980/batch_test.php
|
||||
// 参数: ?format=json 返回 JSON 格式, 否则返回可读文本
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
|
||||
// 输出格式
|
||||
$format = $_GET['format'] ?? 'text';
|
||||
$isJson = $format === 'json';
|
||||
|
||||
// 排除的系统文件
|
||||
$excludeFiles = [
|
||||
'index.php',
|
||||
'config.php',
|
||||
'spider.php',
|
||||
'example_t4.php',
|
||||
'test_runner.php',
|
||||
'batch_test.php'
|
||||
];
|
||||
|
||||
// 获取当前目录下所有 PHP 文件
|
||||
$dir = __DIR__;
|
||||
$files = scandir($dir);
|
||||
$spiderFiles = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) !== 'php') continue;
|
||||
if (in_array($file, $excludeFiles)) continue;
|
||||
$spiderFiles[] = $file;
|
||||
}
|
||||
|
||||
// 测试结果
|
||||
$results = [];
|
||||
$summary = [
|
||||
'total' => count($spiderFiles),
|
||||
'passed' => 0,
|
||||
'failed' => 0,
|
||||
'skipped' => 0,
|
||||
'start_time' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
|
||||
if (!$isJson) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo "╔══════════════════════════════════════════════════════════════╗\n";
|
||||
echo "║ PHP Spider 批量测试工具 v1.0 ║\n";
|
||||
echo "╚══════════════════════════════════════════════════════════════╝\n\n";
|
||||
echo "📁 扫描目录: $dir\n";
|
||||
echo "📄 发现 " . count($spiderFiles) . " 个待测试文件\n";
|
||||
echo "⏰ 开始时间: " . $summary['start_time'] . "\n";
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n";
|
||||
}
|
||||
|
||||
foreach ($spiderFiles as $index => $file) {
|
||||
$filePath = $dir . DIRECTORY_SEPARATOR . $file;
|
||||
$testResult = [
|
||||
'file' => $file,
|
||||
'status' => 'unknown',
|
||||
'tests' => [],
|
||||
'error' => null,
|
||||
'total_time' => 0,
|
||||
];
|
||||
|
||||
$fileStartTime = microtime(true);
|
||||
|
||||
if (!$isJson) {
|
||||
$num = $index + 1;
|
||||
echo "┌─────────────────────────────────────────────────────────────┐\n";
|
||||
echo "│ [$num/" . count($spiderFiles) . "] 测试文件: $file\n";
|
||||
echo "└─────────────────────────────────────────────────────────────┘\n";
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用输出缓冲捕获 require 过程中的输出
|
||||
ob_start();
|
||||
|
||||
// 使用独立的命名空间避免类冲突
|
||||
$tempFile = $dir . DIRECTORY_SEPARATOR . '.temp_test_' . uniqid() . '.php';
|
||||
$wrapperCode = '<?php
|
||||
namespace TestNS' . uniqid() . ';
|
||||
' . file_get_contents($filePath) . '
|
||||
';
|
||||
// 由于命名空间会影响类名,我们改用不同的方法
|
||||
// 直接 require,但先检查 Spider 类是否已存在
|
||||
ob_end_clean();
|
||||
|
||||
// 简单处理:如果 Spider 类已存在,跳过
|
||||
if (class_exists('Spider', false)) {
|
||||
// 重新定义类会报错,所以需要在新进程中测试
|
||||
// 这里我们通过 HTTP 调用每个脚本的首页接口来测试
|
||||
$testResult['status'] = 'http_test';
|
||||
|
||||
// 通过 HTTP 请求测试
|
||||
$host = $_SERVER['HTTP_HOST'] ?? '127.0.0.1:9980';
|
||||
$testUrl = "http://$host/$file?filter=true";
|
||||
|
||||
if (!$isJson) {
|
||||
echo " 📡 使用 HTTP 模式测试...\n";
|
||||
echo " 🔗 URL: $testUrl\n";
|
||||
}
|
||||
|
||||
// 测试首页接口
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'timeout' => 15,
|
||||
'ignore_errors' => true,
|
||||
]
|
||||
]);
|
||||
|
||||
$startTime = microtime(true);
|
||||
$response = @file_get_contents($testUrl, false, $ctx);
|
||||
$cost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
if ($response !== false) {
|
||||
$data = json_decode($response, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$classes = $data['class'] ?? [];
|
||||
$list = $data['list'] ?? [];
|
||||
|
||||
$testResult['tests']['home'] = [
|
||||
'status' => !empty($classes) ? 'pass' : 'warn',
|
||||
'time' => $cost,
|
||||
'classes' => count($classes),
|
||||
'list' => count($list),
|
||||
];
|
||||
|
||||
if (!$isJson) {
|
||||
if (!empty($classes)) {
|
||||
echo " ✅ 首页接口: 通过 (分类: " . count($classes) . ", 耗时: {$cost}ms)\n";
|
||||
} else {
|
||||
echo " ⚠️ 首页接口: 无分类 (list: " . count($list) . ", 耗时: {$cost}ms)\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有分类,继续测试分类接口
|
||||
if (!empty($classes)) {
|
||||
$tid = $classes[0]['type_id'] ?? null;
|
||||
if ($tid) {
|
||||
$catUrl = "http://$host/$file?t=$tid&ac=detail&pg=1";
|
||||
$startTime = microtime(true);
|
||||
$catResponse = @file_get_contents($catUrl, false, $ctx);
|
||||
$catCost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
if ($catResponse !== false) {
|
||||
$catData = json_decode($catResponse, true);
|
||||
$catList = $catData['list'] ?? [];
|
||||
|
||||
$testResult['tests']['category'] = [
|
||||
'status' => !empty($catList) ? 'pass' : 'fail',
|
||||
'time' => $catCost,
|
||||
'count' => count($catList),
|
||||
];
|
||||
|
||||
if (!$isJson) {
|
||||
if (!empty($catList)) {
|
||||
echo " ✅ 分类接口: 通过 (数据: " . count($catList) . " 条, 耗时: {$catCost}ms)\n";
|
||||
} else {
|
||||
echo " ❌ 分类接口: 无数据 (耗时: {$catCost}ms)\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有数据,继续测试详情接口
|
||||
if (!empty($catList)) {
|
||||
$vodId = $catList[0]['vod_id'] ?? null;
|
||||
if ($vodId) {
|
||||
$detailUrl = "http://$host/$file?ac=detail&ids=" . urlencode($vodId);
|
||||
$startTime = microtime(true);
|
||||
$detailResponse = @file_get_contents($detailUrl, false, $ctx);
|
||||
$detailCost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
if ($detailResponse !== false) {
|
||||
$detailData = json_decode($detailResponse, true);
|
||||
$detailList = $detailData['list'] ?? [];
|
||||
$hasPlayUrl = !empty($detailList[0]['vod_play_url'] ?? '');
|
||||
|
||||
$testResult['tests']['detail'] = [
|
||||
'status' => !empty($detailList) ? ($hasPlayUrl ? 'pass' : 'warn') : 'fail',
|
||||
'time' => $detailCost,
|
||||
'has_play_url' => $hasPlayUrl,
|
||||
];
|
||||
|
||||
if (!$isJson) {
|
||||
if (!empty($detailList)) {
|
||||
$name = $detailList[0]['vod_name'] ?? '未知';
|
||||
if ($hasPlayUrl) {
|
||||
echo " ✅ 详情接口: 通过 ($name, 耗时: {$detailCost}ms)\n";
|
||||
} else {
|
||||
echo " ⚠️ 详情接口: 无播放链接 ($name, 耗时: {$detailCost}ms)\n";
|
||||
}
|
||||
} else {
|
||||
echo " ❌ 详情接口: 无数据 (耗时: {$detailCost}ms)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$testResult['status'] = 'pass';
|
||||
$summary['passed']++;
|
||||
} else {
|
||||
$testResult['status'] = 'fail';
|
||||
$testResult['error'] = 'JSON 解析失败: ' . json_last_error_msg();
|
||||
$summary['failed']++;
|
||||
|
||||
if (!$isJson) {
|
||||
echo " ❌ 响应解析失败: " . json_last_error_msg() . "\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$testResult['status'] = 'fail';
|
||||
$testResult['error'] = 'HTTP 请求失败';
|
||||
$summary['failed']++;
|
||||
|
||||
if (!$isJson) {
|
||||
echo " ❌ HTTP 请求失败\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Spider 类不存在,直接 require 测试
|
||||
ob_start();
|
||||
require_once $filePath;
|
||||
ob_end_clean();
|
||||
|
||||
if (!class_exists('Spider')) {
|
||||
throw new Exception("未找到 Spider 类");
|
||||
}
|
||||
|
||||
$spider = new Spider();
|
||||
$spider->init();
|
||||
|
||||
// 测试首页
|
||||
$startTime = microtime(true);
|
||||
$home = $spider->homeContent(true);
|
||||
$cost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
$classes = $home['class'] ?? [];
|
||||
$testResult['tests']['home'] = [
|
||||
'status' => !empty($classes) ? 'pass' : 'warn',
|
||||
'time' => $cost,
|
||||
'classes' => count($classes),
|
||||
];
|
||||
|
||||
if (!$isJson) {
|
||||
if (!empty($classes)) {
|
||||
echo " ✅ 首页接口: 通过 (分类: " . count($classes) . ", 耗时: {$cost}ms)\n";
|
||||
} else {
|
||||
echo " ⚠️ 首页接口: 无分类 (耗时: {$cost}ms)\n";
|
||||
}
|
||||
}
|
||||
|
||||
$testResult['status'] = 'pass';
|
||||
$summary['passed']++;
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
$testResult['status'] = 'error';
|
||||
$testResult['error'] = $e->getMessage();
|
||||
$summary['failed']++;
|
||||
|
||||
if (!$isJson) {
|
||||
echo " ⛔ 错误: " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$testResult['total_time'] = round((microtime(true) - $fileStartTime) * 1000, 2);
|
||||
$results[] = $testResult;
|
||||
|
||||
if (!$isJson) {
|
||||
echo " ⏱️ 总耗时: " . $testResult['total_time'] . "ms\n";
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
$summary['end_time'] = date('Y-m-d H:i:s');
|
||||
|
||||
if ($isJson) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'summary' => $summary,
|
||||
'results' => $results,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
} else {
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
|
||||
echo "📊 测试汇总\n";
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
|
||||
echo " 📄 总文件数: " . $summary['total'] . "\n";
|
||||
echo " ✅ 通过: " . $summary['passed'] . "\n";
|
||||
echo " ❌ 失败: " . $summary['failed'] . "\n";
|
||||
echo " ⏭️ 跳过: " . $summary['skipped'] . "\n";
|
||||
echo " ⏰ 结束时间: " . $summary['end_time'] . "\n";
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
|
||||
}
|
||||
|
||||
|
||||
47
wwwroot/config.php
Normal file
47
wwwroot/config.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
// 设置返回为 JSON
|
||||
// http://127.0.0.1:9980/config.php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 当前目录
|
||||
$dir = __DIR__;
|
||||
|
||||
// 当前脚本名
|
||||
$self = basename(__FILE__);
|
||||
|
||||
// 扫描目录
|
||||
$files = scandir($dir);
|
||||
|
||||
$sites = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
// 只处理 php 文件
|
||||
if (pathinfo($file, PATHINFO_EXTENSION) !== 'php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 排除自身和 index.php
|
||||
if ($file === $self || $file === 'index.php') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 文件名(不含 .php)
|
||||
$filename = pathinfo($file, PATHINFO_FILENAME);
|
||||
|
||||
$sites[] = [
|
||||
"key" => "php_" . $filename,
|
||||
"name" => $filename . "(PHP)",
|
||||
"type" => 4,
|
||||
"api" => "http://127.0.0.1:9980/" . $filename . ".php",
|
||||
"searchable" => 1,
|
||||
"quickSearch" => 1,
|
||||
"changeable" => 0
|
||||
];
|
||||
}
|
||||
|
||||
// 输出 JSON
|
||||
echo json_encode(
|
||||
["sites" => $sites],
|
||||
JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT
|
||||
);
|
||||
|
||||
182
wwwroot/example_t4.php
Normal file
182
wwwroot/example_t4.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* T4 爬虫示例脚本 - Android 版本
|
||||
*
|
||||
* 演示 T4 类型爬虫的标准接口实现
|
||||
* 这是一个模板,您可以基于此开发自己的爬虫
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 获取请求参数
|
||||
$filter = $_GET['filter'] ?? null;
|
||||
$ac = $_GET['ac'] ?? null;
|
||||
$t = $_GET['t'] ?? null;
|
||||
$pg = $_GET['pg'] ?? '1';
|
||||
$ids = $_GET['ids'] ?? null;
|
||||
$wd = $_GET['wd'] ?? null;
|
||||
$flag = $_GET['flag'] ?? null;
|
||||
$play = $_GET['play'] ?? null;
|
||||
$ext = $_GET['ext'] ?? null;
|
||||
|
||||
// 解码 ext 参数(Base64 编码的 JSON)
|
||||
$extData = [];
|
||||
if ($ext) {
|
||||
$extJson = base64_decode($ext);
|
||||
if ($extJson) {
|
||||
$extData = json_decode($extJson, true) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 首页/分类接口
|
||||
// ============================================================================
|
||||
if ($filter !== null) {
|
||||
echo json_encode([
|
||||
'class' => [
|
||||
['type_id' => '1', 'type_name' => '电影'],
|
||||
['type_id' => '2', 'type_name' => '电视剧'],
|
||||
['type_id' => '3', 'type_name' => '综艺'],
|
||||
['type_id' => '4', 'type_name' => '动漫'],
|
||||
],
|
||||
'filters' => [
|
||||
'1' => [
|
||||
[
|
||||
'key' => 'year',
|
||||
'name' => '年份',
|
||||
'value' => [
|
||||
['n' => '全部', 'v' => ''],
|
||||
['n' => '2024', 'v' => '2024'],
|
||||
['n' => '2023', 'v' => '2023'],
|
||||
['n' => '2022', 'v' => '2022'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'key' => 'area',
|
||||
'name' => '地区',
|
||||
'value' => [
|
||||
['n' => '全部', 'v' => ''],
|
||||
['n' => '大陆', 'v' => '大陆'],
|
||||
['n' => '香港', 'v' => '香港'],
|
||||
['n' => '美国', 'v' => '美国'],
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 分类列表
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && $t !== null) {
|
||||
$page = (int)$pg;
|
||||
$pageSize = 20;
|
||||
|
||||
// 模拟数据
|
||||
$list = [];
|
||||
for ($i = 1; $i <= $pageSize; $i++) {
|
||||
$id = ($page - 1) * $pageSize + $i;
|
||||
$list[] = [
|
||||
'vod_id' => (string)$id,
|
||||
'vod_name' => "示例影片 $id",
|
||||
'vod_pic' => 'https://via.placeholder.com/300x400',
|
||||
'vod_remarks' => '第' . rand(1, 20) . '集',
|
||||
'vod_year' => (string)(2020 + rand(0, 4)),
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'page' => $page,
|
||||
'pagecount' => 10,
|
||||
'limit' => $pageSize,
|
||||
'total' => 200,
|
||||
'list' => $list
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 详情接口
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && $ids !== null) {
|
||||
echo json_encode([
|
||||
'list' => [
|
||||
[
|
||||
'vod_id' => $ids,
|
||||
'vod_name' => '示例电影',
|
||||
'vod_pic' => 'https://via.placeholder.com/300x400',
|
||||
'vod_year' => '2024',
|
||||
'vod_area' => '中国',
|
||||
'vod_director' => '导演名',
|
||||
'vod_actor' => '演员A,演员B,演员C',
|
||||
'vod_content' => '这是一部精彩的示例电影,讲述了一个引人入胜的故事...',
|
||||
'vod_play_from' => '线路一$$$线路二$$$线路三',
|
||||
'vod_play_url' => implode('$$$', [
|
||||
'第1集$https://example.com/ep1.m3u8#第2集$https://example.com/ep2.m3u8#第3集$https://example.com/ep3.m3u8',
|
||||
'第1集$https://backup1.com/ep1.m3u8#第2集$https://backup1.com/ep2.m3u8#第3集$https://backup1.com/ep3.m3u8',
|
||||
'第1集$https://backup2.com/ep1.m3u8#第2集$https://backup2.com/ep2.m3u8#第3集$https://backup2.com/ep3.m3u8',
|
||||
])
|
||||
]
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 搜索接口
|
||||
// ============================================================================
|
||||
if ($wd !== null) {
|
||||
$results = [];
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$results[] = [
|
||||
'vod_id' => (string)(1000 + $i),
|
||||
'vod_name' => "搜索结果: $wd ($i)",
|
||||
'vod_pic' => 'https://via.placeholder.com/300x400',
|
||||
'vod_remarks' => 'HD',
|
||||
'vod_year' => '2024',
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'page' => 1,
|
||||
'pagecount' => 1,
|
||||
'list' => $results
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 播放解析
|
||||
// ============================================================================
|
||||
if ($flag !== null && $play !== null) {
|
||||
// 这里可以实现实际的解析逻辑
|
||||
// 例如:调用第三方解析接口、提取真实播放地址等
|
||||
|
||||
echo json_encode([
|
||||
'parse' => 0, // 0=直链, 1=需要解析
|
||||
'url' => $play, // 直接返回原始 URL
|
||||
'header' => [
|
||||
'User-Agent' => 'Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36',
|
||||
'Referer' => 'https://example.com/'
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 默认响应
|
||||
// ============================================================================
|
||||
echo json_encode([
|
||||
'error' => '未知请求',
|
||||
'params' => $_GET,
|
||||
'info' => [
|
||||
'name' => 'T4 示例爬虫',
|
||||
'version' => '1.0.0',
|
||||
'platform' => 'Android',
|
||||
'php_version' => PHP_VERSION
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
|
||||
9
wwwroot/img.php
Normal file
9
wwwroot/img.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP随机图显示
|
||||
*/
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
$img_array = glob("./img/*.jpg",GLOB_BRACE);
|
||||
$img = array_rand($img_array);
|
||||
header("location:.$img_array[$img]");
|
||||
?>
|
||||
16
wwwroot/index.php
Normal file
16
wwwroot/index.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* PHP 服务状态检测 - Android 版本
|
||||
*/
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'message' => 'PHP 服务运行正常',
|
||||
'version' => PHP_VERSION,
|
||||
'platform' => 'Android',
|
||||
'time' => date('Y-m-d H:i:s'),
|
||||
'extensions' => get_loaded_extensions()
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
|
||||
319
wwwroot/spider.php
Normal file
319
wwwroot/spider.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 道长所有
|
||||
* Date: 2026/01/23
|
||||
*/
|
||||
/**
|
||||
* PHP Spider Base Class
|
||||
* 旨在模仿 JS 版 TVBox Spider 的写法,简化 PHP 源开发
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
// 屏蔽一般警告,避免污染 JSON 输出
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
|
||||
abstract class BaseSpider {
|
||||
|
||||
// 默认请求头
|
||||
protected $headers = [
|
||||
'User-Agent' => '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',
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化方法
|
||||
* @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 playContent($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 '';
|
||||
}
|
||||
|
||||
// ================== 辅助方法 ==================
|
||||
|
||||
/**
|
||||
* 快速构建分页返回结果
|
||||
* @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 = []) {
|
||||
$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) {
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动运行,处理路由
|
||||
*/
|
||||
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->playContent($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);
|
||||
}
|
||||
}
|
||||
}
|
||||
248
wwwroot/test_runner.php
Normal file
248
wwwroot/test_runner.php
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
// test_runner.php
|
||||
// 这是一个用于测试 Spider 插件接口的脚本
|
||||
// 用法: php test_runner.php [插件文件路径]
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// 设置默认时区,避免时间相关函数警告
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
|
||||
$file = $argv[1] ?? '';
|
||||
if (!$file || !file_exists($file)) {
|
||||
die("错误: 未找到文件 '$file'\n用法: php test_runner.php [插件文件路径]\n");
|
||||
}
|
||||
|
||||
echo "==================================================\n";
|
||||
echo "正在测试文件: $file\n";
|
||||
echo "==================================================\n";
|
||||
|
||||
try {
|
||||
// 使用输出缓冲捕获 require 过程中可能的输出(如 (new Spider())->run())
|
||||
// 防止污染后续的测试输出
|
||||
ob_start();
|
||||
require_once $file;
|
||||
ob_end_clean();
|
||||
|
||||
if (!class_exists('Spider')) {
|
||||
die("错误: 在文件 '$file' 中未找到 'Spider' 类\n");
|
||||
}
|
||||
|
||||
echo "[初始化] 实例化 Spider 类...\n";
|
||||
$spider = new Spider();
|
||||
$spider->init();
|
||||
echo "[初始化] 完成\n\n";
|
||||
|
||||
// --- 1. 测试首页接口 (Home Interface) ---
|
||||
echo ">>> [1/5] 测试首页接口 (homeContent)\n";
|
||||
$startTime = microtime(true);
|
||||
$home = $spider->homeContent(true);
|
||||
$cost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
$classes = $home['class'] ?? [];
|
||||
$filters = $home['filters'] ?? [];
|
||||
|
||||
if (!empty($classes)) {
|
||||
echo " ✅ 通过 (耗时: {$cost}ms)\n";
|
||||
echo " - 获取到 " . count($classes) . " 个分类\n";
|
||||
|
||||
// 打印前几个分类名称作为示例
|
||||
$classNames = array_column(array_slice($classes, 0, 5), 'type_name');
|
||||
echo " - 分类示例: " . implode(', ', $classNames) . (count($classes) > 5 ? ' ...' : '') . "\n";
|
||||
|
||||
if (!empty($filters)) {
|
||||
echo " - 包含筛选配置 (Filters): " . count($filters) . " 组\n";
|
||||
}
|
||||
} else {
|
||||
echo " ⚠️ 警告: 未获取到分类列表 (class 为空)\n";
|
||||
}
|
||||
|
||||
// 确定用于测试分类接口的 type_id
|
||||
$tid = $classes[0]['type_id'] ?? null;
|
||||
$tname = $classes[0]['type_name'] ?? '未知分类';
|
||||
|
||||
if (!$tid && !empty($filters)) {
|
||||
// 如果 class 为空但有 filters,尝试从 filters 获取 key
|
||||
foreach ($filters as $key => $val) {
|
||||
$tid = $key;
|
||||
$tname = "FilterKey:$key";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// --- 2. 测试分类接口 (Category Interface) ---
|
||||
$vodId = null;
|
||||
$vodName = null; // 用于搜索测试
|
||||
if ($tid) {
|
||||
echo ">>> [2/5] 测试分类接口 (categoryContent) - 测试分类: [$tname] (ID: $tid)\n";
|
||||
$startTime = microtime(true);
|
||||
// 模拟传入 filter 参数为空
|
||||
$cat = $spider->categoryContent($tid, 1, false, []);
|
||||
$cost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
$list = $cat['list'] ?? [];
|
||||
if (!empty($list)) {
|
||||
echo " ✅ 通过 (耗时: {$cost}ms)\n";
|
||||
echo " - 获取到 " . count($list) . " 个资源\n";
|
||||
|
||||
$firstItem = $list[0];
|
||||
$vodId = $firstItem['vod_id'] ?? null;
|
||||
$vodName = $firstItem['vod_name'] ?? '未知名称';
|
||||
echo " - 第一条数据: [$vodName] (ID: $vodId)\n";
|
||||
} else {
|
||||
echo " ❌ 失败: 未返回资源列表 (list 为空)\n";
|
||||
}
|
||||
} else {
|
||||
echo ">>> [2/5] 测试分类接口: ⏭️ 跳过 (未找到有效的分类ID)\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// --- 3. 测试详情接口 (Detail Interface) ---
|
||||
$playUrl = null;
|
||||
$playFrom = null;
|
||||
|
||||
if ($vodId) {
|
||||
echo ">>> [3/5] 测试详情接口 (detailContent) - 测试资源ID: $vodId\n";
|
||||
$startTime = microtime(true);
|
||||
$detail = $spider->detailContent([$vodId]);
|
||||
$cost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
$detailList = $detail['list'] ?? [];
|
||||
|
||||
if (!empty($detailList)) {
|
||||
$vod = $detailList[0];
|
||||
$name = $vod['vod_name'] ?? '未知';
|
||||
// 更新 vodName,详情页的名称通常更准确
|
||||
if ($name && $name !== '未知') {
|
||||
$vodName = $name;
|
||||
}
|
||||
$playUrl = $vod['vod_play_url'] ?? '';
|
||||
$playFrom = $vod['vod_play_from'] ?? '';
|
||||
$desc = $vod['vod_content'] ?? '';
|
||||
|
||||
echo " ✅ 通过 (耗时: {$cost}ms)\n";
|
||||
echo " - 资源名称: $name\n";
|
||||
echo " - 播放源 (vod_play_from): $playFrom\n";
|
||||
|
||||
// 检查播放地址
|
||||
if (!empty($playUrl)) {
|
||||
$urlCount = substr_count($playUrl, '$');
|
||||
// 粗略估计集数,通常每集是 名称$url
|
||||
$episodeCount = $urlCount > 0 ? ($urlCount + 1) / 2 : 1;
|
||||
// 或者直接按 # 分割统计播放列表数
|
||||
$playlistCount = substr_count($playFrom, '$$$') + 1;
|
||||
|
||||
echo " - 播放列表数据长度: " . strlen($playUrl) . " 字符\n";
|
||||
// 简单展示部分播放链接
|
||||
$previewUrl = mb_substr($playUrl, 0, 50) . '...';
|
||||
echo " - 播放链接预览: $previewUrl\n";
|
||||
} else {
|
||||
echo " ⚠️ 警告: vod_play_url 为空!\n";
|
||||
}
|
||||
|
||||
if (!empty($desc)) {
|
||||
echo " - 简介长度: " . mb_strlen($desc) . " 字\n";
|
||||
}
|
||||
|
||||
} else {
|
||||
echo " ❌ 失败: 未返回详情数据\n";
|
||||
}
|
||||
} else {
|
||||
echo ">>> [3/5] 测试详情接口: ⏭️ 跳过 (未找到有效的资源ID)\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// --- 4. 测试搜索接口 (Search Interface) ---
|
||||
// 使用之前获取到的 vodName 进行搜索,如果没有则使用默认关键词 "爱"
|
||||
$searchKey = $vodName ?: "爱";
|
||||
echo ">>> [4/5] 测试搜索接口 (searchContent) - 关键词: [$searchKey]\n";
|
||||
|
||||
try {
|
||||
$startTime = microtime(true);
|
||||
$searchRes = $spider->searchContent($searchKey, false, 1);
|
||||
$cost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
$searchList = $searchRes['list'] ?? [];
|
||||
if (!empty($searchList)) {
|
||||
echo " ✅ 通过 (耗时: {$cost}ms)\n";
|
||||
echo " - 搜索到 " . count($searchList) . " 个结果\n";
|
||||
$firstSearch = $searchList[0];
|
||||
echo " - 第一条结果: " . ($firstSearch['vod_name'] ?? '未知') . "\n";
|
||||
} else {
|
||||
echo " ⚠️ 警告: 搜索未返回结果 (但这不代表接口错误)\n";
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
echo " ⚠️ 异常: 搜索接口调用失败 (允许失败)\n";
|
||||
echo " 错误信息: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// --- 5. 测试播放接口 (Player Interface) ---
|
||||
if ($playUrl && $playFrom) {
|
||||
// 解析播放链接,取第一组的第一个链接
|
||||
// 格式通常是: 播放源1$$$集数1$链接1#集数2$链接2...$$$播放源2...
|
||||
// 或者是: 集数1$链接1#集数2$链接2...
|
||||
|
||||
// 简单处理:先按 $$$ 分割取第一个播放源对应的链接串
|
||||
$playUrls = explode('$$$', $playUrl);
|
||||
$currentUrlBlock = $playUrls[0] ?? '';
|
||||
|
||||
// 再按 # 分割取第一集
|
||||
$episodes = explode('#', $currentUrlBlock);
|
||||
$firstEp = $episodes[0] ?? '';
|
||||
|
||||
// 再按 $ 分割取链接 (通常是 名称$链接)
|
||||
$parts = explode('$', $firstEp);
|
||||
$targetUrl = end($parts); // 取最后一部分作为链接
|
||||
|
||||
// 播放源flag
|
||||
$playFroms = explode('$$$', $playFrom);
|
||||
$flag = $playFroms[0] ?? 'default';
|
||||
|
||||
echo ">>> [5/5] 测试播放接口 (playerContent) - Flag: [$flag]\n";
|
||||
echo " - 目标链接: $targetUrl\n";
|
||||
|
||||
try {
|
||||
$startTime = microtime(true);
|
||||
// $flag, $id, $vipFlags
|
||||
$playerRes = $spider->playerContent($flag, $targetUrl, []);
|
||||
$cost = round((microtime(true) - $startTime) * 1000, 2);
|
||||
|
||||
if (!empty($playerRes)) {
|
||||
echo " ✅ 通过 (耗时: {$cost}ms)\n";
|
||||
// 打印返回的关键字段
|
||||
$parse = $playerRes['parse'] ?? 'N/A';
|
||||
$url = $playerRes['url'] ?? 'N/A';
|
||||
$header = $playerRes['header'] ?? 'N/A';
|
||||
|
||||
echo " - Parse: $parse\n";
|
||||
echo " - PlayUrl: $url\n";
|
||||
if (is_array($header)) {
|
||||
echo " - Header: " . json_encode($header, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
}
|
||||
} else {
|
||||
echo " ⚠️ 警告: 播放接口返回为空\n";
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
echo " ⚠️ 异常: 播放接口调用失败 (允许失败)\n";
|
||||
echo " 错误信息: " . $e->getMessage() . "\n";
|
||||
}
|
||||
} else {
|
||||
echo ">>> [5/5] 测试播放接口: ⏭️ 跳过 (未获取到有效的播放链接或播放源信息)\n";
|
||||
}
|
||||
|
||||
} catch (Throwable $e) {
|
||||
echo "\n⛔ 严重错误 (CRITICAL ERROR):\n";
|
||||
echo " 信息: " . $e->getMessage() . "\n";
|
||||
echo " 位置: " . $e->getFile() . " 第 " . $e->getLine() . " 行\n";
|
||||
echo " 堆栈:\n" . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
|
||||
echo "==================================================\n";
|
||||
echo "测试结束\n";
|
||||
2512
wwwroot/全能王终端兼容修复版.php
Normal file
2512
wwwroot/全能王终端兼容修复版.php
Normal file
File diff suppressed because it is too large
Load Diff
1028
wwwroot/自动接口.php
Normal file
1028
wwwroot/自动接口.php
Normal file
File diff suppressed because it is too large
Load Diff
318
wwwroot/色色.php
Normal file
318
wwwroot/色色.php
Normal file
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/lib/spider.php';
|
||||
|
||||
class Spider extends BaseSpider {
|
||||
|
||||
private $host;
|
||||
private $customHeaders;
|
||||
|
||||
public function getName() {
|
||||
return "复古片";
|
||||
}
|
||||
|
||||
public function init($extend = "") {
|
||||
$this->host = "https://vintagepornfun.com";
|
||||
$this->customHeaders = array(
|
||||
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Referer" => $this->host,
|
||||
"Origin" => $this->host,
|
||||
"Connection" => "keep-alive"
|
||||
);
|
||||
}
|
||||
|
||||
private function _fetch($url, $headers = null) {
|
||||
$reqHeaders = $headers ? $headers : $this->customHeaders;
|
||||
$html = $this->fetch($url, array('headers' => $reqHeaders));
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function _generateRandomString($length = 10) {
|
||||
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
$charactersLength = strlen($characters);
|
||||
$randomString = '';
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$randomString .= $characters[rand(0, $charactersLength - 1)];
|
||||
}
|
||||
return $randomString;
|
||||
}
|
||||
|
||||
private function _resolve_myvidplay($url) {
|
||||
try {
|
||||
$embed = str_replace("/d/", "/e/", $url);
|
||||
if (strpos($embed, 'd000d.com') !== false) {
|
||||
$embed = str_replace('d000d.com', 'myvidplay.com', $embed);
|
||||
}
|
||||
if (strpos($embed, 'doood.com') !== false) {
|
||||
$embed = str_replace('doood.com', 'myvidplay.com', $embed);
|
||||
}
|
||||
|
||||
$parsedUrl = parse_url($embed);
|
||||
$host = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
|
||||
|
||||
$hReq = array(
|
||||
"User-Agent" => $this->customHeaders['User-Agent'],
|
||||
"Referer" => $this->host
|
||||
);
|
||||
|
||||
$r = $this->fetch($embed, array('headers' => $hReq));
|
||||
if (!$r) {
|
||||
return array('parse' => 1, 'url' => $url, 'header' => $hReq);
|
||||
}
|
||||
|
||||
$m = array();
|
||||
preg_match('/\/pass_md5\/[^\'"]+/', $r, $m);
|
||||
if (empty($m)) {
|
||||
return array('parse' => 1, 'url' => $url, 'header' => $hReq);
|
||||
}
|
||||
|
||||
$hReq["Referer"] = $embed;
|
||||
$prefix = trim($this->fetch($host . $m[0], array('headers' => $hReq)));
|
||||
|
||||
if (substr($prefix, 0, 4) !== "http") {
|
||||
return array('parse' => 1, 'url' => $url, 'header' => $hReq);
|
||||
}
|
||||
|
||||
$parts = explode("/", $m[0]);
|
||||
$token = end($parts);
|
||||
$rnd = $this->_generateRandomString(10);
|
||||
|
||||
$finalHeaders = array(
|
||||
'User-Agent' => $this->customHeaders['User-Agent'],
|
||||
'Referer' => $host . '/',
|
||||
'Connection' => 'keep-alive'
|
||||
);
|
||||
|
||||
return array(
|
||||
'parse' => 0,
|
||||
'url' => $prefix . $rnd . '?token=' . $token,
|
||||
'header' => $finalHeaders
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
return array('parse' => 1, 'url' => $url, 'header' => $this->customHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
public function homeContent($filter) {
|
||||
$classes = array(
|
||||
array("type_name" => "最新更新", "type_id" => "latest"),
|
||||
array("type_name" => "70年代", "type_id" => "70s-porn"),
|
||||
array("type_name" => "80年代", "type_id" => "80s-porn"),
|
||||
array("type_name" => "亚洲经典", "type_id" => "asian-vintage-porn"),
|
||||
array("type_name" => "欧洲经典", "type_id" => "euro-porn-movies"),
|
||||
array("type_name" => "日本经典", "type_id" => "japanese-vintage-porn"),
|
||||
array("type_name" => "法国经典", "type_id" => "french-vintage-porn"),
|
||||
array("type_name" => "德国经典", "type_id" => "german-vintage-porn"),
|
||||
array("type_name" => "意大利经典", "type_id" => "italian-vintage-porn"),
|
||||
array("type_name" => "经典影片", "type_id" => "classic-porn-movies")
|
||||
);
|
||||
|
||||
$sortConf = array(
|
||||
"key" => "order",
|
||||
"name" => "排序",
|
||||
"value" => array(
|
||||
array("n" => "默认", "v" => ""),
|
||||
array("n" => "最新", "v" => "date"),
|
||||
array("n" => "随机", "v" => "rand"),
|
||||
array("n" => "标题", "v" => "title"),
|
||||
array("n" => "热度", "v" => "comment_count")
|
||||
)
|
||||
);
|
||||
|
||||
$tagConf = array(
|
||||
"key" => "tag",
|
||||
"name" => "标签",
|
||||
"value" => array(
|
||||
array("n" => "全部", "v" => ""),
|
||||
array("n" => "70年代", "v" => "70s-porn"),
|
||||
array("n" => "80年代", "v" => "80s-porn"),
|
||||
array("n" => "90年代", "v" => "90s-porn"),
|
||||
array("n" => "肛交", "v" => "anal-sex"),
|
||||
array("n" => "亚洲", "v" => "asian"),
|
||||
array("n" => "大胸", "v" => "big-boobs"),
|
||||
array("n" => "金发", "v" => "blonde"),
|
||||
array("n" => "经典", "v" => "classic"),
|
||||
array("n" => "喜剧", "v" => "comedy"),
|
||||
array("n" => "绿帽", "v" => "cuckold"),
|
||||
array("n" => "黑人", "v" => "ebony"),
|
||||
array("n" => "欧洲", "v" => "european"),
|
||||
array("n" => "法国", "v" => "french"),
|
||||
array("n" => "德国", "v" => "german"),
|
||||
array("n" => "群交", "v" => "group-sex"),
|
||||
array("n" => "多毛", "v" => "hairy-porn"),
|
||||
array("n" => "跨种族", "v" => "interracial"),
|
||||
array("n" => "意大利", "v" => "italian"),
|
||||
array("n" => "女同", "v" => "lesbian"),
|
||||
array("n" => "熟女", "v" => "milf"),
|
||||
array("n" => "乱交", "v" => "orgy"),
|
||||
array("n" => "户外", "v" => "public-sex"),
|
||||
array("n" => "复古", "v" => "retro"),
|
||||
array("n" => "少女", "v" => "teen-sex"),
|
||||
array("n" => "3P", "v" => "threesome"),
|
||||
array("n" => "老片", "v" => "vintage-porn"),
|
||||
array("n" => "偷窥", "v" => "voyeur")
|
||||
)
|
||||
);
|
||||
|
||||
$filters = array();
|
||||
foreach ($classes as $item) {
|
||||
$filters[$item['type_id']] = array($sortConf, $tagConf);
|
||||
}
|
||||
|
||||
return array("class" => $classes, "filters" => $filters);
|
||||
}
|
||||
|
||||
public function homeVideoContent() {
|
||||
return array("list" => array());
|
||||
}
|
||||
|
||||
public function categoryContent($tid, $pg = 1, $filter = array(), $extend = array()) {
|
||||
if ($tid == "latest") {
|
||||
$url = ($pg == 1) ? $this->host : $this->host . "/page/" . $pg . "/";
|
||||
} else {
|
||||
$base = $this->host . "/category/" . $tid;
|
||||
$url = ($pg == 1) ? $base . "/" : $base . "/page/" . $pg . "/";
|
||||
}
|
||||
|
||||
$queryParts = array();
|
||||
if (isset($extend['order']) && $extend['order']) {
|
||||
$queryParts[] = "orderby=" . $extend['order'];
|
||||
}
|
||||
if (isset($extend['tag']) && $extend['tag']) {
|
||||
$queryParts[] = "tag=" . $extend['tag'];
|
||||
}
|
||||
|
||||
if (!empty($queryParts)) {
|
||||
$sep = strpos($url, '?') !== false ? '&' : '?';
|
||||
$url .= $sep . implode('&', $queryParts);
|
||||
}
|
||||
|
||||
return $this->_get_list($url, intval($pg));
|
||||
}
|
||||
|
||||
private function _get_list($url, $page = 1) {
|
||||
$videos = array();
|
||||
$html = $this->_fetch($url);
|
||||
if ($html) {
|
||||
$articles = array();
|
||||
preg_match_all('/<article[^>]*>(.*?)<\/article>/s', $html, $articles);
|
||||
if (isset($articles[1])) {
|
||||
foreach ($articles[1] as $item) {
|
||||
$aMatch = array();
|
||||
if (!preg_match('/<a[^>]*href=["\']([^"\']+)["\']/', $item, $aMatch)) {
|
||||
continue;
|
||||
}
|
||||
$href = $aMatch[1];
|
||||
|
||||
$pic = "";
|
||||
$imgMatch = array();
|
||||
if (preg_match('/<img[^>]*data-src=["\']([^"\']+)["\']/', $item, $imgMatch)) {
|
||||
$pic = $imgMatch[1];
|
||||
} elseif (preg_match('/<img[^>]*src=["\']([^"\']+)["\']/', $item, $imgMatch)) {
|
||||
$pic = $imgMatch[1];
|
||||
}
|
||||
|
||||
if ($pic && substr($pic, 0, 4) !== "http") {
|
||||
$pic = $this->host . $pic;
|
||||
}
|
||||
|
||||
$name = "";
|
||||
$headMatch = array();
|
||||
if (preg_match('/class="entry-header"[^>]*>(.*?)<\/div>/s', $item, $headMatch)) {
|
||||
$name = strip_tags($headMatch[1]);
|
||||
} else {
|
||||
$titleMatch = array();
|
||||
if (preg_match('/title=["\']([^"\']+)["\']/', $item, $titleMatch)) {
|
||||
$name = $titleMatch[1];
|
||||
}
|
||||
}
|
||||
$name = trim($name);
|
||||
|
||||
$remarks = "";
|
||||
$remMatch = array();
|
||||
if (preg_match('/class="rating-bar"[^>]*>(.*?)<\/div>/s', $item, $remMatch)) {
|
||||
$remarks = trim(strip_tags($remMatch[1]));
|
||||
}
|
||||
|
||||
$videos[] = array(
|
||||
"vod_id" => $href,
|
||||
"vod_name" => $name ? $name : "",
|
||||
"vod_pic" => $pic,
|
||||
"vod_remarks" => $remarks
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pagecount = (!empty($videos) ? $page + 1 : $page);
|
||||
return array(
|
||||
"list" => $videos,
|
||||
"page" => $page,
|
||||
"pagecount" => $pagecount,
|
||||
"limit" => 20,
|
||||
"total" => 999
|
||||
);
|
||||
}
|
||||
|
||||
public function detailContent($ids) {
|
||||
$html = $this->_fetch($ids[0]);
|
||||
if (!$html) {
|
||||
return array("list" => array());
|
||||
}
|
||||
|
||||
$metaImg = "";
|
||||
$metaMatch = array();
|
||||
if (preg_match('/<meta[^>]*property="og:image"[^>]*content=["\']([^"\']+)["\']/', $html, $metaMatch)) {
|
||||
$metaImg = $metaMatch[1];
|
||||
}
|
||||
|
||||
$metaDesc = "";
|
||||
if (preg_match('/<meta[^>]*property="og:description"[^>]*content=["\']([^"\']+)["\']/', $html, $metaMatch)) {
|
||||
$metaDesc = $metaMatch[1];
|
||||
}
|
||||
|
||||
$name = "";
|
||||
$h1Match = array();
|
||||
if (preg_match('/<h1[^>]*>(.*?)<\/h1>/s', $html, $h1Match)) {
|
||||
$name = trim(strip_tags($h1Match[1]));
|
||||
}
|
||||
|
||||
$playUrl = "";
|
||||
$m = array();
|
||||
if (preg_match('/src=["\'](https?:\/\/(?:[^"\']*(?:d000d|doood|myvidplay)\.[a-z]+)\/e\/[a-zA-Z0-9]+)/i', $html, $m)) {
|
||||
$playUrl = $m[1];
|
||||
} else {
|
||||
$iframeMatch = array();
|
||||
if (preg_match('/<iframe[^>]*src=["\']([^"\']*\/e\/[^"\']+)["\']/', $html, $iframeMatch)) {
|
||||
$playUrl = $iframeMatch[1];
|
||||
}
|
||||
}
|
||||
|
||||
$vodPlayUrl = $playUrl ? 'HD$' . $playUrl : '无资源$#';
|
||||
$result = array(
|
||||
"list" => array(
|
||||
array(
|
||||
"vod_id" => $ids[0],
|
||||
"vod_name" => $name,
|
||||
"vod_pic" => $metaImg,
|
||||
"vod_content" => $metaDesc,
|
||||
"vod_play_from" => "文艺复兴",
|
||||
"vod_play_url" => $vodPlayUrl
|
||||
)
|
||||
)
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function searchContent($key, $quick = false, $pg = 1) {
|
||||
return $this->_get_list($this->host . "/page/" . $pg . "/?s=" . urlencode($key), intval($pg));
|
||||
}
|
||||
|
||||
public function playerContent($flag, $id, $vipFlags = array()) {
|
||||
if ($flag == 'myvidplay' || strpos($id, 'myvidplay') !== false || strpos($id, 'd000d') !== false || strpos($id, 'doood') !== false) {
|
||||
return $this->_resolve_myvidplay($id);
|
||||
}
|
||||
return array("parse" => 1, "url" => $id, "header" => $this->customHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
(new Spider())->run();
|
||||
402
wwwroot/虎牙直播.php
Normal file
402
wwwroot/虎牙直播.php
Normal file
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/lib/spider.php';
|
||||
|
||||
class HuyaSpider extends BaseSpider
|
||||
{
|
||||
private const TITLE = "虎牙直播";
|
||||
private const HOST = "https://www.huya.com";
|
||||
private const HOME_URL = "/cache.php?m=LiveList&do=getLiveListByPage&gameId=2168&tagAll=0&page=1";
|
||||
private const CATEGORY_URL_TEMPLATE = "/cache.php?m=LiveList&do=getLiveListByPage&gameId=fyfilter&tagAll=0&page=fypage";
|
||||
private const ROOM_INFO_URL = "https://mp.huya.com/cache.php?m=Live&do=profileRoom&roomid=";
|
||||
private const SEARCH_URL = "https://search.cdn.huya.com/?m=Search&do=getSearchContent&q=**&uid=0&v=4&typ=-5&livestate=0&rows=40&start=0";
|
||||
|
||||
public function init($extend = "")
|
||||
{
|
||||
}
|
||||
|
||||
public function homeContent($filter)
|
||||
{
|
||||
$classes = [
|
||||
['type_id' => '8', 'type_name' => '娱乐'],
|
||||
['type_id' => '1', 'type_name' => '网游'],
|
||||
['type_id' => '2', 'type_name' => '单机'],
|
||||
['type_id' => '3', 'type_name' => '手游']
|
||||
];
|
||||
|
||||
$filters = $this->getFilters();
|
||||
$recommend = $this->getRecommendVideos();
|
||||
|
||||
return [
|
||||
'class' => $classes,
|
||||
'filters' => $filters,
|
||||
'list' => $recommend
|
||||
];
|
||||
}
|
||||
|
||||
public function categoryContent($tid, $pg = 1, $filter = [], $extend = [])
|
||||
{
|
||||
$page = max(1, $pg);
|
||||
$gameId = $this->getGameIdFromParams($tid, $filter, $extend);
|
||||
|
||||
$url = self::HOST . str_replace(
|
||||
['fyfilter', 'fypage'],
|
||||
[$gameId, $page],
|
||||
self::CATEGORY_URL_TEMPLATE
|
||||
);
|
||||
|
||||
$html = $this->fetch($url);
|
||||
$data = json_decode($html, true);
|
||||
|
||||
if (empty($data) || empty($data['data']['datas'])) {
|
||||
return $this->pageResult([], $page);
|
||||
}
|
||||
|
||||
$videos = [];
|
||||
foreach ($data['data']['datas'] as $item) {
|
||||
$profileRoom = $item['profileRoom'] ?? '';
|
||||
$roomId = $this->extractRoomId($profileRoom);
|
||||
|
||||
$videos[] = [
|
||||
'vod_id' => $roomId,
|
||||
'vod_name' => $item['introduction'] ?? '未知标题',
|
||||
'vod_pic' => $item['screenshot'] ?? '',
|
||||
'vod_remarks' => '👁' . ($item['totalCount'] ?? 0) . ' 🆙' . ($item['nick'] ?? '')
|
||||
];
|
||||
}
|
||||
|
||||
$total = $data['data']['total'] ?? 0;
|
||||
$pageSize = 8;
|
||||
|
||||
return $this->pageResult($videos, $page, $total, $pageSize);
|
||||
}
|
||||
|
||||
private function getGameIdFromParams($tid, $filter, $extend)
|
||||
{
|
||||
$defaultGameIds = [
|
||||
'8' => '2135',
|
||||
'1' => '1',
|
||||
'2' => '1732',
|
||||
'3' => '2336'
|
||||
];
|
||||
|
||||
if (!empty($extend) && isset($extend['cateId']) && !empty($extend['cateId'])) {
|
||||
return $extend['cateId'];
|
||||
}
|
||||
|
||||
if (is_string($filter) && !empty($filter)) {
|
||||
if (strpos($filter, '{') === 0 || strpos($filter, '[') === 0) {
|
||||
$decoded = json_decode($filter, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && isset($decoded['cateId'])) {
|
||||
return $decoded['cateId'];
|
||||
}
|
||||
} else {
|
||||
return $filter;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($filter) && !empty($filter)) {
|
||||
if (isset($filter['cateId']) && !empty($filter['cateId'])) {
|
||||
return $filter['cateId'];
|
||||
}
|
||||
}
|
||||
|
||||
return $defaultGameIds[$tid] ?? $tid;
|
||||
}
|
||||
|
||||
public function detailContent($ids)
|
||||
{
|
||||
if (empty($ids) || !is_array($ids)) {
|
||||
return ['list' => []];
|
||||
}
|
||||
|
||||
$roomId = $ids[0];
|
||||
if (empty($roomId)) {
|
||||
return ['list' => []];
|
||||
}
|
||||
|
||||
$roomInfo = $this->getRoomInfo($roomId);
|
||||
|
||||
$vodName = '虎牙直播间';
|
||||
$vodPic = '';
|
||||
$vodContent = '房间ID: ' . $roomId;
|
||||
|
||||
if ($roomInfo && is_array($roomInfo)) {
|
||||
$vodName = isset($roomInfo['roomName']) ? (string)$roomInfo['roomName'] : $vodName;
|
||||
$vodPic = isset($roomInfo['screenshot']) ? (string)$roomInfo['screenshot'] : $vodPic;
|
||||
|
||||
$introduction = isset($roomInfo['introduction']) ? (string)$roomInfo['introduction'] : '';
|
||||
$nick = isset($roomInfo['nick']) ? (string)$roomInfo['nick'] : '';
|
||||
$vodContent = trim($introduction . "\n主播: " . $nick);
|
||||
}
|
||||
|
||||
return [
|
||||
'list' => [
|
||||
[
|
||||
'vod_id' => (string)$roomId,
|
||||
'vod_name' => $vodName,
|
||||
'vod_pic' => $vodPic,
|
||||
'vod_content' => $vodContent,
|
||||
'vod_play_from' => '直播',
|
||||
'vod_play_url' => (string)$roomId
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function searchContent($key, $quick = false, $pg = 1)
|
||||
{
|
||||
$page = max(1, $pg);
|
||||
$start = ($page - 1) * 40;
|
||||
|
||||
$url = str_replace(
|
||||
['**', 'start=0'],
|
||||
[urlencode($key), "start={$start}"],
|
||||
self::SEARCH_URL
|
||||
);
|
||||
|
||||
$html = $this->fetch($url);
|
||||
$data = json_decode($html, true);
|
||||
|
||||
if (empty($data) || empty($data[3]['docs'])) {
|
||||
return $this->pageResult([]);
|
||||
}
|
||||
|
||||
$videos = [];
|
||||
foreach ($data[3]['docs'] as $item) {
|
||||
$roomId = isset($item['room_id']) ? (string)$item['room_id'] : '';
|
||||
|
||||
$videos[] = [
|
||||
'vod_id' => $roomId,
|
||||
'vod_name' => isset($item['game_roomName']) ? (string)$item['game_roomName'] : '未知标题',
|
||||
'vod_pic' => isset($item['game_screenshot']) ? (string)$item['game_screenshot'] : '',
|
||||
'vod_remarks' => '主播: ' . (isset($item['game_nick']) ? (string)$item['game_nick'] : '')
|
||||
];
|
||||
}
|
||||
|
||||
return $this->pageResult($videos, $page);
|
||||
}
|
||||
|
||||
public function playerContent($flag, $id, $vipFlags = [])
|
||||
{
|
||||
$rid = $this->extractRoomId($id);
|
||||
|
||||
if (empty($rid)) {
|
||||
return ['parse' => 0, 'url' => ''];
|
||||
}
|
||||
|
||||
$apiUrl = self::ROOM_INFO_URL . $rid;
|
||||
$response = $this->fetch($apiUrl);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (empty($data['data']['stream']['flv']['multiLine'][0]['url'])) {
|
||||
return ['parse' => 0, 'url' => ''];
|
||||
}
|
||||
|
||||
$purl = $data['data']['stream']['flv']['multiLine'][0]['url'];
|
||||
$realUrl = $this->getRealUrl($purl);
|
||||
|
||||
return [
|
||||
'parse' => 0,
|
||||
'jx' => 0,
|
||||
'url' => $realUrl,
|
||||
'header' => (object)[
|
||||
'user-agent' => 'Mozilla/5.0'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function getRecommendVideos()
|
||||
{
|
||||
$url = self::HOST . self::HOME_URL;
|
||||
$html = $this->fetch($url);
|
||||
$data = json_decode($html, true);
|
||||
|
||||
$videos = [];
|
||||
if (!empty($data['data']['datas'])) {
|
||||
foreach ($data['data']['datas'] as $item) {
|
||||
$profileRoom = $item['profileRoom'] ?? '';
|
||||
$roomId = $this->extractRoomId($profileRoom);
|
||||
|
||||
$videos[] = [
|
||||
'vod_id' => $roomId,
|
||||
'vod_name' => $item['introduction'] ?? '未知标题',
|
||||
'vod_pic' => $item['screenshot'] ?? '',
|
||||
'vod_remarks' => '👁' . ($item['totalCount'] ?? 0) . ' 🆙' . ($item['nick'] ?? '')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $videos;
|
||||
}
|
||||
|
||||
private function getRoomInfo($roomId)
|
||||
{
|
||||
if (empty($roomId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$apiUrl = self::ROOM_INFO_URL . $roomId;
|
||||
$response = $this->fetch($apiUrl);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (!empty($data['data'])) {
|
||||
return $data['data'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function extractRoomId($url)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (is_numeric($url)) {
|
||||
return (string)$url;
|
||||
}
|
||||
|
||||
preg_match('/(\d+)/', $url, $matches);
|
||||
|
||||
if (!empty($matches[1])) {
|
||||
return (string)$matches[1];
|
||||
}
|
||||
|
||||
return (string)$url;
|
||||
}
|
||||
|
||||
private function getFilters()
|
||||
{
|
||||
// 简化分类数据,只保留主要分类
|
||||
return [
|
||||
'8' => [
|
||||
[
|
||||
'key' => 'cateId',
|
||||
'name' => '分类',
|
||||
'value' => [
|
||||
['n' => '一起看', 'v' => '2135'],
|
||||
['n' => '星秀', 'v' => '1663'],
|
||||
['n' => '户外', 'v' => '2165'],
|
||||
['n' => '二次元', 'v' => '2633'],
|
||||
['n' => '颜值', 'v' => '2168']
|
||||
]
|
||||
]
|
||||
],
|
||||
'1' => [
|
||||
[
|
||||
'key' => 'cateId',
|
||||
'name' => '分类',
|
||||
'value' => [
|
||||
['n' => '英雄联盟', 'v' => '1'],
|
||||
['n' => 'CS2', 'v' => '862'],
|
||||
['n' => '穿越火线', 'v' => '4'],
|
||||
['n' => '无畏契约', 'v' => '5937'],
|
||||
['n' => 'DOTA2', 'v' => '7']
|
||||
]
|
||||
]
|
||||
],
|
||||
'2' => [
|
||||
[
|
||||
'key' => 'cateId',
|
||||
'name' => '分类',
|
||||
'value' => [
|
||||
['n' => '天天吃鸡', 'v' => '2793'],
|
||||
['n' => '永劫无间', 'v' => '6219'],
|
||||
['n' => '我的世界', 'v' => '1732'],
|
||||
['n' => '主机游戏', 'v' => '100032'],
|
||||
['n' => 'Apex英雄', 'v' => '5011']
|
||||
]
|
||||
]
|
||||
],
|
||||
'3' => [
|
||||
[
|
||||
'key' => 'cateId',
|
||||
'name' => '分类',
|
||||
'value' => [
|
||||
['n' => '王者荣耀', 'v' => '2336'],
|
||||
['n' => '和平精英', 'v' => '3203'],
|
||||
['n' => '英雄联盟手游', 'v' => '6203'],
|
||||
['n' => '原神', 'v' => '5489'],
|
||||
['n' => '金铲铲之战', 'v' => '7185']
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
private function getRealUrl($live_url)
|
||||
{
|
||||
if (empty($live_url)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$parts = explode('?', $live_url, 2);
|
||||
if (count($parts) < 2) {
|
||||
return $live_url;
|
||||
}
|
||||
|
||||
list($i, $b) = $parts;
|
||||
$r = basename($i);
|
||||
$s = preg_replace('/\.(flv|m3u8)$/', '', $r);
|
||||
|
||||
$params = explode('&', $b);
|
||||
$params = array_filter($params);
|
||||
|
||||
$n = [];
|
||||
$c_tmp2 = [];
|
||||
|
||||
foreach ($params as $index => $param) {
|
||||
if ($index < 3) {
|
||||
$pair = explode('=', $param, 2);
|
||||
if (count($pair) == 2) {
|
||||
$n[$pair[0]] = $pair[1];
|
||||
}
|
||||
} else {
|
||||
$c_tmp2[] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
$tmp2 = implode('&', $c_tmp2);
|
||||
if (!empty($tmp2)) {
|
||||
$pair = explode('=', $tmp2, 2);
|
||||
if (count($pair) == 2) {
|
||||
$n[$pair[0]] = $pair[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($n['fm'])) {
|
||||
return $live_url;
|
||||
}
|
||||
|
||||
$fm = urldecode($n['fm']);
|
||||
$fmParts = explode('&', $fm);
|
||||
$fm = $fmParts[0] ?? '';
|
||||
|
||||
$u = base64_decode($fm);
|
||||
if ($u === false) {
|
||||
return $live_url;
|
||||
}
|
||||
|
||||
$uParts = explode('_', $u);
|
||||
$p = $uParts[0] ?? '';
|
||||
|
||||
$f = time() . '0000';
|
||||
$ll = $n['wsTime'] ?? '';
|
||||
$t = '0';
|
||||
|
||||
$h = "{$p}_{$t}_{$s}_{$f}_{$ll}";
|
||||
$m = md5($h);
|
||||
|
||||
$result = $i . '?wsSecret=' . $m . '&wsTime=' . $ll . '&u=' . $t . '&seqid=' . $f;
|
||||
|
||||
if (!empty($c_tmp2)) {
|
||||
$result .= '&' . end($c_tmp2);
|
||||
}
|
||||
|
||||
return str_replace(['hls', 'm3u8'], ['flv', 'flv'], $result);
|
||||
}
|
||||
}
|
||||
|
||||
if (basename(__FILE__) == basename($_SERVER['SCRIPT_FILENAME'])) {
|
||||
(new HuyaSpider())->run();
|
||||
}
|
||||
Reference in New Issue
Block a user