xxxx
This commit is contained in:
357
scripts/52tv_pro.php
Normal file
357
scripts/52tv_pro.php
Normal file
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
error_reporting(0);
|
||||
|
||||
// ================= 配置区域 =================
|
||||
define('DEBUG_MODE', false);
|
||||
define('LOG_FILE', 'debug_log.txt');
|
||||
|
||||
define('SITE_URL', 'https://52tvdy.com');
|
||||
// 精简 UA,稍微缩短一点请求包
|
||||
define('UA', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/142.0.0.0 Safari/537.36');
|
||||
// ===========================================
|
||||
|
||||
function write_log($msg) {
|
||||
if (!DEBUG_MODE) return;
|
||||
if (file_exists(LOG_FILE) && filesize(LOG_FILE) > 2 * 1024 * 1024) {
|
||||
file_put_contents(LOG_FILE, "--- Log Cleared (Size Limit) ---\n");
|
||||
}
|
||||
$time = date('Y-m-d H:i:s');
|
||||
$content = is_array($msg) || is_object($msg) ? json_encode($msg, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) : $msg;
|
||||
file_put_contents(LOG_FILE, "[$time] $content\n", FILE_APPEND);
|
||||
}
|
||||
|
||||
$ac = isset($_GET['ac']) ? $_GET['ac'] : '';
|
||||
$t = isset($_GET['t']) ? $_GET['t'] : '';
|
||||
$pg = isset($_GET['pg']) ? $_GET['pg'] : '1';
|
||||
$wd = isset($_GET['wd']) ? $_GET['wd'] : '';
|
||||
$ids = isset($_GET['ids']) ? $_GET['ids'] : '';
|
||||
$play_url = isset($_GET['play_url']) ? $_GET['play_url'] : '';
|
||||
$play = isset($_GET['play']) ? $_GET['play'] : '';
|
||||
|
||||
$current_script = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[PHP_SELF]";
|
||||
|
||||
if ($ac == 'play' || !empty($play)) {
|
||||
if (empty($play_url) && !empty($play)) {
|
||||
$play_url = $play;
|
||||
}
|
||||
render_play($play_url);
|
||||
}
|
||||
elseif ($ac == 'detail' && !empty($ids)) {
|
||||
render_detail($ids, $current_script);
|
||||
}
|
||||
elseif (!empty($wd)) {
|
||||
render_search($wd, $pg);
|
||||
}
|
||||
elseif (!empty($t)) {
|
||||
render_category($t, $pg);
|
||||
}
|
||||
else {
|
||||
render_home();
|
||||
}
|
||||
|
||||
// ================= 业务逻辑 =================
|
||||
|
||||
function render_home() {
|
||||
$html = get_web_content(SITE_URL);
|
||||
$list = parse_video_list_robust($html);
|
||||
$data = [
|
||||
"class" => [
|
||||
["type_id" => "tv", "type_name" => "剧集"],
|
||||
["type_id" => "movies", "type_name" => "电影"],
|
||||
["type_id" => "varietyshow", "type_name" => "综艺"],
|
||||
["type_id" => "anime", "type_name" => "动漫"],
|
||||
["type_id" => "shortdrama", "type_name" => "短剧"]
|
||||
],
|
||||
"list" => $list,
|
||||
"filters" => new stdClass()
|
||||
];
|
||||
echo_json($data);
|
||||
}
|
||||
|
||||
function render_category($tid, $pg) {
|
||||
$url = SITE_URL . '/' . $tid;
|
||||
if ($pg > 1) $url .= '/page/' . $pg;
|
||||
$html = get_web_content($url);
|
||||
$list = parse_video_list_robust($html);
|
||||
if (empty($list)) $list = [];
|
||||
echo_json([
|
||||
"page" => intval($pg),
|
||||
"pagecount" => 999,
|
||||
"limit" => 20,
|
||||
"total" => 999,
|
||||
"list" => $list
|
||||
]);
|
||||
}
|
||||
|
||||
function render_search($wd, $pg) {
|
||||
$url = SITE_URL . '/search/video/w/' . urlencode($wd);
|
||||
if ($pg > 1) $url .= '/page/' . $pg;
|
||||
$html = get_web_content($url);
|
||||
$list = parse_search_results($html, $wd);
|
||||
echo_json([
|
||||
"page" => intval($pg),
|
||||
"list" => $list
|
||||
]);
|
||||
}
|
||||
|
||||
function render_detail($ids, $script_url) {
|
||||
$url = SITE_URL . '/detail/' . $ids;
|
||||
$html = get_web_content($url);
|
||||
|
||||
preg_match('/<h2 class="slide-info-title">(.*?)<\/h2>/', $html, $m_title);
|
||||
$title = $m_title[1] ?? '未知';
|
||||
|
||||
$pic = '';
|
||||
if (preg_match('/class="detail-pic[^"]*".*?z-image-loader-url="(.*?)"/s', $html, $m_z)) {
|
||||
$pic = str_replace('`', '', $m_z[1]);
|
||||
}
|
||||
if (empty($pic) && preg_match('/class="detail-pic[^"]*".*?src="(.*?)"/s', $html, $m_p)) {
|
||||
$pic = $m_p[1];
|
||||
}
|
||||
if (!empty($pic) && strpos($pic, 'http') === false) {
|
||||
if (strpos($pic, '//') === 0) $pic = 'https:' . $pic;
|
||||
else $pic = SITE_URL . $pic;
|
||||
}
|
||||
|
||||
preg_match('/class="check desc-text selected">\s*(.*?)\s*<\/div>/s', $html, $m_desc);
|
||||
$desc = strip_tags($m_desc[1] ?? '');
|
||||
|
||||
$vod_play_from = [];
|
||||
$vod_play_url = [];
|
||||
|
||||
preg_match_all('/<ul class="anthology-list-play size">([\s\S]*?)<\/ul>/', $html, $matches_ul);
|
||||
|
||||
$i = 1;
|
||||
foreach ($matches_ul[1] as $ul_content) {
|
||||
$urls = [];
|
||||
preg_match_all('/<a.*?href="(.*?)".*?>(.*?)<\/a>/', $ul_content, $matches_li);
|
||||
|
||||
foreach ($matches_li[1] as $k => $href) {
|
||||
$name = strip_tags($matches_li[2][$k]);
|
||||
$target = $script_url . '?ac=play&play=' . urlencode($href);
|
||||
$urls[] = $name . '$' . $target;
|
||||
}
|
||||
|
||||
if (!empty($urls)) {
|
||||
$line_name = "52TV直连" . (count($matches_ul[1]) > 1 ? $i : '');
|
||||
$vod_play_from[] = $line_name;
|
||||
$vod_play_url[] = implode('#', $urls);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
echo_json([
|
||||
"list" => [[
|
||||
"vod_id" => $ids,
|
||||
"vod_name" => $title,
|
||||
"vod_pic" => $pic,
|
||||
"vod_content" => $desc,
|
||||
"vod_play_from" => implode('$$$', $vod_play_from),
|
||||
"vod_play_url" => implode('$$$', $vod_play_url)
|
||||
]]
|
||||
]);
|
||||
}
|
||||
|
||||
function render_play($path) {
|
||||
$path = urldecode($path);
|
||||
|
||||
// 防死锁
|
||||
if (strpos($path, 'play=') !== false) {
|
||||
$parts = parse_url($path);
|
||||
if (isset($parts['query'])) {
|
||||
parse_str($parts['query'], $query);
|
||||
if (isset($query['play'])) {
|
||||
$inner_play = urldecode($query['play']);
|
||||
if (!empty($inner_play) && strpos($inner_play, 'http') !== 0) {
|
||||
$path = $inner_play;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($path, 'http') === 0) {
|
||||
$url = $path;
|
||||
} else {
|
||||
if (strpos($path, 'play') === false && preg_match('/^\d+-\d+-\d+/', $path)) {
|
||||
$path = '/play/' . $path;
|
||||
}
|
||||
if (substr($path, 0, 1) !== '/') {
|
||||
$path = '/' . $path;
|
||||
}
|
||||
$url = SITE_URL . $path;
|
||||
}
|
||||
|
||||
$html = get_web_content($url);
|
||||
$real_url = '';
|
||||
|
||||
if (preg_match('/data-m3u8="(.*?)"/', $html, $matches)) {
|
||||
$real_url = $matches[1];
|
||||
}
|
||||
|
||||
if (empty($real_url)) {
|
||||
if (preg_match_all('/"url":"(.*?)"/', $html, $matches)) {
|
||||
foreach ($matches[1] as $val) {
|
||||
$val = str_replace('\\/', '/', $val);
|
||||
if (strpos($val, 'http') === 0 || strpos($val, '/') === 0) {
|
||||
$real_url = $val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($real_url)) {
|
||||
if (preg_match('/(http[s]?:\/\/[\w\.\/\-]+\.m3u8[\w\-\.\?=&]+)/', $html, $matches)) {
|
||||
$real_url = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($real_url)) {
|
||||
$real_url = htmlspecialchars_decode($real_url);
|
||||
$data = [
|
||||
"parse" => 0,
|
||||
"playUrl" => "",
|
||||
"url" => $real_url,
|
||||
"header" => [
|
||||
"User-Agent" => UA,
|
||||
"Origin" => SITE_URL,
|
||||
"Referer" => SITE_URL . "/"
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$data = [
|
||||
"parse" => 1,
|
||||
"playUrl" => "",
|
||||
"url" => $url,
|
||||
"header" => [
|
||||
"User-Agent" => UA
|
||||
]
|
||||
];
|
||||
}
|
||||
echo_json($data);
|
||||
}
|
||||
|
||||
function echo_json($data) {
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
function parse_search_results($html, $wd = '') {
|
||||
$list = [];
|
||||
$blocks = explode('class="search-box', $html);
|
||||
array_shift($blocks);
|
||||
|
||||
foreach ($blocks as $block) {
|
||||
preg_match('/href="\/detail\/(\d+)/', $block, $m_id);
|
||||
$vid = $m_id[1] ?? '';
|
||||
if (empty($vid)) continue;
|
||||
|
||||
preg_match('/class="thumb-txt[^"]*">([^<]+)</', $block, $m_title);
|
||||
$name = trim($m_title[1] ?? '');
|
||||
if (empty($name)) {
|
||||
preg_match('/title="([^"]+)"/', $block, $m_title_alt);
|
||||
$name = $m_title_alt[1] ?? '未知';
|
||||
}
|
||||
|
||||
// 关键词过滤
|
||||
if (!empty($wd)) {
|
||||
$keyword = urldecode($wd);
|
||||
if (mb_stripos($name, $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$pic = '';
|
||||
if (preg_match('/src="(http[^"]+)"/', $block, $m_pic)) {
|
||||
$pic = $m_pic[1];
|
||||
}
|
||||
$pic = htmlspecialchars_decode($pic);
|
||||
|
||||
preg_match('/class="public-list-prb[^"]*">([^<]+)</', $block, $m_rem);
|
||||
$remark = trim($m_rem[1] ?? '');
|
||||
|
||||
$list[] = [
|
||||
"vod_id" => $vid,
|
||||
"vod_name" => $name,
|
||||
"vod_pic" => $pic,
|
||||
"vod_remarks" => $remark
|
||||
];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
function parse_video_list_robust($html) {
|
||||
$list = [];
|
||||
$blocks = explode('class="public-list-box', $html);
|
||||
array_shift($blocks);
|
||||
foreach ($blocks as $block) {
|
||||
preg_match('/href="\/detail\/(\d+)"/', $block, $m_id);
|
||||
if (empty($m_id[1])) continue;
|
||||
$vid = $m_id[1];
|
||||
preg_match('/title="([^"]+)"/', $block, $m_title);
|
||||
$name = $m_title[1] ?? '未知';
|
||||
$pic = '';
|
||||
if (preg_match('/z-image-loader-url="([^"]+)"/', $block, $m_zimg)) {
|
||||
$pic = str_replace('`', '', $m_zimg[1]);
|
||||
}
|
||||
if (empty($pic) && preg_match('/src="([^"]+)"/', $block, $m_src)) {
|
||||
$pic = $m_src[1];
|
||||
}
|
||||
if (!empty($pic) && strpos($pic, 'http') === false) {
|
||||
if (strpos($pic, '//') === 0) $pic = 'https:' . $pic;
|
||||
else $pic = SITE_URL . $pic;
|
||||
}
|
||||
$pic = htmlspecialchars_decode($pic);
|
||||
|
||||
preg_match('/class="[^"]*public-list-prb[^"]*">([\s\S]*?)<\/span>/', $block, $m_rem);
|
||||
$remark = trim(strip_tags($m_rem[1] ?? ''));
|
||||
$list[] = [
|
||||
"vod_id" => $vid,
|
||||
"vod_name" => $name,
|
||||
"vod_pic" => $pic,
|
||||
"vod_remarks" => $remark
|
||||
];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
function get_web_content($url) {
|
||||
if (DEBUG_MODE) write_log("CURL Fetching: $url");
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
|
||||
// 1. 开启 GZIP 压缩 (性能提升关键)
|
||||
// 允许服务器返回 gzip 压缩的数据,curl 会自动解压
|
||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||
|
||||
// 2. 强制 IPv4
|
||||
// 避免 IPv6 解析超时或失败,通常 IPv4 响应更快
|
||||
curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
||||
|
||||
// 3. 优化超时时间
|
||||
// 连接超时 3秒,总超时 10秒
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, UA);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
$data = curl_exec($ch);
|
||||
|
||||
if (DEBUG_MODE) {
|
||||
if ($data === false) {
|
||||
write_log("CURL Error: " . curl_error($ch));
|
||||
} else {
|
||||
write_log("CURL Success: Got " . strlen($data) . " bytes");
|
||||
}
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
return $data;
|
||||
}
|
||||
?>
|
||||
837
scripts/B站第6代.php
Normal file
837
scripts/B站第6代.php
Normal file
@@ -0,0 +1,837 @@
|
||||
<?php
|
||||
// B站爬虫模板 - 单一分类无限细分 + 修复播放URL
|
||||
//
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 使用Apple CMS标准参数
|
||||
$ac = $_GET['ac'] ?? 'detail';
|
||||
$t = $_GET['t'] ?? '';
|
||||
$pg = $_GET['pg'] ?? '1';
|
||||
$f = $_GET['f'] ?? '';
|
||||
$ids = $_GET['ids'] ?? '';
|
||||
$wd = $_GET['wd'] ?? '';
|
||||
$flag = $_GET['flag'] ?? '';
|
||||
$id = $_GET['id'] ?? '';
|
||||
|
||||
// B站爬虫类
|
||||
class BiliBiliSpider {
|
||||
private $categories = [];
|
||||
private $baseUrl = 'https://api.bilibili.com';
|
||||
|
||||
public function __construct() {
|
||||
$this->initializeCategories();
|
||||
}
|
||||
|
||||
// 初始化分类 - 只有B站一个一级分类
|
||||
private function initializeCategories() {
|
||||
// 只有一个一级分类:B站
|
||||
$this->categories = [
|
||||
'1' => ['name' => 'B站', 'tid' => 0, 'level' => 1, 'has_children' => true],
|
||||
];
|
||||
|
||||
// 初始化B站所有主要分区作为二级分类
|
||||
$this->initializeBilibiliCategories();
|
||||
}
|
||||
|
||||
// 初始化B站所有分类
|
||||
private function initializeBilibiliCategories() {
|
||||
// B站主要分区
|
||||
$mainCategories = [
|
||||
['id' => '1_1', 'name' => '动画', 'tid' => 1],
|
||||
['id' => '1_2', 'name' => '番剧', 'tid' => 13],
|
||||
['id' => '1_3', 'name' => '国创', 'tid' => 167],
|
||||
['id' => '1_4', 'name' => '音乐', 'tid' => 3],
|
||||
['id' => '1_5', 'name' => '舞蹈', 'tid' => 129],
|
||||
['id' => '1_6', 'name' => '游戏', 'tid' => 4],
|
||||
['id' => '1_7', 'name' => '知识', 'tid' => 36],
|
||||
['id' => '1_8', 'name' => '科技', 'tid' => 188],
|
||||
['id' => '1_9', 'name' => '运动', 'tid' => 234],
|
||||
['id' => '1_10', 'name' => '汽车', 'tid' => 223],
|
||||
['id' => '1_11', 'name' => '生活', 'tid' => 160],
|
||||
['id' => '1_12', 'name' => '美食', 'tid' => 211],
|
||||
['id' => '1_13', 'name' => '动物', 'tid' => 217],
|
||||
['id' => '1_14', 'name' => '鬼畜', 'tid' => 119],
|
||||
['id' => '1_15', 'name' => '时尚', 'tid' => 155],
|
||||
['id' => '1_16', 'name' => '娱乐', 'tid' => 5],
|
||||
['id' => '1_17', 'name' => '影视', 'tid' => 181],
|
||||
['id' => '1_18', 'name' => '纪录片', 'tid' => 177],
|
||||
['id' => '1_19', 'name' => '电影', 'tid' => 23],
|
||||
['id' => '1_20', 'name' => '电视剧', 'tid' => 11],
|
||||
['id' => '1_21', 'name' => '热门推荐', 'tid' => 0],
|
||||
];
|
||||
|
||||
foreach ($mainCategories as $cat) {
|
||||
$this->categories[$cat['id']] = [
|
||||
'name' => $cat['name'],
|
||||
'tid' => $cat['tid'],
|
||||
'level' => 2,
|
||||
'parent' => '1',
|
||||
'has_children' => $this->hasSubCategories($cat['tid'])
|
||||
];
|
||||
}
|
||||
|
||||
// 初始化所有子分类
|
||||
$this->initializeAllSubCategories();
|
||||
}
|
||||
|
||||
// 检查是否有子分类
|
||||
private function hasSubCategories($tid) {
|
||||
$hasSubs = [1, 13, 167, 3, 129, 4, 36, 188, 234, 160, 5, 181];
|
||||
return in_array($tid, $hasSubs);
|
||||
}
|
||||
|
||||
// 初始化所有子分类
|
||||
private function initializeAllSubCategories() {
|
||||
// ... 前面已有的分类代码保持不变 ...
|
||||
|
||||
// 动画分区细分 - 修复tid
|
||||
$this->addSubCategory('1_1', '1_1_1', 'MAD·AMV', 24);
|
||||
$this->addSubCategory('1_1', '1_1_2', 'MMD·3D', 25);
|
||||
$this->addSubCategory('1_1', '1_1_3', '短片·手书', 47);
|
||||
$this->addSubCategory('1_1', '1_1_4', '综合', 27);
|
||||
|
||||
// 番剧分区细分
|
||||
$this->addSubCategory('1_2', '1_2_1', '连载动画', 33);
|
||||
$this->addSubCategory('1_2', '1_2_2', '完结动画', 32);
|
||||
$this->addSubCategory('1_2', '1_2_3', '资讯', 51);
|
||||
$this->addSubCategory('1_2', '1_2_4', '官方延伸', 152);
|
||||
|
||||
// 国创分区细分
|
||||
$this->addSubCategory('1_3', '1_3_1', '国产动画', 153);
|
||||
$this->addSubCategory('1_3', '1_3_2', '国产原创', 168);
|
||||
$this->addSubCategory('1_3', '1_3_3', '布袋戏', 169);
|
||||
$this->addSubCategory('1_3', '1_3_4', '动态漫·广播剧', 195);
|
||||
|
||||
// 音乐分区细分
|
||||
$this->addSubCategory('1_4', '1_4_1', '原创音乐', 28);
|
||||
$this->addSubCategory('1_4', '1_4_2', '翻唱', 31);
|
||||
$this->addSubCategory('1_4', '1_4_3', 'VOCALOID·UTAU', 30);
|
||||
$this->addSubCategory('1_4', '1_4_4', '演奏', 59);
|
||||
$this->addSubCategory('1_4', '1_4_5', 'MV', 193);
|
||||
$this->addSubCategory('1_4', '1_4_6', '音乐现场', 29);
|
||||
$this->addSubCategory('1_4', '1_4_7', '音乐综合', 130);
|
||||
|
||||
// 舞蹈分区细分
|
||||
$this->addSubCategory('1_5', '1_5_1', '宅舞', 20);
|
||||
$this->addSubCategory('1_5', '1_5_2', '街舞', 198);
|
||||
$this->addSubCategory('1_5', '1_5_3', '明星舞蹈', 199);
|
||||
$this->addSubCategory('1_5', '1_5_4', '中国舞', 200);
|
||||
$this->addSubCategory('1_5', '1_5_5', '舞蹈综合', 154);
|
||||
|
||||
// 游戏分区细分
|
||||
$this->addSubCategory('1_6', '1_6_1', '单机游戏', 17);
|
||||
$this->addSubCategory('1_6', '1_6_2', '电子竞技', 171);
|
||||
$this->addSubCategory('1_6', '1_6_3', '手机游戏', 65);
|
||||
$this->addSubCategory('1_6', '1_6_4', '网络游戏', 172);
|
||||
$this->addSubCategory('1_6', '1_6_5', '桌游棋牌', 173);
|
||||
$this->addSubCategory('1_6', '1_6_6', 'GMV', 121);
|
||||
$this->addSubCategory('1_6', '1_6_7', '音游', 136);
|
||||
$this->addSubCategory('1_6', '1_6_8', 'Mugen', 19);
|
||||
|
||||
// 知识分区细分
|
||||
$this->addSubCategory('1_7', '1_7_1', '科学科普', 201);
|
||||
$this->addSubCategory('1_7', '1_7_2', '社科人文', 124);
|
||||
$this->addSubCategory('1_7', '1_7_3', '财经', 207);
|
||||
$this->addSubCategory('1_7', '1_7_4', '校园学习', 208);
|
||||
$this->addSubCategory('1_7', '1_7_5', '职业职场', 209);
|
||||
$this->addSubCategory('1_7', '1_7_6', '野生技术协会', 122);
|
||||
|
||||
// 科技分区细分
|
||||
$this->addSubCategory('1_8', '1_8_1', '数码', 95);
|
||||
$this->addSubCategory('1_8', '1_8_2', '软件应用', 230);
|
||||
$this->addSubCategory('1_8', '1_8_3', '计算机技术', 231);
|
||||
$this->addSubCategory('1_8', '1_8_4', '工业工程', 232);
|
||||
$this->addSubCategory('1_8', '1_8_5', '机械', 233);
|
||||
|
||||
// 运动分区细分
|
||||
$this->addSubCategory('1_9', '1_9_1', '篮球', 235);
|
||||
$this->addSubCategory('1_9', '1_9_2', '足球', 249);
|
||||
$this->addSubCategory('1_9', '1_9_3', '健身', 164);
|
||||
$this->addSubCategory('1_9', '1_9_4', '竞技体育', 236);
|
||||
$this->addSubCategory('1_9', '1_9_5', '运动文化', 237);
|
||||
|
||||
// 汽车分区细分
|
||||
$this->addSubCategory('1_10', '1_10_1', '汽车生活', 176);
|
||||
$this->addSubCategory('1_10', '1_10_2', '汽车文化', 224);
|
||||
$this->addSubCategory('1_10', '1_10_3', '汽车极客', 225);
|
||||
$this->addSubCategory('1_10', '1_10_4', '智能出行', 226);
|
||||
$this->addSubCategory('1_10', '1_10_5', '购车攻略', 227);
|
||||
|
||||
// 生活分区细分
|
||||
$this->addSubCategory('1_11', '1_11_1', '搞笑', 138);
|
||||
$this->addSubCategory('1_11', '1_11_2', '日常', 21);
|
||||
$this->addSubCategory('1_11', '1_11_3', '手工', 161);
|
||||
$this->addSubCategory('1_11', '1_11_4', '绘画', 162);
|
||||
$this->addSubCategory('1_11', '1_11_5', '运动', 163);
|
||||
$this->addSubCategory('1_11', '1_11_6', '其他', 174);
|
||||
|
||||
// 美食分区细分
|
||||
$this->addSubCategory('1_12', '1_12_1', '美食制作', 76);
|
||||
$this->addSubCategory('1_12', '1_12_2', '美食侦探', 212);
|
||||
$this->addSubCategory('1_12', '1_12_3', '美食测评', 213);
|
||||
$this->addSubCategory('1_12', '1_12_4', '田园美食', 214);
|
||||
$this->addSubCategory('1_12', '1_12_5', '美食记录', 215);
|
||||
|
||||
// 动物分区细分
|
||||
$this->addSubCategory('1_13', '1_13_1', '喵星人', 218);
|
||||
$this->addSubCategory('1_13', '1_13_2', '汪星人', 219);
|
||||
$this->addSubCategory('1_13', '1_13_3', '大熊猫', 220);
|
||||
$this->addSubCategory('1_13', '1_13_4', '野生动物', 221);
|
||||
$this->addSubCategory('1_13', '1_13_5', '爬宠', 222);
|
||||
$this->addSubCategory('1_13', '1_13_6', '动物综合', 75);
|
||||
|
||||
// 鬼畜分区细分
|
||||
$this->addSubCategory('1_14', '1_14_1', '鬼畜调教', 22);
|
||||
$this->addSubCategory('1_14', '1_14_2', '音MAD', 26);
|
||||
$this->addSubCategory('1_14', '1_14_3', '鬼畜剧场', 126);
|
||||
$this->addSubCategory('1_14', '1_14_4', '教程演示', 127);
|
||||
|
||||
// 时尚分区细分
|
||||
$this->addSubCategory('1_15', '1_15_1', '美妆', 157);
|
||||
$this->addSubCategory('1_15', '1_15_2', '服饰', 158);
|
||||
$this->addSubCategory('1_15', '1_15_3', '时尚潮流', 159);
|
||||
$this->addSubCategory('1_15', '1_15_4', '仿妆', 192);
|
||||
$this->addSubCategory('1_15', '1_15_5', '穿搭', 189);
|
||||
|
||||
// 娱乐分区细分
|
||||
$this->addSubCategory('1_16', '1_16_1', '综艺', 71);
|
||||
$this->addSubCategory('1_16', '1_16_2', '明星', 137);
|
||||
$this->addSubCategory('1_16', '1_16_3', 'Korea相关', 131);
|
||||
|
||||
// 影视分区细分
|
||||
$this->addSubCategory('1_17', '1_17_1', '影视杂谈', 182);
|
||||
$this->addSubCategory('1_17', '1_17_2', '影视剪辑', 183);
|
||||
$this->addSubCategory('1_17', '1_17_3', '短片', 85);
|
||||
$this->addSubCategory('1_17', '1_17_4', '预告·资讯', 184);
|
||||
|
||||
// 纪录片分区细分
|
||||
$this->addSubCategory('1_18', '1_18_1', '人文历史', 37);
|
||||
$this->addSubCategory('1_18', '1_18_2', '科学探索', 178);
|
||||
$this->addSubCategory('1_18', '1_18_3', '社会纪实', 179);
|
||||
$this->addSubCategory('1_18', '1_18_4', '自然生态', 180);
|
||||
$this->addSubCategory('1_18', '1_18_5', '旅行纪录片', 196);
|
||||
|
||||
// 电影分区细分
|
||||
$this->addSubCategory('1_19', '1_19_1', '华语电影', 147);
|
||||
$this->addSubCategory('1_19', '1_19_2', '欧美电影', 145);
|
||||
$this->addSubCategory('1_19', '1_19_3', '日本电影', 146);
|
||||
$this->addSubCategory('1_19', '1_19_4', '韩国电影', 83);
|
||||
$this->addSubCategory('1_19', '1_19_5', '其他地区', 82);
|
||||
|
||||
// 电视剧分区细分
|
||||
$this->addSubCategory('1_20', '1_20_1', '国产剧', 185);
|
||||
$this->addSubCategory('1_20', '1_20_2', '海外剧', 187);
|
||||
// 可以继续添加更多细分分类...
|
||||
}
|
||||
|
||||
// 添加子分类
|
||||
private function addSubCategory($parentId, $id, $name, $tid) {
|
||||
$this->categories[$id] = [
|
||||
'name' => $name,
|
||||
'tid' => $tid,
|
||||
'level' => $this->categories[$parentId]['level'] + 1,
|
||||
'parent' => $parentId,
|
||||
'has_children' => false // 默认没有更深层的子分类
|
||||
];
|
||||
}
|
||||
|
||||
// 获取分类信息
|
||||
public function getCategory($categoryId) {
|
||||
return $this->categories[$categoryId] ?? null;
|
||||
}
|
||||
|
||||
// 获取子分类
|
||||
public function getChildren($parentId) {
|
||||
$children = [];
|
||||
foreach ($this->categories as $id => $category) {
|
||||
if (isset($category['parent']) && $category['parent'] === $parentId) {
|
||||
$children[] = [
|
||||
'id' => $id,
|
||||
'name' => $category['name'],
|
||||
'level' => $category['level'],
|
||||
'has_children' => $category['has_children'],
|
||||
'tid' => $category['tid'] ?? 0
|
||||
];
|
||||
}
|
||||
}
|
||||
return $children;
|
||||
}
|
||||
|
||||
// 获取一级分类
|
||||
public function getLevel1Categories() {
|
||||
$result = [];
|
||||
foreach ($this->categories as $id => $category) {
|
||||
if ($category['level'] === 1) {
|
||||
$result[] = [
|
||||
'type_id' => $id,
|
||||
'type_name' => $category['name']
|
||||
];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 从B站API获取视频列表
|
||||
// 从B站API获取视频列表
|
||||
public function getVideoList($tid, $page = 1, $keyword = '') {
|
||||
$videos = [];
|
||||
|
||||
if (!empty($keyword)) {
|
||||
return $this->searchVideos($keyword, $page);
|
||||
}
|
||||
|
||||
// 热门推荐特殊处理
|
||||
if ($tid == 0) {
|
||||
return $this->getPopularVideos($page);
|
||||
}
|
||||
|
||||
// 处理自定义分类
|
||||
if (is_string($tid) && !is_numeric($tid)) {
|
||||
return $this->getCustomCategoryVideos($tid, $page);
|
||||
}
|
||||
|
||||
try {
|
||||
$url = "{$this->baseUrl}/x/web-interface/newlist?" . http_build_query([
|
||||
'rid' => $tid,
|
||||
'type' => 0,
|
||||
'pn' => $page,
|
||||
'ps' => 20
|
||||
]);
|
||||
|
||||
$response = $this->curlRequest($url);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (isset($data['data']['archives'])) {
|
||||
foreach ($data['data']['archives'] as $item) {
|
||||
$videos[] = $this->formatVideoData($item);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$videos = $this->getSampleVideos($tid);
|
||||
}
|
||||
|
||||
return $videos;
|
||||
}
|
||||
|
||||
// 新增方法:处理自定义分类
|
||||
private function getCustomCategoryVideos($categoryId, $page = 1) {
|
||||
$videos = [];
|
||||
|
||||
switch ($categoryId) {
|
||||
// 热门推荐细分
|
||||
case 'hot_today':
|
||||
return $this->getPopularVideos($page);
|
||||
case 'hot_week':
|
||||
return $this->getWeeklyPopularVideos($page);
|
||||
case 'hot_month':
|
||||
return $this->getMonthlyPopularVideos($page);
|
||||
case 'hot_year':
|
||||
return $this->getYearlyPopularVideos($page);
|
||||
case 'hot_soaring':
|
||||
return $this->getSoaringVideos($page);
|
||||
case 'hot_newcomer':
|
||||
return $this->getNewcomerVideos($page);
|
||||
|
||||
// 地区分类 - 使用搜索API
|
||||
case 'region_china':
|
||||
return $this->searchVideos('中国', $page);
|
||||
case 'region_japan':
|
||||
return $this->searchVideos('日本', $page);
|
||||
case 'region_korea':
|
||||
return $this->searchVideos('韩国', $page);
|
||||
case 'region_usa':
|
||||
return $this->searchVideos('美国', $page);
|
||||
case 'region_europe':
|
||||
return $this->searchVideos('欧洲', $page);
|
||||
|
||||
// 时间分类 - 结合年份搜索
|
||||
case 'time_2025':
|
||||
return $this->searchVideos('2025', $page);
|
||||
case 'time_2024':
|
||||
return $this->searchVideos('2024', $page);
|
||||
case 'time_2023':
|
||||
return $this->searchVideos('2023', $page);
|
||||
case 'time_2022':
|
||||
return $this->searchVideos('2022', $page);
|
||||
case 'time_2021':
|
||||
return $this->searchVideos('2021', $page);
|
||||
case 'time_classic':
|
||||
return $this->searchVideos('经典', $page);
|
||||
|
||||
// 类型分类
|
||||
case 'genre_funny':
|
||||
return $this->searchVideos('搞笑', $page);
|
||||
case 'genre_healing':
|
||||
return $this->searchVideos('治愈', $page);
|
||||
case 'genre_hotblood':
|
||||
return $this->searchVideos('热血', $page);
|
||||
case 'genre_romance':
|
||||
return $this->searchVideos('恋爱', $page);
|
||||
case 'genre_mystery':
|
||||
return $this->searchVideos('悬疑', $page);
|
||||
case 'genre_horror':
|
||||
return $this->searchVideos('恐怖', $page);
|
||||
|
||||
// UP主分类
|
||||
case 'uper_laofanqie':
|
||||
return $this->searchVideos('老番茄', $page);
|
||||
case 'uper_lexburner':
|
||||
return $this->searchVideos('LexBurner', $page);
|
||||
case 'uper_aochang':
|
||||
return $this->searchVideos('敖厂长', $page);
|
||||
case 'uper_wanggang':
|
||||
return $this->searchVideos('王刚', $page);
|
||||
case 'uper_liziqi':
|
||||
return $this->searchVideos('李子柒', $page);
|
||||
case 'uper_luoxiang':
|
||||
return $this->searchVideos('罗翔说刑法', $page);
|
||||
|
||||
default:
|
||||
return $this->getSampleVideos($categoryId);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增方法:获取周榜、月榜等(这里可以用不同的API或搜索条件)
|
||||
private function getWeeklyPopularVideos($page = 1) {
|
||||
// 可以使用不同的排序方式或时间范围
|
||||
return $this->getPopularVideos($page);
|
||||
}
|
||||
|
||||
private function getMonthlyPopularVideos($page = 1) {
|
||||
return $this->getPopularVideos($page);
|
||||
}
|
||||
|
||||
private function getYearlyPopularVideos($page = 1) {
|
||||
return $this->getPopularVideos($page);
|
||||
}
|
||||
|
||||
private function getSoaringVideos($page = 1) {
|
||||
// 飙升榜可以使用搜索API按播放量增长率排序
|
||||
return $this->searchVideos('', $page);
|
||||
}
|
||||
|
||||
private function getNewcomerVideos($page = 1) {
|
||||
// 新人榜可以搜索新UP主的视频
|
||||
return $this->searchVideos('新人', $page);
|
||||
}
|
||||
|
||||
// 获取热门视频
|
||||
private function getPopularVideos($page = 1) {
|
||||
try {
|
||||
$url = "{$this->baseUrl}/x/web-interface/popular?" . http_build_query([
|
||||
'pn' => $page,
|
||||
'ps' => 20
|
||||
]);
|
||||
|
||||
$response = $this->curlRequest($url);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
$videos = [];
|
||||
if (isset($data['data']['list'])) {
|
||||
foreach ($data['data']['list'] as $item) {
|
||||
$videos[] = $this->formatVideoData($item);
|
||||
}
|
||||
}
|
||||
return $videos;
|
||||
} catch (Exception $e) {
|
||||
return $this->getSampleVideos(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化视频数据 - 修复播放URL格式
|
||||
private function formatVideoData($item) {
|
||||
$aid = $item['aid'] ?? $item['id'] ?? '0';
|
||||
$bvid = $item['bvid'] ?? '';
|
||||
|
||||
// 修复播放URL - 使用av号_cid格式
|
||||
$cid = $item['cid'] ?? $item['stat']['aid'] ?? $aid;
|
||||
$playUrl = $aid . '_' . $cid;
|
||||
|
||||
return [
|
||||
'vod_id' => $aid,
|
||||
'vod_name' => $item['title'] ?? '未知标题',
|
||||
'vod_pic' => $item['pic'] ?? '',
|
||||
'vod_remarks' => $this->formatPlayCount($item['stat']['view'] ?? $item['play'] ?? 0),
|
||||
'vod_content' => $item['desc'] ?? $item['description'] ?? '',
|
||||
'vod_play_from' => 'B站',
|
||||
'vod_play_url' => "第1集$$playUrl"
|
||||
];
|
||||
}
|
||||
|
||||
// 搜索视频
|
||||
private function searchVideos($keyword, $page = 1) {
|
||||
try {
|
||||
$url = "{$this->baseUrl}/x/web-interface/search/type?" . http_build_query([
|
||||
'search_type' => 'video',
|
||||
'keyword' => $keyword,
|
||||
'page' => $page
|
||||
]);
|
||||
|
||||
$response = $this->curlRequest($url);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
$videos = [];
|
||||
if (isset($data['data']['result'])) {
|
||||
foreach ($data['data']['result'] as $item) {
|
||||
$videos[] = $this->formatVideoData($item);
|
||||
}
|
||||
}
|
||||
return $videos;
|
||||
} catch (Exception $e) {
|
||||
$playUrl = "123456_789012"; // 示例ID
|
||||
return [[
|
||||
'vod_id' => 'search_1',
|
||||
'vod_name' => '搜索结果: ' . $keyword,
|
||||
'vod_pic' => '',
|
||||
'vod_remarks' => '搜索',
|
||||
'vod_play_from' => 'B站',
|
||||
'vod_play_url' => "第1集$$playUrl"
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取视频详情
|
||||
public function getVideoDetail($aid) {
|
||||
try {
|
||||
$url = "{$this->baseUrl}/x/web-interface/view?aid=" . $aid;
|
||||
$response = $this->curlRequest($url);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (isset($data['data'])) {
|
||||
$video = $data['data'];
|
||||
$cid = $video['cid'] ?? $aid;
|
||||
$playUrl = $aid . '_' . $cid;
|
||||
|
||||
return [
|
||||
'vod_id' => $aid,
|
||||
'vod_name' => $video['title'] ?? '未知标题',
|
||||
'vod_pic' => $video['pic'] ?? '',
|
||||
'vod_content' => $video['desc'] ?? '',
|
||||
'vod_play_from' => 'B站',
|
||||
'vod_play_url' => "第1集$$playUrl"
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$playUrl = $aid . '_' . $aid;
|
||||
return [
|
||||
'vod_id' => $aid,
|
||||
'vod_name' => 'B站视频详情 - ' . $aid,
|
||||
'vod_pic' => '',
|
||||
'vod_content' => '这是一个B站视频的详细描述',
|
||||
'vod_play_from' => 'B站',
|
||||
'vod_play_url' => "第1集$$playUrl"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取播放地址 - 采用三级获取策略
|
||||
public function getPlayUrl($id) {
|
||||
if (strpos($id, '_') === false) {
|
||||
return [
|
||||
'parse' => 0,
|
||||
'url' => '',
|
||||
'header' => $this->getHeaders()
|
||||
];
|
||||
}
|
||||
|
||||
list($avid, $cid) = explode('_', $id);
|
||||
$playUrl = '';
|
||||
|
||||
// 第一级:官方API获取播放地址
|
||||
$playUrl = $this->getOfficialPlayUrl($avid, $cid);
|
||||
|
||||
// 第二级:备用地址
|
||||
if (empty($playUrl)) {
|
||||
$playUrl = $this->getBackupPlayUrl($avid, $cid);
|
||||
}
|
||||
|
||||
// 第三级:第三方解析
|
||||
if (empty($playUrl)) {
|
||||
$playUrl = $this->getThirdPartyPlayUrl($avid, $cid);
|
||||
}
|
||||
|
||||
$headers = $this->getHeaders();
|
||||
$headers['Referer'] = 'https://www.bilibili.com/video/av' . $avid;
|
||||
|
||||
return [
|
||||
'parse' => 0, // 0=直接播放
|
||||
'url' => $playUrl,
|
||||
'header' => $headers
|
||||
];
|
||||
}
|
||||
|
||||
// 官方API获取播放地址
|
||||
private function getOfficialPlayUrl($avid, $cid) {
|
||||
try {
|
||||
$url = "{$this->baseUrl}/x/player/playurl";
|
||||
$params = [
|
||||
'avid' => $avid,
|
||||
'cid' => $cid,
|
||||
'qn' => 116, // 高清
|
||||
'fnval' => 16,
|
||||
'fourk' => 1
|
||||
];
|
||||
|
||||
$response = $this->curlRequest($url, $params);
|
||||
$data = json_decode($response, true);
|
||||
|
||||
if (isset($data['data']['durl'][0]['url'])) {
|
||||
return $data['data']['durl'][0]['url'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// 忽略错误,继续尝试其他方法
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// 获取备用播放地址
|
||||
private function getBackupPlayUrl($avid, $cid) {
|
||||
// 尝试多种备用地址格式
|
||||
$backupUrls = [
|
||||
"https://cn-bj-cc-01-12.bilivideo.com/upgcxcode/21/73/{$cid}/{$cid}-1-80.flv",
|
||||
"https://upos-sz-mirrorcos.bilivideo.com/upgcxcode/21/73/{$cid}/{$cid}-1-80.flv",
|
||||
];
|
||||
|
||||
foreach ($backupUrls as $url) {
|
||||
if ($this->checkUrlAccessible($url)) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// 获取第三方解析地址
|
||||
private function getThirdPartyPlayUrl($avid, $cid) {
|
||||
// 使用第三方B站解析服务
|
||||
$thirdPartyUrls = [
|
||||
"https://api.injahow.cn/bparse/?av={$avid}&cid={$cid}",
|
||||
];
|
||||
|
||||
foreach ($thirdPartyUrls as $url) {
|
||||
try {
|
||||
$result = $this->curlRequest($url);
|
||||
$data = json_decode($result, true);
|
||||
if (isset($data['url']) && !empty($data['url'])) {
|
||||
return $data['url'];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
// 继续尝试下一个
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// 检查URL是否可访问
|
||||
private function checkUrlAccessible($url) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_NOBODY => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
CURLOPT_FOLLOWLOCATION => true
|
||||
]);
|
||||
curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
return $httpCode === 200;
|
||||
}
|
||||
|
||||
// 获取请求头
|
||||
private function getHeaders() {
|
||||
return [
|
||||
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
'Referer' => 'https://www.bilibili.com'
|
||||
];
|
||||
}
|
||||
|
||||
// 示例视频数据
|
||||
private function getSampleVideos($tid) {
|
||||
$samples = [];
|
||||
$categoryName = $this->getCategoryNameByTid($tid);
|
||||
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$playUrl = "123456_789012";
|
||||
|
||||
$samples[] = [
|
||||
'vod_id' => 'sample_' . $tid . '_' . $i,
|
||||
'vod_name' => $categoryName . '示例视频' . $i,
|
||||
'vod_pic' => '',
|
||||
'vod_remarks' => '示例',
|
||||
'vod_content' => '这是一个示例视频描述',
|
||||
'vod_play_from' => 'B站',
|
||||
'vod_play_url' => "第1集$$playUrl"
|
||||
];
|
||||
}
|
||||
|
||||
return $samples;
|
||||
}
|
||||
|
||||
// 根据tid获取分类名称
|
||||
private function getCategoryNameByTid($tid) {
|
||||
foreach ($this->categories as $category) {
|
||||
if (isset($category['tid']) && $category['tid'] == $tid) {
|
||||
return $category['name'];
|
||||
}
|
||||
}
|
||||
return '未知分类';
|
||||
}
|
||||
|
||||
// 格式化播放量
|
||||
private function formatPlayCount($count) {
|
||||
if ($count >= 10000) {
|
||||
return round($count / 10000, 1) . '万';
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
// CURL请求
|
||||
private function curlRequest($url, $params = []) {
|
||||
$ch = curl_init();
|
||||
|
||||
if (!empty($params)) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||
CURLOPT_REFERER => 'https://www.bilibili.com',
|
||||
CURLOPT_ENCODING => 'gzip'
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
throw new Exception('HTTP请求失败: ' . $httpCode);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化B站爬虫
|
||||
$biliSpider = new BiliBiliSpider();
|
||||
|
||||
// 获取子分类
|
||||
function getChildCategories($parentId) {
|
||||
global $biliSpider;
|
||||
return $biliSpider->getChildren($parentId);
|
||||
}
|
||||
|
||||
// 获取分类信息
|
||||
function getCategoryInfo($categoryId) {
|
||||
global $biliSpider;
|
||||
return $biliSpider->getCategory($categoryId);
|
||||
}
|
||||
|
||||
// 主逻辑
|
||||
switch ($ac) {
|
||||
case 'detail':
|
||||
if (!empty($ids)) {
|
||||
$videoDetail = $biliSpider->getVideoDetail($ids);
|
||||
$data = ['list' => [$videoDetail]];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
} elseif (!empty($t)) {
|
||||
$filters = !empty($f) ? json_decode($f, true) : [];
|
||||
|
||||
$isSubRequest = isset($filters['is_sub']) && $filters['is_sub'] === 'true';
|
||||
$categoryId = $filters['category_id'] ?? $t;
|
||||
|
||||
$categoryInfo = getCategoryInfo($categoryId);
|
||||
$hasChildren = $categoryInfo['has_children'] ?? false;
|
||||
|
||||
if ($isSubRequest && !$hasChildren) {
|
||||
$tid = $categoryInfo['tid'] ?? 0;
|
||||
$videos = $biliSpider->getVideoList($tid, $pg);
|
||||
|
||||
$data = [
|
||||
'list' => $videos,
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 10,
|
||||
'limit' => 20,
|
||||
'total' => 200,
|
||||
'current_category' => $categoryInfo['name'] ?? '',
|
||||
'style' => ['type' => 'rect', 'ratio' => 0.75]
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
$children = getChildCategories($t);
|
||||
|
||||
if (empty($children)) {
|
||||
$tid = $categoryInfo['tid'] ?? 0;
|
||||
$videos = $biliSpider->getVideoList($tid, $pg);
|
||||
|
||||
$data = [
|
||||
'list' => $videos,
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 10,
|
||||
'limit' => 20,
|
||||
'total' => 200,
|
||||
'current_category' => $categoryInfo['name'] ?? '',
|
||||
'style' => ['type' => 'rect', 'ratio' => 0.75]
|
||||
];
|
||||
} else {
|
||||
$subList = [];
|
||||
foreach ($children as $child) {
|
||||
$subList[] = [
|
||||
'vod_id' => $child['id'],
|
||||
'vod_name' => $child['name'],
|
||||
'vod_pic' => '',
|
||||
'vod_remarks' => '分类'
|
||||
];
|
||||
}
|
||||
|
||||
$data = [
|
||||
'is_sub' => true,
|
||||
'list' => $subList,
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 1,
|
||||
'limit' => 20,
|
||||
'total' => count($subList),
|
||||
'parent_category' => $categoryInfo['name'] ?? '',
|
||||
'style' => ['type' => 'rect', 'ratio' => 1.5]
|
||||
];
|
||||
}
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
$data = [
|
||||
'class' => $biliSpider->getLevel1Categories(),
|
||||
'list' => $biliSpider->getVideoList(0, 1), // 热门推荐
|
||||
'style' => ['type' => 'rect', 'ratio' => 1.33]
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
$videos = $biliSpider->getVideoList(0, $pg, $wd);
|
||||
$data = [
|
||||
'list' => $videos,
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 10,
|
||||
'limit' => 20,
|
||||
'total' => count($videos)
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
case 'play':
|
||||
$playInfo = $biliSpider->getPlayUrl($id);
|
||||
echo json_encode($playInfo, JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
default:
|
||||
$data = ['error' => 'Unknown action: ' . $ac];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
300
scripts/batch_test.php
Normal file
300
scripts/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
scripts/config.php
Normal file
47
scripts/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
|
||||
);
|
||||
|
||||
181
scripts/example_t4.php
Normal file
181
scripts/example_t4.php
Normal file
@@ -0,0 +1,181 @@
|
||||
<?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
scripts/img.php
Normal file
9
scripts/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]");
|
||||
?>
|
||||
9
scripts/img.php.bak
Normal file
9
scripts/img.php.bak
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]");
|
||||
?>
|
||||
15
scripts/index.php
Normal file
15
scripts/index.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?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);
|
||||
|
||||
135
scripts/mfc-server.php
Normal file
135
scripts/mfc-server.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
// 记得先修改node_api改成你自己的ip
|
||||
$node_api = 'http://192.168.100.1:3000';
|
||||
$my_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[PHP_SELF]";
|
||||
|
||||
$backup_url = 'https://cdn.jsdelivr.net/gh/jerainlvjing/tvlive@demo/mfc1.mp4';
|
||||
|
||||
ini_set('default_socket_timeout', 10);
|
||||
|
||||
if (isset($_GET['id']) && !isset($_GET['play']) && !isset($_GET['player'])) {
|
||||
$target = $_GET['id'];
|
||||
$json = fetchUrl($node_api . '/api/play?id=' . urlencode($target));
|
||||
$res = json_decode($json, true);
|
||||
|
||||
if (isset($res['url']) && !empty($res['url'])) {
|
||||
header("Location: " . $res['url']);
|
||||
} else {
|
||||
header("Location: " . $backup_url);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
elseif (isset($_GET['list'])) {
|
||||
$items = getListData($node_api);
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
echo "#EXTM3U\n";
|
||||
foreach ($items as $item) {
|
||||
$play_link = "{$my_url}?id={$item['vod_id']}";
|
||||
echo "#EXTINF:-1 tvg-logo=\"{$item['vod_pic']}\" group-title=\"mfc-live\", {$item['vod_name']}\n";
|
||||
echo "{$play_link}\n";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
elseif (isset($_GET['play']) || isset($_GET['player'])) {
|
||||
$items = getListData($node_api);
|
||||
$auto_play_id = isset($_GET['id']) ? $_GET['id'] : '';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>MFC Player</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||
<style>
|
||||
body { margin: 0; background: #000; color: #fff; display: flex; height: 100vh; font-family: sans-serif; overflow: hidden; }
|
||||
#player { flex: 1; display: flex; align-items: center; justify-content: center; background: #111; position: relative;}
|
||||
video { width: 100%; height: 100%; max-height: 100vh; }
|
||||
#sidebar { width: 300px; background: #1a1a1a; overflow-y: auto; border-left: 1px solid #333; display: flex; flex-direction: column; }
|
||||
.list-item { padding: 10px; display: flex; align-items: center; cursor: pointer; border-bottom: 1px solid #333; transition: 0.2s; }
|
||||
.list-item:hover { background: #333; }
|
||||
.list-item.active { background: #e91e63; }
|
||||
.list-item img { width: 40px; height: 40px; border-radius: 50%; margin-right: 10px; }
|
||||
.list-item .name { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
#loading { display: none; position: absolute; color: #ccc; font-size: 20px; }
|
||||
@media (max-width: 768px) { body { flex-direction: column; } #player { height: 40vh; flex: none; } #sidebar { flex: 1; width: 100%; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="player">
|
||||
<div id="loading">Loading...</div>
|
||||
<video id="video" controls autoplay playsinline></video>
|
||||
</div>
|
||||
<div id="sidebar">
|
||||
<div style="padding:15px;text-align:center;background:#222;">
|
||||
Online (<?php echo count($items); ?>)
|
||||
</div>
|
||||
<?php foreach ($items as $item): ?>
|
||||
<div class="list-item" id="item-<?php echo $item['vod_id']; ?>" onclick="playChannel('<?php echo $item['vod_id']; ?>', this)">
|
||||
<img src="<?php echo $item['vod_pic']; ?>" loading="lazy">
|
||||
<div class="name"><?php echo $item['vod_name']; ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<script>
|
||||
var hls = new Hls();
|
||||
var video = document.getElementById('video');
|
||||
var loading = document.getElementById('loading');
|
||||
|
||||
function playChannel(id, el) {
|
||||
document.querySelectorAll('.list-item').forEach(i => i.classList.remove('active'));
|
||||
if(el) el.classList.add('active');
|
||||
|
||||
loading.style.display = 'block';
|
||||
var playUrl = '<?php echo $my_url; ?>?id=' + encodeURIComponent(id);
|
||||
|
||||
if(Hls.isSupported()){
|
||||
hls.loadSource(playUrl);
|
||||
hls.attachMedia(video);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED,function(){ loading.style.display = 'none'; video.play(); });
|
||||
hls.on(Hls.Events.ERROR, function (event, data) {
|
||||
if (data.fatal) {
|
||||
hls.destroy();
|
||||
video.src = playUrl;
|
||||
video.play();
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
video.src = playUrl;
|
||||
video.play();
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
var autoId = "<?php echo $auto_play_id; ?>";
|
||||
if(autoId) {
|
||||
var targetEl = document.getElementById('item-' + autoId);
|
||||
playChannel(autoId, targetEl);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
function fetchUrl($url) {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 12);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
|
||||
$output = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $output;
|
||||
}
|
||||
|
||||
function getListData($node_api) {
|
||||
$json = fetchUrl($node_api . '/api/list');
|
||||
$data = json_decode($json, true);
|
||||
if (isset($data['data']) && is_array($data['data'])) {
|
||||
return $data['data'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
?>
|
||||
319
scripts/spider.php
Normal file
319
scripts/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
scripts/test_runner.php
Normal file
248
scripts/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
scripts/全能王终端兼容修复版.php
Normal file
2512
scripts/全能王终端兼容修复版.php
Normal file
File diff suppressed because it is too large
Load Diff
176
scripts/哇哇影视.php
Normal file
176
scripts/哇哇影视.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* 哇哇视频 - PHP 适配版
|
||||
* 1. 自动生成全部分类筛选 (Filters)
|
||||
* 2. 实时 RSA 签名与 AES 解密
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
error_reporting(0);
|
||||
|
||||
// ================= 核心加解密类 =================
|
||||
class WawaCrypto {
|
||||
public static function decrypt($encrypted_data) {
|
||||
$key = base64_decode('Crm4FXWkk5JItpYirFDpqg=='); //
|
||||
$data = hex2bin(base64_decode($encrypted_data)); //
|
||||
return openssl_decrypt($data, 'AES-128-ECB', $key, OPENSSL_RAW_DATA);
|
||||
}
|
||||
|
||||
public static function sign($message, $privateKey) {
|
||||
$key = "-----BEGIN PRIVATE KEY-----\n" . wordwrap($privateKey, 64, "\n", true) . "\n-----END PRIVATE KEY-----";
|
||||
$res = openssl_get_privatekey($key);
|
||||
openssl_sign($message, $signature, $res, OPENSSL_ALGO_SHA256); // 使用 SHA256 签名
|
||||
return base64_encode($signature);
|
||||
}
|
||||
|
||||
public static function uuid() {
|
||||
return sprintf('%04x%04x%04x%04x%04x%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ================= 工具函数 =================
|
||||
function fetch($url, $headers = []) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 获取基础配置
|
||||
function getBaseInfo() {
|
||||
$uid = WawaCrypto::uuid();
|
||||
$t = (string)(time() * 1000);
|
||||
$sign = md5("appKey=3bbf7348cf314874883a18d6b6fcf67a&uid=$uid&time=$t"); //
|
||||
|
||||
$url = 'https://gitee.com/api/v5/repos/aycapp/openapi/contents/wawaconf.txt?access_token=74d5879931b9774be10dee3d8c51008e';
|
||||
$res = json_decode(fetch($url, ["User-Agent: okhttp/4.9.3", "uid: $uid", "time: $t", "sign: $sign"]), true);
|
||||
return json_decode(WawaCrypto::decrypt($res['content']), true);
|
||||
}
|
||||
|
||||
$CONF = getBaseInfo();
|
||||
$HOST = $CONF['baseUrl'];
|
||||
$APP_KEY = $CONF['appKey'];
|
||||
$RSA_KEY = $CONF['appSecret'];
|
||||
|
||||
function getWawaHeaders() {
|
||||
global $APP_KEY, $RSA_KEY;
|
||||
$uid = WawaCrypto::uuid();
|
||||
$t = (string)(time() * 1000);
|
||||
$sign = WawaCrypto::sign("appKey=$APP_KEY&time=$t&uid=$uid", $RSA_KEY); //
|
||||
return [
|
||||
'User-Agent: okhttp/4.9.3',
|
||||
"uid: $uid",
|
||||
"time: $t",
|
||||
"appKey: $APP_KEY",
|
||||
"sign: $sign"
|
||||
];
|
||||
}
|
||||
|
||||
// ================= 路由逻辑 =================
|
||||
$ac = $_GET['ac'] ?? null;
|
||||
$t = $_GET['t'] ?? null;
|
||||
$pg = $_GET['pg'] ?? '1';
|
||||
$ids = $_GET['ids'] ?? null;
|
||||
$wd = $_GET['wd'] ?? null;
|
||||
$play = $_GET['play'] ?? null;
|
||||
|
||||
// 1. 播放解析
|
||||
if ($play) {
|
||||
$playData = json_decode(base64_decode($play), true);
|
||||
echo json_encode([
|
||||
'parse' => 1,
|
||||
'url' => $playData['url'],
|
||||
'header' => ['User-Agent' => 'dart:io']
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. 视频详情
|
||||
if ($ids) {
|
||||
$res = json_decode(fetch("$HOST/api.php/zjv6.vod/detail?vod_id=$ids&rel_limit=10", getWawaHeaders()), true);
|
||||
$item = $res['data'];
|
||||
$playFrom = []; $playUrls = [];
|
||||
foreach ($item['vod_play_list'] as $list) {
|
||||
$playFrom[] = $list['player_info']['show'];
|
||||
$urls = [];
|
||||
foreach ($list['urls'] as $u) {
|
||||
$u['parse'] = $list['player_info']['parse2'];
|
||||
$urls[] = $u['name'] . '$' . base64_encode(json_encode($u));
|
||||
}
|
||||
$playUrls[] = implode('#', $urls);
|
||||
}
|
||||
echo json_encode(['list' => [[
|
||||
'vod_id' => $item['vod_id'],
|
||||
'vod_name' => $item['vod_name'],
|
||||
'vod_pic' => $item['vod_pic'],
|
||||
'vod_remarks' => $item['vod_remarks'],
|
||||
'vod_content' => $item['vod_content'],
|
||||
'vod_play_from' => implode('$$$', $playFrom),
|
||||
'vod_play_url' => implode('$$$', $playUrls)
|
||||
]]], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. 搜索
|
||||
if ($wd) {
|
||||
$res = json_decode(fetch("$HOST/api.php/zjv6.vod?page=$pg&limit=20&wd=".urlencode($wd), getWawaHeaders()), true);
|
||||
echo json_encode(['list' => $res['data']['list'] ?: [], 'page' => $pg]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. 分类列表 (含筛选)
|
||||
if ($ac === 'detail' && $t) {
|
||||
$ext = !empty($_GET['ext']) ? json_decode(base64_decode($_GET['ext']), true) : [];
|
||||
$query = http_build_query([
|
||||
'type' => $t, 'page' => $pg, 'limit' => '12',
|
||||
'class' => $ext['class'] ?? '', 'area' => $ext['area'] ?? '',
|
||||
'year' => $ext['year'] ?? '', 'by' => $ext['by'] ?? ''
|
||||
]);
|
||||
$res = json_decode(fetch("$HOST/api.php/zjv6.vod?$query", getWawaHeaders()), true);
|
||||
echo json_encode([
|
||||
'list' => $res['data']['list'] ?: [],
|
||||
'page' => $pg, 'pagecount' => 999, 'limit' => 12, 'total' => 9999
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5. 首页 (精选 + 自动生成筛选配置)
|
||||
$typeData = json_decode(fetch("$HOST/api.php/zjv6.vod/types", getWawaHeaders()), true);
|
||||
$classes = []; $filters = [];
|
||||
$dy = ["class" => "类型", "area" => "地区", "lang" => "语言", "year" => "年份", "letter" => "字母", "by" => "排序"];
|
||||
$sl = ['按更新' => 'time', '按播放' => 'hits', '按评分' => 'score', '按收藏' => 'store_num'];
|
||||
|
||||
foreach ($typeData['data']['list'] as $item) {
|
||||
$classes[] = ['type_id' => $item['type_id'], 'type_name' => $item['type_name']];
|
||||
$tid = (string)$item['type_id'];
|
||||
$filters[$tid] = [];
|
||||
$item['type_extend']['by'] = '按更新,按播放,按评分,按收藏'; // 强制注入排序
|
||||
|
||||
foreach ($dy as $key => $name) {
|
||||
if (!empty($item['type_extend'][$key])) {
|
||||
$values = explode(',', $item['type_extend'][$key]);
|
||||
$value_array = [];
|
||||
foreach ($values as $v) {
|
||||
if (empty($v)) continue;
|
||||
$value_array[] = ["n" => $v, "v" => ($key == "by" ? ($sl[$v] ?? $v) : $v)];
|
||||
}
|
||||
$filters[$tid][] = ["key" => $key, "name" => $name, "value" => $value_array];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$homeList = json_decode(fetch("$HOST/api.php/zjv6.vod/vodPhbAll", getWawaHeaders()), true);
|
||||
echo json_encode([
|
||||
'class' => $classes,
|
||||
'filters' => $filters,
|
||||
'list' => $homeList['data']['list'][0]['vod_list'] ?: [],
|
||||
'page' => 1, 'pagecount' => 1, 'limit' => 20, 'total' => 20
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
386
scripts/哔哩哔哩.php
Normal file
386
scripts/哔哩哔哩.php
Normal file
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
// B站视频爬虫 - 简洁可用版(移除search相关代码)
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
class BiliBiliSpider {
|
||||
private $extendDict = [];
|
||||
private $cookie = [];
|
||||
private $header = [
|
||||
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
|
||||
"Referer" => "https://www.bilibili.com"
|
||||
];
|
||||
|
||||
public function __construct() {
|
||||
$this->extendDict = $this->getExtendDict();
|
||||
$this->cookie = $this->getCookie();
|
||||
}
|
||||
|
||||
private function getExtendDict() {
|
||||
return [
|
||||
'cookie' => $this->getConfigCookie(),
|
||||
'thread' => '0'
|
||||
];
|
||||
}
|
||||
|
||||
private function getConfigCookie() {
|
||||
// 配置您的B站Cookie
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getCookie() {
|
||||
$cookie = $this->extendDict['cookie'] ?? '';
|
||||
if (empty($cookie)) return [];
|
||||
|
||||
$cookies = [];
|
||||
$pairs = explode(';', $cookie);
|
||||
foreach ($pairs as $pair) {
|
||||
$pair = trim($pair);
|
||||
if (strpos($pair, '=') !== false) {
|
||||
list($name, $value) = explode('=', $pair, 2);
|
||||
$cookies[trim($name)] = trim($value);
|
||||
}
|
||||
}
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
private function httpRequest($url, $params = []) {
|
||||
$ch = curl_init();
|
||||
|
||||
if (!empty($params)) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
foreach ($this->header as $key => $value) {
|
||||
$headers[] = $key . ': ' . $value;
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_COOKIE => $this->buildCookieString(),
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($response, true) ?: [];
|
||||
}
|
||||
|
||||
private function buildCookieString() {
|
||||
$pairs = [];
|
||||
foreach ($this->cookie as $name => $value) {
|
||||
$pairs[] = $name . '=' . $value;
|
||||
}
|
||||
return implode('; ', $pairs);
|
||||
}
|
||||
|
||||
// homeContent - 首页分类
|
||||
public function homeContent() {
|
||||
$classes = [
|
||||
["type_id" => "动态漫画", "type_name" => "动态漫画"],
|
||||
["type_id" => "日番", "type_name" => "日番"],
|
||||
["type_id" => "英文歌曲", "type_name" => "英文歌曲"],
|
||||
["type_id" => "日语歌曲", "type_name" => "日语歌曲"],
|
||||
["type_id" => "韩语歌曲", "type_name" => "韩语歌曲"],
|
||||
["type_id" => "hiphop舞蹈", "type_name" => "hiphop舞蹈"],
|
||||
["type_id" => "国外旅行", "type_name" => "国外旅行"],
|
||||
["type_id" => "日韩综艺", "type_name" => "日韩综艺"]
|
||||
];
|
||||
|
||||
return ['class' => $classes];
|
||||
}
|
||||
|
||||
// homeVideoContent - 首页推荐视频
|
||||
public function homeVideoContent() {
|
||||
$url = 'https://api.bilibili.com/x/web-interface/popular';
|
||||
$data = $this->httpRequest($url, ['ps' => 20, 'pn' => 1]);
|
||||
|
||||
$videos = [];
|
||||
if (isset($data['data']['list'])) {
|
||||
foreach ($data['data']['list'] as $item) {
|
||||
$videos[] = [
|
||||
'vod_id' => $item['aid'],
|
||||
'vod_name' => strip_tags($item['title']),
|
||||
'vod_pic' => $item['pic'],
|
||||
'vod_remarks' => $this->formatDuration($item['duration'])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return ['list' => $videos];
|
||||
}
|
||||
|
||||
// categoryContent - 分类内容(使用搜索API)
|
||||
public function categoryContent($tid, $page, $filters = []) {
|
||||
$page = max(1, intval($page));
|
||||
|
||||
$url = 'https://api.bilibili.com/x/web-interface/search/type';
|
||||
$params = [
|
||||
'search_type' => 'video',
|
||||
'keyword' => $tid,
|
||||
'page' => $page
|
||||
];
|
||||
|
||||
$data = $this->httpRequest($url, $params);
|
||||
|
||||
$videos = [];
|
||||
if (isset($data['data']['result'])) {
|
||||
foreach ($data['data']['result'] as $item) {
|
||||
if ($item['type'] !== 'video') continue;
|
||||
|
||||
$videos[] = [
|
||||
'vod_id' => $item['aid'],
|
||||
'vod_name' => strip_tags($item['title']),
|
||||
'vod_pic' => 'https:' . $item['pic'],
|
||||
'vod_remarks' => $this->formatSearchDuration($item['duration'])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$pageCount = $data['data']['numPages'] ?? 1;
|
||||
$total = $data['data']['numResults'] ?? count($videos);
|
||||
|
||||
return [
|
||||
'list' => $videos,
|
||||
'page' => $page,
|
||||
'pagecount' => $pageCount,
|
||||
'limit' => 20,
|
||||
'total' => $total
|
||||
];
|
||||
}
|
||||
|
||||
// detailContent - 视频详情
|
||||
public function detailContent($vid) {
|
||||
$url = 'https://api.bilibili.com/x/web-interface/view';
|
||||
$data = $this->httpRequest($url, ['aid' => $vid]);
|
||||
|
||||
if (!isset($data['data'])) {
|
||||
return ['list' => []];
|
||||
}
|
||||
|
||||
$video = $data['data'];
|
||||
|
||||
// 构建播放列表
|
||||
$playUrl = '';
|
||||
foreach ($video['pages'] as $index => $page) {
|
||||
$part = $page['part'] ?: '第' . ($index + 1) . '集';
|
||||
$duration = $this->formatDuration($page['duration']);
|
||||
$playUrl .= "{$part}\${$vid}_{$page['cid']}#";
|
||||
}
|
||||
|
||||
$vod = [
|
||||
"vod_id" => $vid,
|
||||
"vod_name" => strip_tags($video['title']),
|
||||
"vod_pic" => $video['pic'],
|
||||
"vod_content" => $video['desc'],
|
||||
"vod_play_from" => "B站视频",
|
||||
"vod_play_url" => rtrim($playUrl, '#')
|
||||
];
|
||||
|
||||
return ['list' => [$vod]];
|
||||
}
|
||||
|
||||
// playContent - 播放地址(高清优化)
|
||||
public function playContent($vid) {
|
||||
if (strpos($vid, '_') !== false) {
|
||||
list($avid, $cid) = explode('_', $vid);
|
||||
} else {
|
||||
return $this->errorResponse('无效的视频ID格式');
|
||||
}
|
||||
|
||||
// 使用高质量参数
|
||||
$url = 'https://api.bilibili.com/x/player/playurl';
|
||||
$params = [
|
||||
'avid' => $avid,
|
||||
'cid' => $cid,
|
||||
'qn' => 80, // 原画质量
|
||||
'fnval' => 0,
|
||||
];
|
||||
|
||||
$data = $this->httpRequest($url, $params);
|
||||
|
||||
if (!isset($data['data']) || $data['code'] !== 0) {
|
||||
return $this->errorResponse('获取播放地址失败');
|
||||
}
|
||||
|
||||
// 直接返回第一个播放地址
|
||||
if (isset($data['data']['durl'][0]['url'])) {
|
||||
$playUrl = $data['data']['durl'][0]['url'];
|
||||
|
||||
$headers = $this->header;
|
||||
$headers['Referer'] = 'https://www.bilibili.com/video/av' . $avid;
|
||||
$headers['Origin'] = 'https://www.bilibili.com';
|
||||
|
||||
return [
|
||||
'parse' => 0,
|
||||
'url' => $playUrl,
|
||||
'header' => $headers,
|
||||
'danmaku' => "https://api.bilibili.com/x/v1/dm/list.so?oid={$cid}"
|
||||
];
|
||||
}
|
||||
|
||||
return $this->errorResponse('无法获取播放地址');
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
private function formatDuration($seconds) {
|
||||
if ($seconds <= 0) return '00:00';
|
||||
$minutes = floor($seconds / 60);
|
||||
$secs = $seconds % 60;
|
||||
return sprintf('%02d:%02d', $minutes, $secs);
|
||||
}
|
||||
|
||||
private function formatSearchDuration($duration) {
|
||||
$parts = explode(':', $duration);
|
||||
if (count($parts) === 2) {
|
||||
return $duration;
|
||||
}
|
||||
return '00:00';
|
||||
}
|
||||
|
||||
private function errorResponse($message) {
|
||||
return [
|
||||
'parse' => 0,
|
||||
'url' => '',
|
||||
'error' => $message
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取请求参数
|
||||
$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) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
$spider = new BiliBiliSpider();
|
||||
|
||||
try {
|
||||
// ============================================================================
|
||||
// 首页/分类接口
|
||||
// ============================================================================
|
||||
if ($filter !== null) {
|
||||
echo json_encode([
|
||||
'class' => [
|
||||
['type_id' => '动态漫画', 'type_name' => '动态漫画'],
|
||||
['type_id' => '日番', 'type_name' => '日番'],
|
||||
['type_id' => '日语歌曲', 'type_name' => '日语歌曲'],
|
||||
['type_id' => '韩语歌曲', 'type_name' => '韩语歌曲'],
|
||||
['type_id' => '英文歌曲', 'type_name' => '英文歌曲'],
|
||||
['type_id' => 'hiphop舞蹈', 'type_name' => 'hiphop舞蹈'],
|
||||
['type_id' => '国外旅行', 'type_name' => '国外旅行'],
|
||||
['type_id' => '日韩综艺', 'type_name' => '日韩综艺']
|
||||
],
|
||||
'filters' => [
|
||||
'1001' => [
|
||||
[
|
||||
'key' => 'sort',
|
||||
'name' => '排序',
|
||||
'value' => [
|
||||
['n' => '最新', 'v' => 'new'],
|
||||
['n' => '最热', 'v' => 'hot'],
|
||||
['n' => '推荐', 'v' => 'recommend']
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 首页视频内容
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && empty($t) && empty($ids)) {
|
||||
$result = $spider->homeContent();
|
||||
$videoResult = $spider->homeVideoContent();
|
||||
$result['list'] = $videoResult['list'];
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 分类列表接口
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && $t !== null) {
|
||||
$filters = !empty($extData) ? $extData : [];
|
||||
echo json_encode($spider->categoryContent($t, $pg, $filters), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 详情接口
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && $ids !== null) {
|
||||
echo json_encode($spider->detailContent($ids), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 播放解析接口
|
||||
// ============================================================================
|
||||
if ($flag !== null && $play !== null) {
|
||||
// 解析B站视频地址
|
||||
if (strpos($play, 'bilibili.com') !== false) {
|
||||
// 对于B站地址,直接返回(TVBox会自行解析)
|
||||
echo json_encode([
|
||||
'parse' => 0,
|
||||
'url' => $play,
|
||||
'header' => [
|
||||
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36',
|
||||
'Referer' => 'https://www.bilibili.com/'
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
// 使用spider的playContent方法解析内部格式
|
||||
echo json_encode($spider->playContent($play), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 搜索接口(简单实现)
|
||||
// ============================================================================
|
||||
if ($wd !== null) {
|
||||
echo json_encode($spider->categoryContent($wd, $pg), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 默认响应
|
||||
// ============================================================================
|
||||
echo json_encode([
|
||||
'error' => '未知请求',
|
||||
'params' => $_GET,
|
||||
'info' => [
|
||||
'name' => 'B站视频源',
|
||||
'version' => '1.0.0',
|
||||
'description' => '简洁可用版B站爬虫接口',
|
||||
'platform' => 'TVBox/T4',
|
||||
'php_version' => PHP_VERSION
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
441
scripts/哔哩大全.php
Normal file
441
scripts/哔哩大全.php
Normal file
@@ -0,0 +1,441 @@
|
||||
<?php
|
||||
// B站视频爬虫 - 简洁可用版(移除search相关代码)
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
class BiliBiliSpider {
|
||||
private $extendDict = [];
|
||||
private $cookie = [];
|
||||
private $header = [
|
||||
"User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36",
|
||||
"Referer" => "https://www.bilibili.com"
|
||||
];
|
||||
|
||||
public function __construct() {
|
||||
$this->extendDict = $this->getExtendDict();
|
||||
$this->cookie = $this->getCookie();
|
||||
}
|
||||
|
||||
private function getExtendDict() {
|
||||
return [
|
||||
'cookie' => $this->getConfigCookie(),
|
||||
'thread' => '0'
|
||||
];
|
||||
}
|
||||
|
||||
private function getConfigCookie() {
|
||||
// 配置您的B站Cookie
|
||||
return '';
|
||||
}
|
||||
|
||||
private function getCookie() {
|
||||
$cookie = $this->extendDict['cookie'] ?? '';
|
||||
if (empty($cookie)) return [];
|
||||
|
||||
$cookies = [];
|
||||
$pairs = explode(';', $cookie);
|
||||
foreach ($pairs as $pair) {
|
||||
$pair = trim($pair);
|
||||
if (strpos($pair, '=') !== false) {
|
||||
list($name, $value) = explode('=', $pair, 2);
|
||||
$cookies[trim($name)] = trim($value);
|
||||
}
|
||||
}
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
private function httpRequest($url, $params = []) {
|
||||
$ch = curl_init();
|
||||
|
||||
if (!empty($params)) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
foreach ($this->header as $key => $value) {
|
||||
$headers[] = $key . ': ' . $value;
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_COOKIE => $this->buildCookieString(),
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return json_decode($response, true) ?: [];
|
||||
}
|
||||
|
||||
private function buildCookieString() {
|
||||
$pairs = [];
|
||||
foreach ($this->cookie as $name => $value) {
|
||||
$pairs[] = $name . '=' . $value;
|
||||
}
|
||||
return implode('; ', $pairs);
|
||||
}
|
||||
|
||||
// homeContent - 首页分类
|
||||
public function homeContent() {
|
||||
$classes = [
|
||||
["type_id" => "沙雕仙逆", "type_name" => "傻屌仙逆"],
|
||||
["type_id" => "沙雕动画", "type_name" => "沙雕动画"],
|
||||
["type_id" => "纪录片超清", "type_name" => "纪录片"],
|
||||
["type_id" => "演唱会超清", "type_name" => "演唱会"],
|
||||
["type_id" => "音乐超清", "type_name" => "流行音乐"],
|
||||
["type_id" => "美食超清", "type_name" => "美食"],
|
||||
["type_id" => "食谱", "type_name" => "食谱"],
|
||||
["type_id" => "体育超清", "type_name" => "体育"],
|
||||
["type_id" => "球星", "type_name" => "球星"],
|
||||
["type_id" => "中小学教育", "type_name" => "教育"],
|
||||
["type_id" => "幼儿教育", "type_name" => "幼儿教育"],
|
||||
["type_id" => "旅游", "type_name" => "旅游"],
|
||||
["type_id" => "风景4K", "type_name" => "风景"],
|
||||
["type_id" => "说案", "type_name" => "说案"],
|
||||
["type_id" => "知名UP主", "type_name" => "知名UP主"],
|
||||
["type_id" => "探索发现超清", "type_name" => "探索发现"],
|
||||
["type_id" => "鬼畜", "type_name" => "鬼畜"],
|
||||
["type_id" => "搞笑超清", "type_name" => "搞笑"],
|
||||
["type_id" => "儿童超清", "type_name" => "儿童"],
|
||||
["type_id" => "动物世界超清", "type_name" => "动物世界"],
|
||||
["type_id" => "相声小品超清", "type_name" => "相声小品"],
|
||||
["type_id" => "戏曲", "type_name" => "戏曲"],
|
||||
["type_id" => "解说", "type_name" => "解说"],
|
||||
["type_id" => "演讲", "type_name" => "演讲"],
|
||||
["type_id" => "小姐姐超清", "type_name" => "小姐姐"],
|
||||
["type_id" => "荒野求生超清", "type_name" => "荒野求生"],
|
||||
["type_id" => "健身", "type_name" => "健身"],
|
||||
["type_id" => "帕梅拉", "type_name" => "帕梅拉"],
|
||||
["type_id" => "太极拳", "type_name" => "太极拳"],
|
||||
["type_id" => "广场舞", "type_name" => "广场舞"],
|
||||
["type_id" => "舞蹈", "type_name" => "舞蹈"],
|
||||
["type_id" => "音乐", "type_name" => "音乐"],
|
||||
["type_id" => "歌曲", "type_name" => "歌曲"],
|
||||
["type_id" => "MV4K", "type_name" => "MV"],
|
||||
["type_id" => "舞曲超清", "type_name" => "舞曲"],
|
||||
["type_id" => "4K", "type_name" => "4K"],
|
||||
["type_id" => "电影", "type_name" => "电影"],
|
||||
["type_id" => "电视剧", "type_name" => "电视剧"],
|
||||
["type_id" => "白噪音超清", "type_name" => "白噪音"],
|
||||
["type_id" => "考公考证", "type_name" => "考公考证"],
|
||||
["type_id" => "平面设计教学", "type_name" => "平面设计教学"],
|
||||
["type_id" => "软件教程", "type_name" => "软件教程"],
|
||||
["type_id" => "Windows", "type_name" => "Windows"]
|
||||
];
|
||||
|
||||
return ['class' => $classes];
|
||||
}
|
||||
|
||||
// homeVideoContent - 首页推荐视频
|
||||
public function homeVideoContent() {
|
||||
$url = 'https://api.bilibili.com/x/web-interface/popular';
|
||||
$data = $this->httpRequest($url, ['ps' => 20, 'pn' => 1]);
|
||||
|
||||
$videos = [];
|
||||
if (isset($data['data']['list'])) {
|
||||
foreach ($data['data']['list'] as $item) {
|
||||
$videos[] = [
|
||||
'vod_id' => $item['aid'],
|
||||
'vod_name' => strip_tags($item['title']),
|
||||
'vod_pic' => $item['pic'],
|
||||
'vod_remarks' => $this->formatDuration($item['duration'])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return ['list' => $videos];
|
||||
}
|
||||
|
||||
// categoryContent - 分类内容(使用搜索API)
|
||||
public function categoryContent($tid, $page, $filters = []) {
|
||||
$page = max(1, intval($page));
|
||||
|
||||
$url = 'https://api.bilibili.com/x/web-interface/search/type';
|
||||
$params = [
|
||||
'search_type' => 'video',
|
||||
'keyword' => $tid,
|
||||
'page' => $page
|
||||
];
|
||||
|
||||
$data = $this->httpRequest($url, $params);
|
||||
|
||||
$videos = [];
|
||||
if (isset($data['data']['result'])) {
|
||||
foreach ($data['data']['result'] as $item) {
|
||||
if ($item['type'] !== 'video') continue;
|
||||
|
||||
$videos[] = [
|
||||
'vod_id' => $item['aid'],
|
||||
'vod_name' => strip_tags($item['title']),
|
||||
'vod_pic' => 'https:' . $item['pic'],
|
||||
'vod_remarks' => $this->formatSearchDuration($item['duration'])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$pageCount = $data['data']['numPages'] ?? 1;
|
||||
$total = $data['data']['numResults'] ?? count($videos);
|
||||
|
||||
return [
|
||||
'list' => $videos,
|
||||
'page' => $page,
|
||||
'pagecount' => $pageCount,
|
||||
'limit' => 20,
|
||||
'total' => $total
|
||||
];
|
||||
}
|
||||
|
||||
// detailContent - 视频详情
|
||||
public function detailContent($vid) {
|
||||
$url = 'https://api.bilibili.com/x/web-interface/view';
|
||||
$data = $this->httpRequest($url, ['aid' => $vid]);
|
||||
|
||||
if (!isset($data['data'])) {
|
||||
return ['list' => []];
|
||||
}
|
||||
|
||||
$video = $data['data'];
|
||||
|
||||
// 构建播放列表
|
||||
$playUrl = '';
|
||||
foreach ($video['pages'] as $index => $page) {
|
||||
$part = $page['part'] ?: '第' . ($index + 1) . '集';
|
||||
$duration = $this->formatDuration($page['duration']);
|
||||
$playUrl .= "{$part}\${$vid}_{$page['cid']}#";
|
||||
}
|
||||
|
||||
$vod = [
|
||||
"vod_id" => $vid,
|
||||
"vod_name" => strip_tags($video['title']),
|
||||
"vod_pic" => $video['pic'],
|
||||
"vod_content" => $video['desc'],
|
||||
"vod_play_from" => "B站视频",
|
||||
"vod_play_url" => rtrim($playUrl, '#')
|
||||
];
|
||||
|
||||
return ['list' => [$vod]];
|
||||
}
|
||||
|
||||
// playContent - 播放地址(高清优化)
|
||||
public function playContent($vid) {
|
||||
if (strpos($vid, '_') !== false) {
|
||||
list($avid, $cid) = explode('_', $vid);
|
||||
} else {
|
||||
return $this->errorResponse('无效的视频ID格式');
|
||||
}
|
||||
|
||||
// 使用高质量参数
|
||||
$url = 'https://api.bilibili.com/x/player/playurl';
|
||||
$params = [
|
||||
'avid' => $avid,
|
||||
'cid' => $cid,
|
||||
'qn' => 112, // 原画质量
|
||||
'fnval' => 0,
|
||||
];
|
||||
|
||||
$data = $this->httpRequest($url, $params);
|
||||
|
||||
if (!isset($data['data']) || $data['code'] !== 0) {
|
||||
return $this->errorResponse('获取播放地址失败');
|
||||
}
|
||||
|
||||
// 直接返回第一个播放地址
|
||||
if (isset($data['data']['durl'][0]['url'])) {
|
||||
$playUrl = $data['data']['durl'][0]['url'];
|
||||
|
||||
$headers = $this->header;
|
||||
$headers['Referer'] = 'https://www.bilibili.com/video/av' . $avid;
|
||||
$headers['Origin'] = 'https://www.bilibili.com';
|
||||
|
||||
return [
|
||||
'parse' => 0,
|
||||
'url' => $playUrl,
|
||||
'header' => $headers,
|
||||
'danmaku' => "https://api.bilibili.com/x/v1/dm/list.so?oid={$cid}"
|
||||
];
|
||||
}
|
||||
|
||||
return $this->errorResponse('无法获取播放地址');
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
private function formatDuration($seconds) {
|
||||
if ($seconds <= 0) return '00:00';
|
||||
$minutes = floor($seconds / 60);
|
||||
$secs = $seconds % 60;
|
||||
return sprintf('%02d:%02d', $minutes, $secs);
|
||||
}
|
||||
|
||||
private function formatSearchDuration($duration) {
|
||||
$parts = explode(':', $duration);
|
||||
if (count($parts) === 2) {
|
||||
return $duration;
|
||||
}
|
||||
return '00:00';
|
||||
}
|
||||
|
||||
private function errorResponse($message) {
|
||||
return [
|
||||
'parse' => 0,
|
||||
'url' => '',
|
||||
'error' => $message
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取请求参数
|
||||
$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) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
$spider = new BiliBiliSpider();
|
||||
|
||||
try {
|
||||
// ============================================================================
|
||||
// 首页/分类接口
|
||||
// ============================================================================
|
||||
if ($filter !== null) {
|
||||
echo json_encode([
|
||||
'class' => [
|
||||
["type_id" => "沙雕仙逆", "type_name" => "傻屌仙逆"],
|
||||
["type_id" => "沙雕动画", "type_name" => "沙雕动画"],
|
||||
["type_id" => "纪录片超清", "type_name" => "纪录片"],
|
||||
["type_id" => "演唱会超清", "type_name" => "演唱会"],
|
||||
["type_id" => "音乐超清", "type_name" => "流行音乐"],
|
||||
["type_id" => "美食超清", "type_name" => "美食"],
|
||||
["type_id" => "食谱", "type_name" => "食谱"],
|
||||
["type_id" => "体育超清", "type_name" => "体育"],
|
||||
["type_id" => "球星", "type_name" => "球星"],
|
||||
["type_id" => "中小学教育", "type_name" => "教育"],
|
||||
["type_id" => "幼儿教育", "type_name" => "幼儿教育"],
|
||||
["type_id" => "旅游", "type_name" => "旅游"],
|
||||
["type_id" => "风景4K", "type_name" => "风景"],
|
||||
["type_id" => "说案", "type_name" => "说案"],
|
||||
["type_id" => "知名UP主", "type_name" => "知名UP主"],
|
||||
["type_id" => "探索发现超清", "type_name" => "探索发现"],
|
||||
["type_id" => "鬼畜", "type_name" => "鬼畜"],
|
||||
["type_id" => "搞笑超清", "type_name" => "搞笑"],
|
||||
["type_id" => "儿童超清", "type_name" => "儿童"],
|
||||
["type_id" => "动物世界超清", "type_name" => "动物世界"],
|
||||
["type_id" => "相声小品超清", "type_name" => "相声小品"],
|
||||
["type_id" => "戏曲", "type_name" => "戏曲"],
|
||||
["type_id" => "解说", "type_name" => "解说"],
|
||||
["type_id" => "演讲", "type_name" => "演讲"],
|
||||
["type_id" => "小姐姐超清", "type_name" => "小姐姐"],
|
||||
["type_id" => "荒野求生超清", "type_name" => "荒野求生"],
|
||||
["type_id" => "健身", "type_name" => "健身"],
|
||||
["type_id" => "帕梅拉", "type_name" => "帕梅拉"],
|
||||
["type_id" => "太极拳", "type_name" => "太极拳"],
|
||||
["type_id" => "广场舞", "type_name" => "广场舞"],
|
||||
["type_id" => "舞蹈", "type_name" => "舞蹈"],
|
||||
["type_id" => "音乐", "type_name" => "音乐"],
|
||||
["type_id" => "歌曲", "type_name" => "歌曲"],
|
||||
["type_id" => "MV4K", "type_name" => "MV"],
|
||||
["type_id" => "舞曲超清", "type_name" => "舞曲"],
|
||||
["type_id" => "4K", "type_name" => "4K"],
|
||||
["type_id" => "电影", "type_name" => "电影"],
|
||||
["type_id" => "电视剧", "type_name" => "电视剧"],
|
||||
["type_id" => "白噪音超清", "type_name" => "白噪音"],
|
||||
["type_id" => "考公考证", "type_name" => "考公考证"],
|
||||
["type_id" => "平面设计教学", "type_name" => "平面设计教学"],
|
||||
["type_id" => "软件教程", "type_name" => "软件教程"],
|
||||
["type_id" => "Windows", "type_name" => "Windows"]
|
||||
],
|
||||
'filters' => [
|
||||
'1001' => [
|
||||
[
|
||||
'key' => 'sort',
|
||||
'name' => '排序',
|
||||
'value' => [
|
||||
['n' => '最新', 'v' => 'new'],
|
||||
['n' => '最热', 'v' => 'hot'],
|
||||
['n' => '推荐', 'v' => 'recommend']
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 首页视频内容
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && empty($t) && empty($ids)) {
|
||||
$result = $spider->homeContent();
|
||||
$videoResult = $spider->homeVideoContent();
|
||||
$result['list'] = $videoResult['list'];
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 分类列表接口
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && $t !== null) {
|
||||
$filters = !empty($extData) ? $extData : [];
|
||||
echo json_encode($spider->categoryContent($t, $pg, $filters), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 详情接口
|
||||
// ============================================================================
|
||||
if ($ac === 'detail' && $ids !== null) {
|
||||
echo json_encode($spider->detailContent($ids), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 播放解析接口
|
||||
// ============================================================================
|
||||
if ($flag !== null && $play !== null) {
|
||||
// 解析B站视频地址
|
||||
if (strpos($play, 'bilibili.com') !== false) {
|
||||
// 对于B站地址,直接返回(TVBox会自行解析)
|
||||
echo json_encode([
|
||||
'parse' => 0,
|
||||
'url' => $play,
|
||||
'header' => [
|
||||
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36',
|
||||
'Referer' => 'https://www.bilibili.com/'
|
||||
]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
// 使用spider的playContent方法解析内部格式
|
||||
echo json_encode($spider->playContent($play), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 搜索接口(简单实现)
|
||||
// ============================================================================
|
||||
if ($wd !== null) {
|
||||
echo json_encode($spider->categoryContent($wd, $pg), JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['error' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
260
scripts/山有木兮.php
Normal file
260
scripts/山有木兮.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
/**
|
||||
* 山有木兮 - PHP 适配版
|
||||
* 按照麻雀视频的结构重写
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
error_reporting(0);
|
||||
|
||||
// ================= 全局配置 =================
|
||||
$HOST = 'https://film.symx.club';
|
||||
$UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36';
|
||||
|
||||
// ================= 核心函数 =================
|
||||
|
||||
function fetch($url, $referer = '/') {
|
||||
global $HOST, $UA;
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'User-Agent: ' . $UA,
|
||||
'Accept: application/json, text/plain, */*',
|
||||
'Accept-Language: zh-CN,zh;q=0.9',
|
||||
'Cache-Control: no-cache',
|
||||
'Pragma: no-cache',
|
||||
'Sec-Ch-Ua: "Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
|
||||
'Sec-Ch-Ua-Mobile: ?0',
|
||||
'Sec-Ch-Ua-Platform: "Windows"',
|
||||
'Sec-Fetch-Dest: empty',
|
||||
'Sec-Fetch-Mode: cors',
|
||||
'Sec-Fetch-Site: same-origin',
|
||||
'X-Platform: web',
|
||||
'Referer: ' . $HOST . $referer
|
||||
]);
|
||||
$data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $data;
|
||||
}
|
||||
|
||||
// ================= 路由逻辑 =================
|
||||
|
||||
$ac = $_GET['ac'] ?? null;
|
||||
$t = $_GET['t'] ?? null; // 分类ID
|
||||
$pg = $_GET['pg'] ?? '1';
|
||||
$wd = $_GET['wd'] ?? null;
|
||||
$ids = $_GET['ids'] ?? null;
|
||||
$play = $_GET['play'] ?? null; // 格式: lineId
|
||||
|
||||
// 1. 播放解析 (对应JS的play函数)
|
||||
if ($play !== null) {
|
||||
$lineId = $play;
|
||||
$url = $HOST . "/api/line/play/parse?lineId=" . urlencode($lineId);
|
||||
$data = fetch($url, '/');
|
||||
$json = json_decode($data, true);
|
||||
|
||||
$playUrl = $json['data'] ?? '';
|
||||
|
||||
echo json_encode([
|
||||
'parse' => 0,
|
||||
'url' => $playUrl,
|
||||
'header' => ['User-Agent' => $UA]
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. 视频详情 (对应JS的detail函数)
|
||||
if (!empty($ids)) {
|
||||
$id = $ids;
|
||||
$url = $HOST . "/api/film/detail?id=" . urlencode($id);
|
||||
$data = fetch($url, '/');
|
||||
$json = json_decode($data, true);
|
||||
|
||||
if (!isset($json['data'])) {
|
||||
echo json_encode(['list' => []], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$data = $json['data'];
|
||||
$shows = [];
|
||||
$play_urls = [];
|
||||
|
||||
// 处理播放线路
|
||||
foreach ($data['playLineList'] as $line) {
|
||||
$shows[] = $line['playerName'];
|
||||
$urls = [];
|
||||
foreach ($line['lines'] as $episode) {
|
||||
$urls[] = $episode['name'] . '$' . $episode['id'];
|
||||
}
|
||||
$play_urls[] = implode('#', $urls);
|
||||
}
|
||||
|
||||
$vod = [
|
||||
'vod_id' => $ids,
|
||||
'vod_name' => $data['name'],
|
||||
'vod_pic' => $data['cover'],
|
||||
'vod_year' => $data['year'],
|
||||
'vod_area' => $data['other'],
|
||||
'vod_actor' => $data['actor'],
|
||||
'vod_director' => $data['director'],
|
||||
'vod_content' => $data['blurb'],
|
||||
'vod_score' => $data['doubanScore'],
|
||||
'vod_play_from' => implode('$$$', $shows),
|
||||
'vod_play_url' => implode('$$$', $play_urls),
|
||||
'type_name' => $data['vod_class'] ?? ''
|
||||
];
|
||||
|
||||
echo json_encode(['list' => [$vod]], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. 搜索 (对应JS的search函数)
|
||||
if (!empty($wd)) {
|
||||
$pageNum = intval($pg);
|
||||
if ($pageNum < 1) $pageNum = 1;
|
||||
|
||||
$url = $HOST . "/api/film/search?keyword=" . urlencode($wd) . "&pageNum={$pageNum}&pageSize=10";
|
||||
$data = fetch($url, '/');
|
||||
$json = json_decode($data, true);
|
||||
|
||||
$list = [];
|
||||
if (isset($json['data']['list'])) {
|
||||
foreach ($json['data']['list'] as $item) {
|
||||
$list[] = [
|
||||
'vod_id' => strval($item['id']),
|
||||
'vod_name' => $item['name'],
|
||||
'vod_pic' => $item['cover'],
|
||||
'vod_remarks' => $item['updateStatus'],
|
||||
'vod_year' => $item['year'],
|
||||
'vod_area' => $item['area'],
|
||||
'vod_director' => $item['director']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['list' => $list, 'page' => $pageNum], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. 分类列表 (对应JS的category函数)
|
||||
if (!empty($t)) {
|
||||
$tid = $t;
|
||||
$pageNum = intval($pg);
|
||||
if ($pageNum < 1) $pageNum = 1;
|
||||
|
||||
$url = $HOST . "/api/film/category/list?area=&categoryId={$tid}&language=&pageNum={$pageNum}&pageSize=15&sort=updateTime&year=";
|
||||
$data = fetch($url, '/');
|
||||
$json = json_decode($data, true);
|
||||
|
||||
$list = [];
|
||||
if (isset($json['data']['list'])) {
|
||||
foreach ($json['data']['list'] as $item) {
|
||||
$list[] = [
|
||||
'vod_id' => strval($item['id']),
|
||||
'vod_name' => $item['name'],
|
||||
'vod_pic' => $item['cover'],
|
||||
'vod_remarks' => $item['updateStatus']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['list' => $list, 'page' => $pageNum], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5. 首页推荐 (对应JS的homeVod函数)
|
||||
if ($ac === 'homeVod') {
|
||||
$url = $HOST . "/api/film/category";
|
||||
$data = fetch($url, '/');
|
||||
$json = json_decode($data, true);
|
||||
|
||||
$list = [];
|
||||
foreach ($json['data'] as $category) {
|
||||
$filmList = $category['filmList'] ?? [];
|
||||
foreach ($filmList as $film) {
|
||||
$list[] = [
|
||||
'vod_id' => strval($film['id']),
|
||||
'vod_name' => $film['name'],
|
||||
'vod_pic' => $film['cover'],
|
||||
'vod_remarks' => $film['doubanScore'] ?? ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 限制数量
|
||||
$list = array_slice($list, 0, 30);
|
||||
|
||||
echo json_encode(['list' => $list], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 6. 首页分类 (对应JS的home函数)
|
||||
if ($ac === 'home') {
|
||||
$url = $HOST . "/api/category/top";
|
||||
$data = fetch($url, '/');
|
||||
$json = json_decode($data, true);
|
||||
|
||||
$classes = [];
|
||||
foreach ($json['data'] as $item) {
|
||||
$classes[] = [
|
||||
'type_id' => strval($item['id']),
|
||||
'type_name' => $item['name']
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode(['class' => $classes], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 7. 初始化 (对应JS的init函数)
|
||||
if ($ac === 'init') {
|
||||
$ext = $_GET['ext'] ?? '';
|
||||
if (!empty($ext) && strpos($ext, 'http') === 0) {
|
||||
$HOST = rtrim($ext, '/');
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 8. 默认首页 (返回分类和推荐)
|
||||
$url = $HOST . "/api/category/top";
|
||||
$data = fetch($url, '/');
|
||||
$json = json_decode($data, true);
|
||||
|
||||
$classes = [];
|
||||
foreach ($json['data'] as $item) {
|
||||
$classes[] = [
|
||||
'type_id' => strval($item['id']),
|
||||
'type_name' => $item['name']
|
||||
];
|
||||
}
|
||||
|
||||
// 获取推荐视频
|
||||
$url2 = $HOST . "/api/film/category";
|
||||
$data2 = fetch($url2, '/');
|
||||
$json2 = json_decode($data2, true);
|
||||
|
||||
$list = [];
|
||||
foreach ($json2['data'] as $category) {
|
||||
$filmList = $category['filmList'] ?? [];
|
||||
foreach ($filmList as $film) {
|
||||
$list[] = [
|
||||
'vod_id' => strval($film['id']),
|
||||
'vod_name' => $film['name'],
|
||||
'vod_pic' => $film['cover'],
|
||||
'vod_remarks' => $film['doubanScore'] ?? ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$list = array_slice($list, 0, 30);
|
||||
|
||||
echo json_encode([
|
||||
'class' => $classes,
|
||||
'list' => $list
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
170
scripts/演示.php
Normal file
170
scripts/演示.php
Normal file
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
// 示例PHP爬虫脚本 - 兼容Apple CMS API格式
|
||||
// 支持二级分类功能和自定义样式
|
||||
//
|
||||
// 二级分类实现说明:
|
||||
// 1. 当返回的JSON根级包含 'is_sub' => true 时,表示当前列表是二级分类
|
||||
// 2. 二级分类项目会被标记为文件夹图标,点击后会重新调用categoryContent
|
||||
// 3. 点击二级分类时,会在筛选参数f中包含 'is_sub' => 'true'
|
||||
// 4. 根据is_sub参数判断是返回二级分类还是实际内容
|
||||
//
|
||||
// 自定义样式说明:
|
||||
// 1. style字段应放在JSON最外层,会应用到所有列表项
|
||||
// 2. type: 'rect'(矩形), 'oval'(椭圆), 'round'(圆形)
|
||||
// 3. ratio: 宽高比例,如1.5表示3:2,0.67表示2:3
|
||||
//
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// 使用Apple CMS标准参数
|
||||
$ac = $_GET['ac'] ?? 'detail'; // 操作类型
|
||||
$t = $_GET['t'] ?? ''; // 类型ID
|
||||
$pg = $_GET['pg'] ?? '1'; // 页码
|
||||
$f = $_GET['f'] ?? ''; // 筛选条件JSON
|
||||
$ids = $_GET['ids'] ?? ''; // 详情ID
|
||||
$wd = $_GET['wd'] ?? ''; // 搜索关键词
|
||||
$flag = $_GET['flag'] ?? ''; // 播放标识
|
||||
$id = $_GET['id'] ?? ''; // 播放ID
|
||||
|
||||
switch ($ac) {
|
||||
case 'detail':
|
||||
if (!empty($ids)) {
|
||||
// 视频详情
|
||||
$data = [
|
||||
'list' => [
|
||||
[
|
||||
'vod_id' => $ids,
|
||||
'vod_name' => '测试视频详情',
|
||||
'vod_pic' => 'https://2uspicc12tche.hitv.app/350/upload/vod/20240415-1/2636d5210e5cf7a6f0cff5c737e6c7b5.webp',
|
||||
'vod_content' => '这是一个测试视频的详细描述',
|
||||
'vod_play_from' => '清凉妹子$$$线路2',
|
||||
'vod_play_url' => '第1集$12767287836095919439#第2集$https://example.com/play2.m3u8$$$第1集$https://example2.com/play1.m3u8'
|
||||
]
|
||||
]
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
} elseif (!empty($t)) {
|
||||
// 分类列表
|
||||
$filters = !empty($f) ? json_decode($f, true) : [];
|
||||
|
||||
// 检查是否请求二级分类
|
||||
$isSubRequest = isset($filters['is_sub']) && $filters['is_sub'] === 'true';
|
||||
|
||||
if ($isSubRequest) {
|
||||
// 二级分类:返回实际内容
|
||||
$data = [
|
||||
'list' => [
|
||||
['vod_id' => 'sub_1', 'vod_name' => '二级内容1', 'vod_pic' => 'https://img9.doubanio.com/view/photo/m_ratio_poster/public/p2578045524.jpg'],
|
||||
['vod_id' => 'sub_2', 'vod_name' => '二级内容2', 'vod_pic' => 'https://img3.doubanio.com/view/photo/m_ratio_poster/public/p2921303452.jpg']
|
||||
],
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 5,
|
||||
'limit' => 20,
|
||||
'total' => 100,
|
||||
// 可选:为二级内容设置不同的样式
|
||||
'style' => [
|
||||
'type' => 'oval',
|
||||
'ratio' => 0.75
|
||||
]
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
} elseif ($t == '1') {
|
||||
// 演示:电影分类包含二级分类,设置根级is_sub=true
|
||||
$data = [
|
||||
'is_sub' => true, // 标识这是二级分类列表
|
||||
'list' => [
|
||||
['vod_id' => 'movie_action', 'vod_name' => '动作片', 'vod_pic' => 'https://img9.doubanio.com/view/photo/m_ratio_poster/public/p2578045524.jpg'],
|
||||
['vod_id' => 'movie_comedy', 'vod_name' => '喜剧片', 'vod_pic' => 'https://img3.doubanio.com/view/photo/m_ratio_poster/public/p2921303452.jpg']
|
||||
],
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 1,
|
||||
'limit' => 20,
|
||||
'total' => 2,
|
||||
// 文件夹样式:适合二级分类导航
|
||||
'style' => [
|
||||
'type' => 'rect',
|
||||
'ratio' => 2.0
|
||||
]
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
// 普通分类列表
|
||||
$data = [
|
||||
'list' => [
|
||||
['vod_id' => '1', 'vod_name' => '清凉视频', 'vod_pic' => 'https://2uspicc12tche.hitv.app/350/upload/vod/20240415-1/2636d5210e5cf7a6f0cff5c737e6c7b5.webp'],
|
||||
['vod_id' => '2', 'vod_name' => '测试视频2', 'vod_pic' => 'https://img3.doubanio.com/view/photo/m_ratio_poster/public/p2921303452.jpg']
|
||||
],
|
||||
'filters' => [
|
||||
'2' => [
|
||||
['key' => 'class', 'name' => '类型', 'value' => [
|
||||
['n' => '全部', 'v' => ''],
|
||||
['n' => '动作', 'v' => '动作'],
|
||||
['n' => '喜剧', 'v' => '喜剧']
|
||||
]]
|
||||
]
|
||||
],
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 10,
|
||||
'limit' => 20,
|
||||
'total' => 200,
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
} else {
|
||||
// 首页分类
|
||||
$data = [
|
||||
'class' => [
|
||||
['type_id' => '1', 'type_name' => '电影'],
|
||||
['type_id' => '2', 'type_name' => '电视剧'],
|
||||
['type_id' => '3', 'type_name' => '综艺'],
|
||||
['type_id' => '4', 'type_name' => '动漫']
|
||||
],
|
||||
'list' => [
|
||||
['vod_id' => 'home_1', 'vod_name' => '首页推荐视频1', 'vod_pic' => 'https://img9.doubanio.com/view/photo/m_ratio_poster/public/p2578045524.jpg', 'vod_remarks' => '演示备注1'],
|
||||
['vod_id' => 'home_2', 'vod_name' => '首页推荐视频2', 'vod_pic' => 'https://img3.doubanio.com/view/photo/m_ratio_poster/public/p2921303452.jpg', 'vod_remarks' => '演示备注2']
|
||||
],
|
||||
'filters' => [
|
||||
'1' => [
|
||||
['key' => 'class', 'name' => '类型', 'value' => [
|
||||
['n' => '全部', 'v' => ''],
|
||||
['n' => '动作', 'v' => '动作'],
|
||||
['n' => '喜剧', 'v' => '喜剧']
|
||||
]]
|
||||
]
|
||||
],
|
||||
// 自定义样式(可选)- 在最外层定义
|
||||
'style' => [
|
||||
'type' => 'rect',
|
||||
'ratio' => 1.33
|
||||
]
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
$data = [
|
||||
'list' => [
|
||||
['vod_id' => 's1', 'vod_name' => '搜索结果: ' . $wd, 'vod_pic' => 'https://2uspicc12tche.hitv.app/350/upload/vod/20240415-1/2636d5210e5cf7a6f0cff5c737e6c7b5.webp']
|
||||
],
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 1,
|
||||
'limit' => 20,
|
||||
'total' => 1
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
case 'play':
|
||||
$data = [
|
||||
'parse' => 1, // 0=直接播放, 1=需要解析
|
||||
'playUrl' => '', // 如果 parse=0,这里填实际播放地址
|
||||
'url' => 'https://haokan.baidu.com/v?vid=' . $id // 如果 parse=1,这里填需要解析的地址
|
||||
];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
|
||||
default:
|
||||
$data = ['error' => 'Unknown action: ' . $ac];
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
?>
|
||||
120
scripts/演示class.php
Normal file
120
scripts/演示class.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
// PHP爬虫脚本
|
||||
// 使用类封装接口,每个方法对应一个操作,方法头有中文注释
|
||||
|
||||
class DemoSpider {
|
||||
|
||||
/**
|
||||
* 首页内容
|
||||
* @return JSON
|
||||
*/
|
||||
public function homeContent() {
|
||||
$data = [
|
||||
'class' => [
|
||||
['type_id' => '1', 'type_name' => '电影'],
|
||||
['type_id' => '2', 'type_name' => '电视剧']
|
||||
],
|
||||
'list' => [
|
||||
['vod_id' => 'h1', 'vod_name' => '首页视频1', 'vod_pic' => 'https://example.com/home1.jpg'],
|
||||
['vod_id' => 'h2', 'vod_name' => '首页视频2', 'vod_pic' => 'https://example.com/home2.jpg']
|
||||
],
|
||||
'style' => [ 'type' => 'rect', 'ratio' => 1.33 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 二级分类内容
|
||||
* @param string $t 分类ID
|
||||
* @param string $subId 二级分类ID
|
||||
*/
|
||||
public function subCategory($t, $subId) {
|
||||
$data = [
|
||||
'list' => [
|
||||
['vod_id' => $t . '_' . $subId . '_1', 'vod_name' => '二级分类内容1', 'vod_pic' => 'https://example.com/sub1.jpg'],
|
||||
['vod_id' => $t . '_' . $subId . '_2', 'vod_name' => '二级分类内容2', 'vod_pic' => 'https://example.com/sub2.jpg']
|
||||
],
|
||||
'style' => [ 'type' => 'oval', 'ratio' => 0.75 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类内容
|
||||
* @param string $t 分类ID
|
||||
* @param int $pg 页码
|
||||
* @param string $f 筛选条件
|
||||
* @param string $extend 扩展参数
|
||||
*/
|
||||
public function categoryContent($t, $pg, $f, $extend) {
|
||||
if ($t == '1') {
|
||||
$data = [
|
||||
'is_sub' => true,
|
||||
'list' => [
|
||||
['vod_id' => 'movie_action', 'vod_name' => '动作片', 'vod_pic' => 'https://example.com/action.jpg'],
|
||||
['vod_id' => 'movie_comedy', 'vod_name' => '喜剧片', 'vod_pic' => 'https://example.com/comedy.jpg']
|
||||
],
|
||||
'style' => [ 'type' => 'rect', 'ratio' => 2.0 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$data = [
|
||||
'list' => [
|
||||
['vod_id' => $t . '_c1', 'vod_name' => '分类视频1', 'vod_pic' => 'https://example.com/cat1.jpg'],
|
||||
['vod_id' => $t . '_c2', 'vod_name' => '分类视频2', 'vod_pic' => 'https://example.com/cat2.jpg']
|
||||
],
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 5,
|
||||
'limit' => 20,
|
||||
'total' => 100,
|
||||
'style' => [ 'type' => 'round', 'ratio' => 1.0 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情内容
|
||||
* @param string $ids 视频ID
|
||||
*/
|
||||
public function detailContent($ids) {
|
||||
$data = [
|
||||
'list' => [[
|
||||
'vod_id' => $ids,
|
||||
'vod_name' => '详情: ' . $ids,
|
||||
'vod_pic' => 'https://example.com/detail.jpg',
|
||||
'vod_content' => '这是 ' . $ids . ' 的详细描述'
|
||||
]]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索内容
|
||||
* @param string $wd 搜索关键词
|
||||
* @param bool $quick 是否快速搜索
|
||||
*/
|
||||
public function searchContent($wd, $quick) {
|
||||
$data = [
|
||||
'list' => [[
|
||||
'vod_id' => 's_' . $wd,
|
||||
'vod_name' => '搜索结果: ' . $wd,
|
||||
'vod_pic' => 'https://example.com/search.jpg'
|
||||
]]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放内容
|
||||
* @param string $id 视频ID
|
||||
*/
|
||||
public function playerContent($id) {
|
||||
$data = [
|
||||
'parse' => 1,
|
||||
'url' => 'https://haokan.baidu.com/v?vid=' . $id
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
117
scripts/演示function.php
Normal file
117
scripts/演示function.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
// PHP爬虫脚本
|
||||
// 每个函数对应一个接口,参数在函数头有中文注释
|
||||
|
||||
/**
|
||||
* 首页内容
|
||||
* @return JSON
|
||||
*/
|
||||
function homeContent() {
|
||||
$data = [
|
||||
'class' => [
|
||||
['type_id' => '1', 'type_name' => '电影'],
|
||||
['type_id' => '2', 'type_name' => '电视剧']
|
||||
],
|
||||
'list' => [
|
||||
['vod_id' => 'h1', 'vod_name' => '首页视频1', 'vod_pic' => 'https://example.com/home1.jpg'],
|
||||
['vod_id' => 'h2', 'vod_name' => '首页视频2', 'vod_pic' => 'https://example.com/home2.jpg']
|
||||
],
|
||||
'style' => [ 'type' => 'rect', 'ratio' => 1.33 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 二级分类内容
|
||||
* @param string $t 分类ID
|
||||
* @param string $subId 二级分类ID
|
||||
*/
|
||||
function subCategory($t, $subId) {
|
||||
$data = [
|
||||
'list' => [
|
||||
['vod_id' => $t . '_' . $subId . '_1', 'vod_name' => '二级分类内容1', 'vod_pic' => 'https://example.com/sub1.jpg'],
|
||||
['vod_id' => $t . '_' . $subId . '_2', 'vod_name' => '二级分类内容2', 'vod_pic' => 'https://example.com/sub2.jpg']
|
||||
],
|
||||
'style' => [ 'type' => 'oval', 'ratio' => 0.75 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类内容
|
||||
* @param string $t 分类ID
|
||||
* @param int $pg 页码
|
||||
* @param string $f 筛选条件
|
||||
* @param string $extend 扩展参数
|
||||
*/
|
||||
function categoryContent($t, $pg, $f, $extend) {
|
||||
if ($t == '1') {
|
||||
$data = [
|
||||
'is_sub' => true,
|
||||
'list' => [
|
||||
['vod_id' => 'movie_action', 'vod_name' => '动作片', 'vod_pic' => 'https://example.com/action.jpg'],
|
||||
['vod_id' => 'movie_comedy', 'vod_name' => '喜剧片', 'vod_pic' => 'https://example.com/comedy.jpg']
|
||||
],
|
||||
'style' => [ 'type' => 'rect', 'ratio' => 2.0 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$data = [
|
||||
'list' => [
|
||||
['vod_id' => $t . '_c1', 'vod_name' => '分类视频1', 'vod_pic' => 'https://example.com/cat1.jpg'],
|
||||
['vod_id' => $t . '_c2', 'vod_name' => '分类视频2', 'vod_pic' => 'https://example.com/cat2.jpg']
|
||||
],
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 5,
|
||||
'limit' => 20,
|
||||
'total' => 100,
|
||||
'style' => [ 'type' => 'round', 'ratio' => 1.0 ]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情内容
|
||||
* @param string $ids 视频ID
|
||||
*/
|
||||
function detailContent($ids) {
|
||||
$data = [
|
||||
'list' => [[
|
||||
'vod_id' => $ids,
|
||||
'vod_name' => '详情: ' . $ids,
|
||||
'vod_pic' => 'https://example.com/detail.jpg',
|
||||
'vod_content' => '这是 ' . $ids . ' 的详细描述'
|
||||
]]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索内容
|
||||
* @param string $wd 搜索关键词
|
||||
* @param bool $quick 是否快速搜索
|
||||
*/
|
||||
function searchContent($wd, $quick) {
|
||||
$data = [
|
||||
'list' => [[
|
||||
'vod_id' => 's_' . $wd,
|
||||
'vod_name' => '搜索结果: ' . $wd,
|
||||
'vod_pic' => 'https://example.com/search.jpg'
|
||||
]]
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放内容
|
||||
* @param string $id 视频ID
|
||||
*/
|
||||
function playerContent($id) {
|
||||
$data = [
|
||||
'parse' => 1,
|
||||
'url' => 'https://haokan.baidu.com/v?vid=' . $id
|
||||
];
|
||||
return json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
?>
|
||||
326
scripts/牛二.php
Normal file
326
scripts/牛二.php
Normal file
@@ -0,0 +1,326 @@
|
||||
<?php
|
||||
// 设置时区为上海(东八区)
|
||||
date_default_timezone_set('Asia/Shanghai');
|
||||
|
||||
// 快速排序.php - TVBox 站点排序删除管理(最终纯净版)
|
||||
// ✅ 左滑 >1/3 宽度 → 自动删除(无确认)
|
||||
// ✅ 删除后立即上移
|
||||
// ✅ 重置 = 删除缓存 + 重新拉取
|
||||
// ✅ 保存后自动刷新
|
||||
// ✅ 来源:天机阁
|
||||
// ✅ 时间:北京时间(东八区)
|
||||
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
|
||||
// ========== 处理 ?reset=1 ==========
|
||||
if (isset($_GET['reset']) && $_GET['reset'] === '1') {
|
||||
@unlink(__DIR__ . '/cache/niu.json');
|
||||
header('Location: ' . strtok($_SERVER['REQUEST_URI'], '?'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// ========== 配置 ==========
|
||||
$urls = [
|
||||
"https://9280.kstore.vip/newwex.json",
|
||||
];
|
||||
$cache_file = __DIR__ . '/cache/niu.json';
|
||||
$cache_ttl = 300;
|
||||
|
||||
// 创建缓存目录
|
||||
if (!is_dir(dirname($cache_file))) {
|
||||
@mkdir(dirname($cache_file), 0755, true);
|
||||
}
|
||||
|
||||
// ========== 辅助函数 ==========
|
||||
function fetch_json($url) {
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_URL => trim($url),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 5,
|
||||
CURLOPT_CONNECTTIMEOUT => 1.5,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_SSL_VERIFYHOST => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_USERAGENT => 'okhttp/3.15',
|
||||
]);
|
||||
$data = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($code >= 200 && $code < 400 && $data) {
|
||||
$json = json_decode($data, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && !empty($json['sites'])) {
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function get_sites_data() {
|
||||
global $urls, $cache_file, $cache_ttl;
|
||||
if (file_exists($cache_file) && (time() - filemtime($cache_file)) <= $cache_ttl) {
|
||||
$data = json_decode(file_get_contents($cache_file), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && !empty($data['sites'])) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($urls as $i => $url) {
|
||||
if ($data = fetch_json($url)) {
|
||||
$data['_source'] = '天机阁';
|
||||
$data['_fetched_at'] = date('Y-m-d H:i:s');
|
||||
file_put_contents($cache_file, json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
return ['sites' => [], '_source' => '天机阁', '_fetched_at' => date('Y-m-d H:i:s')];
|
||||
}
|
||||
|
||||
// ========== UA 检测:TVBox 直接返回 JSON ==========
|
||||
$ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
if (stripos($ua, 'okhttp') !== false || stripos($ua, 'TVBox') !== false || stripos($ua, 'FongMi') !== false) {
|
||||
$data = get_sites_data();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
echo json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ========== 保存处理 ==========
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save') {
|
||||
$order = json_decode($_POST['order'], true);
|
||||
$all_sites = json_decode($_POST['all_sites'], true);
|
||||
|
||||
if (!$order || !$all_sites) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['success' => false, 'message' => '❌ 数据格式错误']);
|
||||
exit; }
|
||||
|
||||
$new_sites = [];
|
||||
foreach ($order as $key) {
|
||||
if (isset($all_sites[$key])) {
|
||||
$new_sites[] = $all_sites[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$original_data = get_sites_data();
|
||||
$output = array_merge($original_data, [
|
||||
'sites' => $new_sites,
|
||||
'_custom_sorted' => true,
|
||||
'_sorted_at' => date('Y-m-d H:i:s'),
|
||||
'_source' => '天机阁'
|
||||
]);
|
||||
|
||||
if (file_put_contents($cache_file, json_encode($output, JSON_UNESCAPED_UNICODE)) !== false) {
|
||||
echo json_encode(['success' => true, 'message' => '✅ 保存成功!' . count($new_sites) . '个站点已更新']);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'message' => '❌ 保存失败!检查 cache 目录权限']);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
$data = get_sites_data();
|
||||
$sites = $data['sites'] ?? [];
|
||||
$source_info = $data['_source'] ?? '天机阁';
|
||||
$fetched_at = $data['_fetched_at'] ?? date('Y-m-d H:i:s');
|
||||
|
||||
$sites_map = [];
|
||||
foreach ($sites as $site) {
|
||||
$key = $site['key'] ?? md5(json_encode($site));
|
||||
$sites_map[$key] = $site;
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>📺 TVBox 站点排序删除管理</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#333;padding:env(safe-area-inset-top) env(safe-area-inset-right) 90px env(safe-area-inset-left);min-height:100vh}
|
||||
.header{background:white;padding:15px 20px;text-align:center;box-shadow:0 2px 15px rgba(0,0,0,0.1);position:sticky;top:0;z-index:100;border-radius:0 0 16px 16px}
|
||||
.header h1{font-size:22px;font-weight:700;color:#4361ee;display:flex;align-items:center;justify-content:center;gap:8px}
|
||||
.stats{display:flex;justify-content:space-around;margin-top:8px;font-size:13px;color:#64748b;background:#f8fafc;border-radius:12px;padding:8px 0} .list-container{background:white;border-radius:20px;margin:15px;box-shadow:0 5px 25px rgba(0,0,0,0.12);max-height:calc(100vh - 230px);overflow-y:auto}
|
||||
.list-header{padding:14px 20px;background:linear-gradient(to right,#4361ee,#3a0ca3);color:white;font-weight:600;font-size:16px;border-radius:18px 18px 0 0;display:flex;justify-content:space-between;align-items:center}
|
||||
.item{position:relative;display:flex;align-items:center;padding:16px 20px;border-bottom:1px solid #f1f5f9;background:#fff;touch-action:pan-y;overflow:hidden;cursor:grab}
|
||||
.item:last-child{border-bottom:none}
|
||||
.item-content{flex:1;min-width:0;padding-right:10px}
|
||||
.name{font-size:17px;color:#1e293b;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.item-delete{position:absolute;right:-60px;top:0;height:100%;width:60px;background:linear-gradient(135deg,#ff6b6b,#ee5a5a);color:white;display:flex;align-items:center;justify-content:center;font-size:28px;font-weight:700;z-index:10;transition:right 0.2s ease;pointer-events:none}
|
||||
.action-bar{position:fixed;bottom:0;left:0;right:0;background:white;padding:12px 15px env(safe-area-inset-bottom);box-shadow:0 -3px 20px rgba(0,0,0,0.15);display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;z-index:200}
|
||||
.main-btn{height:52px;border-radius:14px;border:none;font-size:17px;font-weight:600;color:white;display:flex;align-items:center;justify-content:center;gap:6px;box-shadow:0 3px 12px rgba(0,0,0,0.15);touch-action:manipulation;transition:all 0.2s}
|
||||
.save-btn{background:linear-gradient(to right,#4361ee,#3a0ca3)}
|
||||
.refresh-btn{background:linear-gradient(to right,#4cc9f0,#4361ee)}
|
||||
.restore-btn{background:linear-gradient(to right,#7209b7,#480ca8)}
|
||||
.toast{position:fixed;bottom:90px;left:50%;transform:translateX(-50%) translateY(100px);background:white;color:#1e293b;padding:14px 28px;border-radius:50px;box-shadow:0 5px 25px rgba(0,0,0,0.25);font-size:17px;font-weight:500;z-index:1000;opacity:0;transition:all 0.3s cubic-bezier(0.23,1,0.32,1);text-align:center;max-width:85%}
|
||||
.toast.show{transform:translateX(-50%) translateY(0);opacity:1}
|
||||
.toast.success{background:linear-gradient(to right,#10b981,#0da271);color:white}
|
||||
.toast.error{background:linear-gradient(to right,#ef4444,#dc2626);color:white}
|
||||
.empty-state{text-align:center;padding:40px 20px;color:#94a3b8}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>📺 TVBox站点排序删除管理</h1>
|
||||
<div class="stats">
|
||||
<div>📍 来源: <strong><?= htmlspecialchars($source_info) ?></strong></div>
|
||||
<div>⏰ <?= date('H:i') ?></div> <!-- 使用当前北京时间 -->
|
||||
<div>📊 <strong id="count"><?= count($sites) ?></strong>个</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-container">
|
||||
<div class="list-header">
|
||||
<span>↑ 拖拽排序左滑删除 (<span id="visible-count"><?= count($sites) ?></span>)</span>
|
||||
<span style="background:rgba(255,255,255,0.2);padding:2px 8px;border-radius:10px;font-size:13px">URL#1 → URL#2</span>
|
||||
</div>
|
||||
<div id="list">
|
||||
<?php if (empty($sites)): ?>
|
||||
<div class="empty-state">
|
||||
<div style="font-size:48px;margin-bottom:10px">📭</div>
|
||||
<div>暂无站点数据</div>
|
||||
<div>点击【重置】获取最新数据</div>
|
||||
</div>
|
||||
<?php else: foreach ($sites as $site):
|
||||
$key = htmlspecialchars($site['key'] ?? '');
|
||||
$name = htmlspecialchars($site['name'] ?? $key);
|
||||
?>
|
||||
<div class="item" data-key="<?= $key ?>" draggable="true">
|
||||
<div class="item-content"><div class="name"><?= $name ?></div></div>
|
||||
<div class="item-delete">×</div>
|
||||
</div>
|
||||
<?php endforeach; endif; ?> </div>
|
||||
</div>
|
||||
|
||||
<div class="action-bar">
|
||||
<button class="main-btn refresh-btn" onclick="refreshData()"><span>🔄</span> 刷新</button>
|
||||
<button class="main-btn restore-btn" onclick="restoreAll()"><span>↺</span> 重置</button>
|
||||
<button class="main-btn save-btn" onclick="saveData()"><span>💾</span> 保存</button>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
const allSites = <?= json_encode($sites_map, JSON_UNESCAPED_UNICODE) ?>;
|
||||
const list = document.getElementById('list');
|
||||
|
||||
// ========== 桌面拖拽 ==========
|
||||
document.querySelectorAll('.item').forEach(item => {
|
||||
item.addEventListener('dragstart', () => setTimeout(() => item.classList.add('dragging'), 0));
|
||||
item.addEventListener('dragend', () => item.classList.remove('dragging'));
|
||||
item.addEventListener('dragover', e => e.preventDefault());
|
||||
item.addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
const dragged = document.querySelector('.dragging');
|
||||
if (dragged && dragged !== item) {
|
||||
const rect = item.getBoundingClientRect();
|
||||
const mid = rect.top + rect.height / 2;
|
||||
if (e.clientY < mid) {
|
||||
item.parentNode.insertBefore(dragged, item);
|
||||
} else {
|
||||
item.parentNode.insertBefore(dragged, item.nextSibling);
|
||||
}
|
||||
updateVisibleCount();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ========== 手机触摸:左滑删除 + 立即上移 ==========
|
||||
let activeItem = null;
|
||||
list.addEventListener('touchstart', e => {
|
||||
const item = e.target.closest('.item');
|
||||
if (item && e.touches.length === 1) {
|
||||
activeItem = item;
|
||||
item.dataset.startX = e.touches[0].clientX;
|
||||
document.body.style.touchAction = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
list.addEventListener('touchmove', e => {
|
||||
if (activeItem && e.touches.length === 1) {
|
||||
const dx = e.touches[0].clientX - activeItem.dataset.startX; if (Math.abs(dx) > 10) {
|
||||
e.preventDefault();
|
||||
activeItem.querySelector('.item-delete').style.right = dx < 0 ? `-${Math.min(-dx, 60)}px` : '-60px';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
list.addEventListener('touchend', e => {
|
||||
if (activeItem) {
|
||||
const dx = e.changedTouches[0].clientX - activeItem.dataset.startX;
|
||||
if (dx < -40) {
|
||||
const name = activeItem.querySelector('.name').textContent || '未知站点';
|
||||
activeItem.remove();
|
||||
updateVisibleCount();
|
||||
// 强制重排(关键!)
|
||||
list.parentElement.style.pointerEvents = 'none';
|
||||
void list.parentElement.offsetHeight;
|
||||
list.parentElement.style.pointerEvents = 'auto';
|
||||
showToast(`🗑️ 已删除: ${name}`, 'success');
|
||||
} else {
|
||||
activeItem.querySelector('.item-delete').style.right = '-60px';
|
||||
}
|
||||
activeItem = null;
|
||||
document.body.style.touchAction = '';
|
||||
}
|
||||
});
|
||||
|
||||
// ========== 功能 ==========
|
||||
function saveData() {
|
||||
const items = document.querySelectorAll('.item');
|
||||
if (items.length === 0) return showToast('⚠️ 无站点可保存', 'error');
|
||||
|
||||
const order = Array.from(items).map(el => el.dataset.key);
|
||||
const btn = document.querySelector('.save-btn');
|
||||
btn.innerHTML = '<span>⏳</span> 保存中...';
|
||||
btn.disabled = true;
|
||||
|
||||
fetch(location.href, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `action=save&order=${encodeURIComponent(JSON.stringify(order))}&all_sites=${encodeURIComponent(JSON.stringify(allSites))}`
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showToast(data.message, 'success');
|
||||
setTimeout(() => location.reload(), 1200);
|
||||
} else {
|
||||
showToast(data.message, 'error');
|
||||
} })
|
||||
.catch(err => showToast('❌ 保存失败: ' + err.message, 'error'))
|
||||
.finally(() => {
|
||||
btn.innerHTML = '<span>💾</span> 保存';
|
||||
btn.disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
if (!confirm('确定刷新?将覆盖当前编辑内容!')) return;
|
||||
location.href = location.href.split('?')[0] + '?t=' + Date.now();
|
||||
}
|
||||
|
||||
function restoreAll() {
|
||||
if (!confirm('确定重置?\n将删除缓存并重新拉取最新数据!')) return;
|
||||
location.href = location.href.split('?')[0] + '?reset=1';
|
||||
}
|
||||
|
||||
function updateVisibleCount() {
|
||||
const n = document.querySelectorAll('.item').length;
|
||||
document.getElementById('visible-count').textContent = n;
|
||||
document.getElementById('count').textContent = n;
|
||||
}
|
||||
|
||||
function showToast(msg, type = 'success') {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.className = `toast ${type} show`;
|
||||
setTimeout(() => t.classList.remove('show'), 2800);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
9
scripts/自动壁纸更换php/img.php
Normal file
9
scripts/自动壁纸更换php/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]");
|
||||
?>
|
||||
BIN
scripts/自动壁纸更换php/img/b02.jpg
Normal file
BIN
scripts/自动壁纸更换php/img/b02.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
scripts/自动壁纸更换php/img/g01.jpg
Normal file
BIN
scripts/自动壁纸更换php/img/g01.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
scripts/自动壁纸更换php/img/r01.jpg
Normal file
BIN
scripts/自动壁纸更换php/img/r01.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
4
scripts/自动壁纸更换php/说明.txt
Normal file
4
scripts/自动壁纸更换php/说明.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
传到根目录运行img.php即可,img文件夹存放jpg格式的图片。可用于猫壁纸自动切换。
|
||||
|
||||
|
||||
$:Wen
|
||||
131
scripts/色epg.php
Normal file
131
scripts/色epg.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
/**
|
||||
* EPG 节目表获取插件 - 最终全兼容修复版
|
||||
*/
|
||||
|
||||
error_reporting(0);
|
||||
date_default_timezone_set('Asia/Taipei');
|
||||
|
||||
$CHANNELS = [
|
||||
// 凯擘大宽频(kbro)
|
||||
"松视1" => "kbro@@297",
|
||||
"松视2" => "kbro@@298",
|
||||
"松视3" => "kbro@@299",
|
||||
"松视4" => "kbro@@300",
|
||||
"彩虹电影" => "kbro@@292",
|
||||
"彩虹E" => "kbro@@291",
|
||||
"彩虹K" => "kbro@@293",
|
||||
"潘朵啦玩美" => "kbro@@904",
|
||||
"潘朵啦粉红" => "kbro@@905",
|
||||
"惊艳" => "kbro@@906",
|
||||
"香蕉成人" => "kbro@@907",
|
||||
"极限电影" => "kbro@@912",
|
||||
// 中嘉宽频(homeplus)
|
||||
"彩虹" => "homeplus@@730@@301",
|
||||
"松视4" => "homeplus@@730@@302",
|
||||
"潘朵啦玩美" => "homeplus@@730@@303",
|
||||
"潘朵啦粉红" => "homeplus@@730@@304",
|
||||
"松视1" => "homeplus@@730@@305",
|
||||
"松视2" => "homeplus@@730@@306",
|
||||
"松视3" => "homeplus@@730@@307",
|
||||
"彩虹E" => "homeplus@@730@@308",
|
||||
"彩虹MOVIE" => "homeplus@@730@@309",
|
||||
"彩虹K" => "homeplus@@730@@310",
|
||||
"HOT" => "homeplus@@730@@311",
|
||||
"HAPPY" => "homeplus@@730@@312",
|
||||
"玩家" => "homeplus@@730@@313",
|
||||
"惊艳成人电影" => "homeplus@@730@@314",
|
||||
"香蕉成人" => "homeplus@@730@@315",
|
||||
"乐活" => "homeplus@@730@@316",
|
||||
];
|
||||
|
||||
$id = $_GET['id'] ?? $_GET['ch'] ?? '';
|
||||
$date = $_GET['date'] ?? date("Y-m-d");
|
||||
$format = isset($_GET['id']) ? 1 : 2;
|
||||
|
||||
if (!$id) {
|
||||
header('Content-Type: application/json');
|
||||
exit(json_encode(["code" => 500, "msg" => "参数缺失"]));
|
||||
}
|
||||
|
||||
function matchChannel($query, $channels) {
|
||||
$cleaner = function($str) {
|
||||
$str = strtoupper(urldecode($str));
|
||||
$map = ["視"=>"视", "樂"=>"乐", "驚"=>"惊", "艷"=>"艳", "電影"=>"电影", "頻"=>"频", "極"=>"极"];
|
||||
$str = strtr($str, $map);
|
||||
$junk = ['JSTAR', 'KBRO', 'HOMEPLUS', '高清', 'HD', '频道', '頻道', '台', '电影', '電影', ' '];
|
||||
return str_replace($junk, '', $str);
|
||||
};
|
||||
|
||||
$input = $cleaner($query);
|
||||
foreach ($channels as $key => $val) {
|
||||
$target = $cleaner($key);
|
||||
if ($input !== '' && (strpos($input, $target) !== false || strpos($target, $input) !== false)) {
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = matchChannel($id, $CHANNELS);
|
||||
|
||||
if (!$config) {
|
||||
header('Content-Type: application/json');
|
||||
exit(json_encode(["code" => 500, "msg" => "未匹配"]));
|
||||
}
|
||||
|
||||
$params = explode("@@", $config);
|
||||
$source = $params[0];
|
||||
|
||||
function epg_kbro($tvid, $date) {
|
||||
$url = "https://www.kbro.com.tw/do/getpage_catvtable.php?action=get_channelprogram&channelid=$tvid&showtime=" . date('Ymd', strtotime($date));
|
||||
$res = json_decode(curl_request($url), true);
|
||||
$list = [];
|
||||
foreach (($res['data'] ?? []) as $row) {
|
||||
$list[] = ["title" => $row["programname"], "start" => date("H:i", strtotime($row["starttime"])), "end" => date("H:i", strtotime($row["endtime"]))];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
function epg_homeplus($so, $channelid, $date) {
|
||||
$url = "https://www.homeplus.net.tw/cable/Product_introduce/digital_tv/get_channel_content";
|
||||
$res = json_decode(curl_request($url, ['so' => $so, 'channelid' => $channelid]), true);
|
||||
$list = [];
|
||||
foreach (($res['date_program'][$date] ?? []) as $programs) {
|
||||
foreach ($programs as $p) {
|
||||
$list[] = ["title" => $p['name'], "start" => date("H:i", strtotime($p['beginTime']))];
|
||||
}
|
||||
}
|
||||
for ($i = 0; $i < count($list); $i++) {
|
||||
$list[$i]['end'] = ($i < count($list) - 1) ? $list[$i+1]['start'] : "00:00";
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
function curl_request($url, $post = null) {
|
||||
$ch = curl_init($url);
|
||||
if ($post) {
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
|
||||
}
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_USERAGENT => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
]);
|
||||
$res = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $res;
|
||||
}
|
||||
|
||||
$list = ($source === 'kbro') ? epg_kbro($params[1], $date) : epg_homeplus($params[1], $params[2], $date);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
if ($format === 1) {
|
||||
$data = [];
|
||||
foreach ($list as $row) { $data[] = ["name" => $row["title"], "starttime" => $row["start"]]; }
|
||||
echo json_encode(["code" => $list ? 200 : 500, "name" => $id, "date" => $date, "data" => $data], JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
echo json_encode(["code" => $list ? 200 : 500, "channel_name" => $id, "date" => $date, "epg_data" => $list], JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
318
scripts/色色.php
Normal file
318
scripts/色色.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
scripts/虎牙直播.php
Normal file
402
scripts/虎牙直播.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();
|
||||
}
|
||||
272
scripts/金牌.php
Normal file
272
scripts/金牌.php
Normal file
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* 金牌动漫 - PHP 适配版
|
||||
* 1. [核心] 参照 cat_金牌.js 逻辑,实现 MD5+SHA1 嵌套签名
|
||||
* 2. [适配] 完整支持分类、筛选、详情、搜索及播放
|
||||
* 3. [架构] 采用“嗷呜动漫”模板结构
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
error_reporting(0);
|
||||
|
||||
// ================= 全局配置 =================
|
||||
$HOST = 'https://m.jiabaide.cn';
|
||||
$UA = 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';
|
||||
|
||||
// ================= 工具函数 =================
|
||||
|
||||
/**
|
||||
* 核心签名算法:sha1(md5(query_string))
|
||||
*/
|
||||
function getHeaders($params) {
|
||||
global $UA, $HOST;
|
||||
$t = (string)(time() * 1000); // 毫秒时间戳
|
||||
$params['key'] = 'cb808529bae6b6be45ecfab29a4889bc';
|
||||
$params['t'] = $t;
|
||||
|
||||
// 构建 QueryString
|
||||
$query = [];
|
||||
foreach ($params as $k => $v) {
|
||||
$query[] = "$k=$v";
|
||||
}
|
||||
$queryStr = implode('&', $query);
|
||||
|
||||
// 签名逻辑:SHA1(MD5(str))
|
||||
$sign = sha1(md5($queryStr));
|
||||
|
||||
return [
|
||||
'User-Agent: ' . $UA,
|
||||
'Referer: ' . $HOST,
|
||||
't: ' . $t,
|
||||
'sign: ' . $sign
|
||||
];
|
||||
}
|
||||
|
||||
function fetch($url, $headers = []) {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $data;
|
||||
}
|
||||
|
||||
// ================= 参数接收 =================
|
||||
$ac = $_GET['ac'] ?? null;
|
||||
$t = $_GET['t'] ?? null;
|
||||
$pg = $_GET['pg'] ?? '1';
|
||||
$ids = $_GET['ids'] ?? null;
|
||||
$wd = $_GET['wd'] ?? null;
|
||||
$play = $_GET['play'] ?? null;
|
||||
$ext = $_GET['ext'] ?? null;
|
||||
|
||||
// ================= 路由逻辑 =================
|
||||
|
||||
// 1. 播放解析
|
||||
if ($play !== null) {
|
||||
// 格式: vodId@nid
|
||||
list($sid, $nid) = explode('@', $play);
|
||||
$params = [
|
||||
'clientType' => '3',
|
||||
'id' => $sid,
|
||||
'nid' => $nid
|
||||
];
|
||||
$apiUrl = $HOST . '/api/mw-movie/anonymous/v2/video/episode/url?' . http_build_query($params);
|
||||
$res = fetch($apiUrl, getHeaders($params));
|
||||
$json = json_decode($res, true);
|
||||
|
||||
$playUrl = "";
|
||||
if (!empty($json['data']['list'])) {
|
||||
// 取第一个清晰度的 URL
|
||||
$playUrl = $json['data']['list'][0]['url'];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'parse' => 0,
|
||||
'url' => $playUrl,
|
||||
'header' => ['User-Agent' => $UA]
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. 视频详情
|
||||
if (!empty($ids)) {
|
||||
$params = ['id' => $ids];
|
||||
$apiUrl = $HOST . '/api/mw-movie/anonymous/video/detail?' . http_build_query($params);
|
||||
$res = fetch($apiUrl, getHeaders($params));
|
||||
$json = json_decode($res, true);
|
||||
$kvod = $json['data'];
|
||||
|
||||
$episodes = [];
|
||||
if (!empty($kvod['episodeList'])) {
|
||||
foreach ($kvod['episodeList'] as $it) {
|
||||
// 存入格式:名字$ID@NID
|
||||
$episodes[] = $it['name'] . '$' . $kvod['vodId'] . '@' . $it['nid'];
|
||||
}
|
||||
}
|
||||
|
||||
$vod = [
|
||||
'vod_id' => $kvod['vodId'],
|
||||
'vod_name' => $kvod['vodName'],
|
||||
'vod_pic' => $kvod['vodPic'],
|
||||
'type_name' => $kvod['vodClass'],
|
||||
'vod_remarks' => $kvod['vodRemarks'],
|
||||
'vod_content' => trim(strip_tags($kvod['vodContent'])),
|
||||
'vod_play_from' => '金牌线路',
|
||||
'vod_play_url' => implode('#', $episodes)
|
||||
];
|
||||
|
||||
echo json_encode(['list' => [$vod]], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. 搜索
|
||||
if (!empty($wd)) {
|
||||
$params = [
|
||||
'keyword' => $wd,
|
||||
'pageNum' => $pg,
|
||||
'pageSize' => '30'
|
||||
];
|
||||
$apiUrl = $HOST . '/api/mw-movie/anonymous/video/searchByWordPageable?' . http_build_query($params);
|
||||
$res = fetch($apiUrl, getHeaders($params));
|
||||
$json = json_decode($res, true);
|
||||
|
||||
$list = [];
|
||||
foreach ($json['data']['list'] as $it) {
|
||||
$list[] = [
|
||||
'vod_id' => $it['vodId'],
|
||||
'vod_name' => $it['vodName'],
|
||||
'vod_pic' => $it['vodPic'],
|
||||
'vod_remarks' => $it['vodRemarks']
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'list' => $list,
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 10,
|
||||
'limit' => 30,
|
||||
'total' => 300
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. 分类列表
|
||||
if ($ac === 'detail' && !empty($t)) {
|
||||
$extend = [];
|
||||
if (!empty($ext)) {
|
||||
$decoded = json_decode(base64_decode($ext), true);
|
||||
if ($decoded) $extend = $decoded;
|
||||
}
|
||||
|
||||
$params = [
|
||||
'area' => $extend['area'] ?? '',
|
||||
'lang' => $extend['lang'] ?? '',
|
||||
'pageNum' => $pg,
|
||||
'pageSize' => '30',
|
||||
'sort' => $extend['by'] ?? '1',
|
||||
'sortBy' => '1',
|
||||
'type' => $extend['type'] ?? '',
|
||||
'type1' => $t,
|
||||
'v_class' => $extend['class'] ?? '',
|
||||
'year' => $extend['year'] ?? '',
|
||||
];
|
||||
|
||||
$apiUrl = $HOST . '/api/mw-movie/anonymous/video/list?' . http_build_query($params);
|
||||
$res = fetch($apiUrl, getHeaders($params));
|
||||
$json = json_decode($res, true);
|
||||
|
||||
$list = [];
|
||||
foreach ($json['data']['list'] as $it) {
|
||||
$list[] = [
|
||||
'vod_id' => $it['vodId'],
|
||||
'vod_name' => $it['vodName'],
|
||||
'vod_pic' => $it['vodPic'],
|
||||
'vod_remarks' => $it['vodRemarks'] . '_' . $it['vodDoubanScore']
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'list' => $list,
|
||||
'page' => intval($pg),
|
||||
'pagecount' => 99,
|
||||
'limit' => 30,
|
||||
'total' => 3000
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5. 首页 (获取分类与筛选)
|
||||
$typeUrl = $HOST . '/api/mw-movie/anonymous/get/filer/type';
|
||||
$typeRes = fetch($typeUrl, getHeaders([]));
|
||||
$typeArr = json_decode($typeRes, true)['data'] ?? [];
|
||||
|
||||
$classes = [];
|
||||
foreach ($typeArr as $item) {
|
||||
$classes[] = ['type_id' => (string)$item['typeId'], 'type_name' => $item['typeName']];
|
||||
}
|
||||
|
||||
// 获取筛选
|
||||
$filterUrl = $HOST . '/api/mw-movie/anonymous/v1/get/filer/list';
|
||||
$filterRes = fetch($filterUrl, getHeaders([]));
|
||||
$filterData = json_decode($filterRes, true)['data'] ?? [];
|
||||
|
||||
$filters = [];
|
||||
$nameMap = [
|
||||
'typeList' => ['key' => 'type', 'name' => '类型'],
|
||||
'plotList' => ['key' => 'class', 'name' => '剧情'],
|
||||
'districtList' => ['key' => 'area', 'name' => '地区'],
|
||||
'languageList' => ['key' => 'lang', 'name' => '语言'],
|
||||
'yearList' => ['key' => 'year', 'name' => '年份']
|
||||
];
|
||||
|
||||
foreach ($classes as $cls) {
|
||||
$tid = $cls['type_id'];
|
||||
$fRow = [];
|
||||
foreach ($nameMap as $apiKey => $cfg) {
|
||||
if (!isset($filterData[$tid][$apiKey])) continue;
|
||||
$values = [['n' => '全部', 'v' => '']];
|
||||
foreach ($filterData[$tid][$apiKey] as $v) {
|
||||
$values[] = [
|
||||
'n' => $v['itemText'],
|
||||
'v' => ($apiKey === 'typeList') ? $v['itemValue'] : $v['itemText']
|
||||
];
|
||||
}
|
||||
$fRow[] = ['key' => $cfg['key'], 'name' => $cfg['name'], 'value' => $values];
|
||||
}
|
||||
// 增加排序
|
||||
$fRow[] = [
|
||||
'key' => 'by', 'name' => '排序',
|
||||
'value' => [
|
||||
['n' => '最近更新', 'v' => '1'],
|
||||
['n' => '添加时间', 'v' => '2'],
|
||||
['n' => '人气高低', 'v' => '3'],
|
||||
['n' => '评分高低', 'v' => '4']
|
||||
]
|
||||
];
|
||||
$filters[$tid] = $fRow;
|
||||
}
|
||||
|
||||
// 首页推荐
|
||||
$hotUrl = $HOST . '/api/mw-movie/anonymous/home/hotSearch';
|
||||
$hotRes = fetch($hotUrl, getHeaders([]));
|
||||
$hotVods = json_decode($hotRes, true)['data'] ?? [];
|
||||
$list = [];
|
||||
foreach (array_slice($hotVods, 0, 20) as $it) {
|
||||
$list[] = [
|
||||
'vod_id' => $it['vodId'],
|
||||
'vod_name' => $it['vodName'],
|
||||
'vod_pic' => $it['vodPic'],
|
||||
'vod_remarks' => $it['vodRemarks']
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'class' => $classes,
|
||||
'filters' => $filters,
|
||||
'list' => $list
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
220
scripts/麻雀视频.php
Normal file
220
scripts/麻雀视频.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* 麻雀视频 - PHP 适配版
|
||||
* 1. 核心:实现了 JS 源码中的 XOR + Base64 算法 (Token 生成与数据解密)
|
||||
* 2. 播放:集成了 JS 中的解密逻辑及多线路解析
|
||||
* 3. 搜索:支持 Token 验证的搜索接口
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
error_reporting(0);
|
||||
|
||||
// ================= 全局配置 =================
|
||||
$HOST = 'https://www.mqtv.cc';
|
||||
$KEY = 'Mcxos@mucho!nmme';
|
||||
$UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36';
|
||||
|
||||
// ================= 核心加解密函数 =================
|
||||
|
||||
/**
|
||||
* 对应 JS 中的 encodeData 和 decodeData (XOR + Base64)
|
||||
*/
|
||||
function mq_xor_codec($data, $key, $is_decode = false) {
|
||||
if ($is_decode) {
|
||||
$data = base64_decode($data);
|
||||
} else {
|
||||
$data = json_encode($data, JSON_UNESCAPED_UNICODE);
|
||||
$data = base64_encode($data);
|
||||
}
|
||||
|
||||
$res = '';
|
||||
$keyLen = strlen($key);
|
||||
for ($i = 0; $i < strlen($data); $i++) {
|
||||
$res .= $data[$i] ^ $key[$i % $keyLen];
|
||||
}
|
||||
|
||||
if ($is_decode) {
|
||||
return json_decode(base64_decode($res), true);
|
||||
} else {
|
||||
return urlencode(base64_encode($res));
|
||||
}
|
||||
}
|
||||
|
||||
function fetch($url, $referer = '/') {
|
||||
global $UA, $HOST;
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'User-Agent: ' . $UA,
|
||||
'Referer: ' . $HOST . $referer,
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
]);
|
||||
$data = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 获取页面 PageID 并生成 Token
|
||||
function getToken($path, $ref = '/') {
|
||||
global $HOST, $KEY;
|
||||
$html = fetch($HOST . $path, $ref);
|
||||
preg_match("/window\.pageid\s?=\s?'(.*?)';/i", $html, $m);
|
||||
$pageId = $m[1] ?? "";
|
||||
return mq_xor_codec($pageId, $KEY);
|
||||
}
|
||||
|
||||
// ================= 路由逻辑 =================
|
||||
|
||||
$ac = $_GET['ac'] ?? null;
|
||||
$t = $_GET['t'] ?? null; // 类型,如 /type/movie
|
||||
$pg = $_GET['pg'] ?? '1';
|
||||
$wd = $_GET['wd'] ?? null;
|
||||
$ids = $_GET['ids'] ?? null;
|
||||
$play = $_GET['play'] ?? null; // 格式: url@parse1,parse2
|
||||
|
||||
// 1. 播放解析
|
||||
if ($play !== null) {
|
||||
$parts = explode('@', $play);
|
||||
$rawUrl = $parts[0];
|
||||
$parses = isset($parts[1]) ? explode(',', $parts[1]) : [];
|
||||
|
||||
// 默认返回第一个解析地址配合嗅探,模拟 JS 中的逻辑
|
||||
$finalUrl = $rawUrl;
|
||||
if (!empty($parses)) {
|
||||
$finalUrl = $parses[0] . $rawUrl;
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'parse' => 1,
|
||||
'url' => $finalUrl,
|
||||
'header' => ['User-Agent' => $UA]
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. 视频详情 (detail)
|
||||
if (!empty($ids)) {
|
||||
$pathParts = explode('/', trim($ids, '/'));
|
||||
$realId = end($pathParts);
|
||||
$token = getToken($ids);
|
||||
|
||||
$apiUrl = $HOST . "/libs/VodInfo.api.php?type=ct&id=$realId&token=$token";
|
||||
$json = json_decode(fetch($apiUrl, $ids), true);
|
||||
$data = $json['data'];
|
||||
|
||||
// 处理解析线路
|
||||
$parsesArr = [];
|
||||
foreach (($data['playapi'] ?? []) as $p) {
|
||||
if (isset($p['url'])) {
|
||||
$parsesArr[] = (strpos($p['url'], '//') === 0) ? "https:" . $p['url'] : $p['url'];
|
||||
}
|
||||
}
|
||||
$parsesStr = implode(',', $parsesArr);
|
||||
|
||||
$playFrom = [];
|
||||
$playUrls = [];
|
||||
foreach (($data['playinfo'] ?? []) as $site) {
|
||||
$playFrom[] = $site['cnsite'];
|
||||
$urls = [];
|
||||
foreach ($site['player'] as $ep) {
|
||||
// 将解析接口封装在 URL 后面,供 play 阶段调用
|
||||
$urls[] = $ep['no'] . '$' . $ep['url'] . '@' . $parsesStr;
|
||||
}
|
||||
$playUrls[] = implode('#', $urls);
|
||||
}
|
||||
|
||||
$vod = [
|
||||
'vod_id' => $ids,
|
||||
'vod_name' => $data['title'],
|
||||
'vod_pic' => $data['img'],
|
||||
'vod_remarks' => $data['remark'],
|
||||
'vod_year' => $data['year'],
|
||||
'vod_area' => $data['area'],
|
||||
'vod_actor' => $data['actor'],
|
||||
'vod_director' => $data['director'],
|
||||
'vod_content' => $data['content'] ?? '',
|
||||
'vod_play_from' => implode('$$$', $playFrom),
|
||||
'vod_play_url' => implode('$$$', $playUrls)
|
||||
];
|
||||
|
||||
echo json_encode(['list' => [$vod]], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. 搜索 (search)
|
||||
if (!empty($wd)) {
|
||||
$path = '/search/' . urlencode($wd);
|
||||
$token = getToken($path);
|
||||
$apiUrl = $HOST . "/libs/VodList.api.php?search=" . urlencode($wd) . "&token=$token";
|
||||
|
||||
$resp = json_decode(fetch($apiUrl, $path), true);
|
||||
$data = mq_xor_codec($resp['data'], $KEY, true); // 搜索数据需要解密
|
||||
|
||||
$list = [];
|
||||
if (isset($data['vod_all'])) {
|
||||
foreach ($data['vod_all'] as $item) {
|
||||
foreach ($item['show'] as $v) {
|
||||
$list[] = [
|
||||
'vod_id' => $v['url'],
|
||||
'vod_name' => $v['title'],
|
||||
'vod_pic' => $v['img'],
|
||||
'vod_remarks' => $v['remark']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
echo json_encode(['list' => $list, 'page' => $pg], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. 分类列表 (category)
|
||||
if (!empty($t)) {
|
||||
$typeKey = explode('/', trim($t, '/'))[1] ?? 'movie';
|
||||
$token = getToken($t);
|
||||
$apiUrl = $HOST . "/libs/VodList.api.php?type=$typeKey&rank=rankhot&page=$pg&token=$token";
|
||||
|
||||
$resp = json_decode(fetch($apiUrl, $t), true);
|
||||
$list = [];
|
||||
foreach (($resp['data'] ?? []) as $v) {
|
||||
$list[] = [
|
||||
'vod_id' => $v['url'],
|
||||
'vod_name' => $v['title'],
|
||||
'vod_pic' => $v['img'],
|
||||
'vod_remarks' => $v['remark']
|
||||
];
|
||||
}
|
||||
echo json_encode(['list' => $list, 'page' => intval($pg)], JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5. 首页 (homeVod)
|
||||
$token = getToken('/');
|
||||
$apiUrl = $HOST . "/libs/VodList.api.php?home=index&token=$token";
|
||||
$resp = json_decode(fetch($apiUrl), true);
|
||||
$list = [];
|
||||
if (isset($resp['data']['movie'])) {
|
||||
foreach ($resp['data']['movie'] as $section) {
|
||||
foreach ($section['show'] as $v) {
|
||||
$list[] = [
|
||||
'vod_id' => $v['url'],
|
||||
'vod_name' => $v['title'],
|
||||
'vod_pic' => $v['img'],
|
||||
'vod_remarks' => $v['remark']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'class' => [
|
||||
['type_id' => '/type/movie', 'type_name' => '电影'],
|
||||
['type_id' => '/type/tv', 'type_name' => '电视剧'],
|
||||
['type_id' => '/type/va', 'type_name' => '综艺'],
|
||||
['type_id' => '/type/ct', 'type_name' => '动漫']
|
||||
],
|
||||
'list' => array_slice($list, 0, 30)
|
||||
], JSON_UNESCAPED_UNICODE);
|
||||
Reference in New Issue
Block a user