forked from tfornik/RussiaTools
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
402 lines
12 KiB
402 lines
12 KiB
<?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();
|
|
} |