<?php
/*
新版本接口 ps 页内数量
https://api.bilibili.com/x/space/arc/search?mid=223146252&ps=30&tid=0&pn=1&keyword=&order=pubdate&jsonp=jsonp
fakedanmu
https://api.bilibili.com/x/v2/dm/ajax?callback=jQuery17208966159722380538_1624984205756&aid=BV1464y197zG&jsonp=jsonp&_=1624984533780
https://api.bilibili.com/x/v2/dm/ajax?aid=BV1464y197zG
*/
// ini_set("display_errors", "On");//打开错误提示
// ini_set("error_reporting", E_ALL);//显示所有错误
date_default_timezone_set('Asia/Shanghai');
//页面展示视频数量
define('SHOW_COUNT', 30);
//一个up主最多获取视频个数
define('SINGLE_LIMIT', 3);
//bilibili关注id
$UP = [
//=====科普学习=====
//李永乐老师
//9458053,
//妈咪说
//223146252,
//张召忠
// 33683045,
//芳斯塔芙
72270557,
//zettaranc
// 326246517,
//表象的本质
//363584502,
//奇闻观察室
454719565,
//硬核的半佛仙人
//37663924,
//毕导THU
254463269,
//智慧君
// 20259914,
//天天鉴宝
429392011,
//戴建业老师
532741557,
//TED超级演讲家
// 359516438,
//苑举正教授
518566904,
//罗翔说刑法
517327498,
//老师好我叫何同学
// 163637592,
//无穷小亮的科普日常
14804670,
//老爸评测
347667464,
//老奇好好奇
35894872,
//=====工作和技术=====
//阿里达摩院扫地僧
//483104470,
//小米公司
23920239,
//小米商城官方
// 60253266,
//小米情报官
383423248,
//=====资讯=====
//央视新闻
456664753,
//秀才侃娱乐
//41288240,
//观察者网
10330740,
//喵喵折App
// 338748561,
//郭杰瑞
176037767,
//=====鬼畜========
//OELoop
6171645,
//倒悬的橘子
40966108,
//=======游戏========
//上海滩许van强
23604445,
//LPL战术小黑板
594534726,
//靠谱电竞->靠谱盘点
279991456,
//STEAM情报局
473519710,
//Steam官方特惠
// 518876755,
//哔哩哔哩英雄联盟赛事
50329118,
//CD_PROJEKT_RED
//271442527,
//罗汉解说
51896064,
//=====杨超越=====
//午夜出动
353407160,
//杨超越视频站
// 107961740,
//杨超越盖世无敌可爱
// 4981142,
//杨超越
// 630836232,
//=====美食=====
//品诺美食开课啦
352637817,
//厨师长农国栋
415479453,
//美食作家王刚
290526283,
//真探唐仁杰
544336675,
//华农兄弟
// 250858633,
//吃货请闭眼
// 94114029,
//盗月社食遇记
//99157282,
//=====数码=====
//科技美学-大家测
346098914,
//科技美学
3766866,
//钟文泽
25910292,
//远古时代装机猿
//35359510,
//极客湾Geekerwan
25876945,
//阿狸才不是受
//453972,
//蒸気火鸡
39180492,
//=====VLOG生活=====
//峰哥亡命天涯
35847683,
//孝警阿特
477531206,
//拜托了小翔哥
353539995,
//麻雀喳喳野行记
120122306,
//敬汉卿
9824766,
//机智的党妹
//466272,
//伢伢gagako
//632887,
//视角姬
//52250,
//大胡子魏
// 438880209,
//=====音乐=====
//bibipiano
// 419743655,
//郎朗piano
// 479893558,
//=====搞笑=====
//手工耿
280793434,
//凉风
14110780,
//朱一旦
//437316738,
//陈翔六点半
19286458,
//惊天碉堡团
// 471870511,
//=====影视解说=====
//导演小策
81824112,
//木鱼水心
927587,
//电影最TOP
17819768,
//哔哩哔哩电影
15773384,
//YouTube精选字幕组
//487511093,
//大象放映室
49246269,
];
class bili{
private $UP;
private $last_bvid;
private $old_file;
private $old_json;
private $new_json;
private $type;
private $up_img;
private $test = false;
//我的关注
const MY_FOLLOW = 'myFollow';
//排行榜
const NOW_RANKING = 'nowRanking';
//每周必看
const WEEK_RANKING = 'weekRanking';
const HOT_RANKING = 'hotRanking';
const TESTING = 'testing';
public function __construct($UP, $type){
$this->UP = $UP;
if(in_array($type, [self::MY_FOLLOW,self::NOW_RANKING,self::WEEK_RANKING,self::HOT_RANKING,self::TESTING])){
$this->type = $type;
}else{
$this->type = self::HOT_RANKING;
}
//加载缓存json文件
if (file_exists($this->getTypePath())){
$this->old_file = file_get_contents($this->getTypePath());
$this->old_json = json_decode($this->old_file, 1);
}else{
$this->old_json['last_time'] = 0;
$this->old_json['weekNumber'] = 0;
$this->old_json['GMT'] = gmdate("D, d M Y H:i:s")." GMT";
}
}
//获取路径
private static function getPath($name){
return dirname(__FILE__) . DIRECTORY_SEPARATOR . $name;
}
//获取路径
private function getTypePath($name = ''){
return dirname(__FILE__) . DIRECTORY_SEPARATOR . 'bili_' . $this->type . '.json';
}
//主流程 外层echo
public function execute(){
if(self::TESTING == $this->type){
return json_encode($_SERVER);
}
if(self::MY_FOLLOW == $this->type){
$this->last_bvid = ($_GET['last_bvid']??0);
$this->up_img = $this->getUPImg();
$this->updateJson();
file_put_contents($this->getTypePath(), json_encode($this->new_json));
return $this->buildResp();
}
if(self::NOW_RANKING == $this->type){
if(file_exists($this->getTypePath()) && !$this->ifUpdateJson(3600)){
$this->checkCacheTime($this->old_json['GMT']);
return $this->old_file;
}
$html = self::curlGet('https://www.bilibili.com/v/popular/rank/all');
$html = explode("<script>window.__INITIAL_STATE__=", $html);
$html = explode(";(function()", $html[1]);
$jsonObj = json_decode($html[0]);
$list = [];
foreach($jsonObj->rankList as $item){
$list[] = self::v_popular_rank_all($item, $item->owner->face);
}
$retData = json_encode($this->buildJson($list));
file_put_contents($this->getTypePath(), $retData);
return $retData;
}
if(self::WEEK_RANKING == $this->type){
//每周五晚 18:00 更新
//相同则不更新
if(file_exists($this->getTypePath()) && $this->old_json['weekNumber'] == self::getWeekNumNow()){
$this->checkCacheTime($this->old_json['GMT']);
return $this->old_file;
}
$jsonObj = self::biliGet('https://api.bilibili.com/x/web-interface/popular/series/one?number=' . self::getWeekNumNow());
$list = [];
foreach($jsonObj->list as $item){
$list[] = self::v_popular_rank_all($item, $item->owner->face);
}
$retData = json_encode($this->buildJson($list, ['weekNumber' => $jsonObj->config->number]));
file_put_contents($this->getTypePath(), $retData);
return $retData;
}
if(self::HOT_RANKING == $this->type){
if(file_exists($this->getTypePath()) && !$this->ifUpdateJson(3600)){
$this->checkCacheTime($this->old_json['GMT']);
return $this->old_file;
}
$jsonObj = self::biliGet('https://api.bilibili.com/x/web-interface/popular?ps=50&pn=1');
$list = [];
foreach($jsonObj->list as $item){
$list[] = self::v_popular_rank_all($item, $item->owner->face);
}
$retData = json_encode($this->buildJson($list));
file_put_contents($this->getTypePath(), $retData);
return $retData;
}
return 'fatal';
}
private function checkCacheTime($GMT){
header("Last-Modified: " . $GMT);
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $GMT == $_SERVER['HTTP_IF_MODIFIED_SINCE']){
header("HTTP/1.1 304 Not Modified");
exit();
}
}
//返回值
private function buildResp() : string{
$ret = $this->new_json;
//当前端aid与当前一致,则不需要重写前端页面
if(!empty($this->new_json['list']) && $this->last_bvid == $this->new_json['list'][0]['bvid']){
$ret['ifRefresh'] = false;
}
$ret['ifRefresh'] = true;
return json_encode($ret);
}
//up主头像
private function getUPImg() : array{
$imgSavePath = self::getPath('bili_up_img.json');
//文件缓存
if(file_exists($imgSavePath)){
$ret = json_decode(file_get_contents($imgSavePath),1);
}else{
$ret = [];
}
$update_flag = false;
foreach($this->UP as $item){
if(is_numeric($item)){
//数字配置
$mid = $item;
}else if(is_array($item) && isset($item['mid'])){
//数组配置
$mid = $item['mid'];
}else if(is_array($item) && empty($item['mid'])){
//搜索型配置,指定upId作为头像 暂无搜索型
$mid = $item['upimg_id'];
}
if(isset($ret[$mid])){
//操作存量数据 todo delete
$ret[$mid] = self::getImgName($ret[$mid]);
continue;
}
$update_flag = true;
$url = 'https://api.bilibili.com/x/space/acc/info?mid=' . $mid;
$respObj = self::biliGet($url);
//使用参数设置头像为64*64
$ret[$mid] = self::getImgName($respObj->face);
}
if($update_flag){
file_put_contents($imgSavePath, json_encode($ret));
}
return $ret;
}
//判断是否更新文件
private function ifUpdateJson($timeSec = 600) : bool{
//文件不存在
if($this->old_json['last_time'] == 0){
return true;
}
//文件更新时间超时
if($this->old_json['last_time'] + $timeSec < time()){
return true;
}
return false;
}
//更新json信息,并更新cover图片
private function updateJson(){
if(!$this->ifUpdateJson()){
$this->new_json = $this->old_json;
return;
}
//parse配置信息
$jsonData = [];
foreach($this->UP as $item){
if(is_int($item)){
$jsonData = array_merge($jsonData, $this->upList(['mid'=>$item]));
}else if(is_array($item) && isset($item['mid'])){
$jsonData = array_merge($jsonData, $this->upList($item));
}else if(is_array($item) && empty($item['mid'])){
continue;
}
}
krsort($jsonData);
$jsonData = array_values(array_slice($jsonData, 0, SHOW_COUNT));
$this->new_json = self::buildJson($jsonData);
}
//构造返回值
private static function buildJson(&$list, $extend = []){
$ret = [
'list' => $list,
'last_time_str' => date('Y-m-d H:i:s'),
'last_time' => time(),
'GMT' => gmdate("D, d M Y H:i:s")." GMT",
];
foreach($extend as $key => $val){
$ret[$key] = $val;
}
return $ret;
}
//根据up主id检索视频信息
private function upList($conf) : array{
$param = [
'tid'=>'0',
//最多显示SINGLE_LIMIT个
'ps'=>SINGLE_LIMIT,
//第一页
'pn'=>'1',
//在当前up的投稿中搜索
'keyword'=>isset($conf['keyword']) ? urlencode($conf['keyword']) : '',
//按照时间排序
'order'=>'pubdate',
//up主id
'mid'=>$conf['mid'],
];
$url = 'https://api.bilibili.com/x/space/arc/search?';
foreach($param as $key=>$item2){
$url .= $key . '=' . $item2 . '&';
}
$respData = self::biliGet($url);
$ret = [];
foreach($respData->list->vlist as $item){
$key = sprintf("%s%012d", $item->created, $item->mid);
$ret[$key] = self::x_space_arc_search($item, $this->up_img[$conf['mid']]);
$ret[$key]['mid'] = $conf['mid'];
}
return $ret;
}
//格式化
private static function v_popular_rank_all(&$json,&$head) : array{
return [
'bvid' => $json->bvid,
'title' => $json->title,
'desc' => $json->desc,//\n点赞:{$json->stat->like}\n投币:{$json->stat->coin}\n收藏:{$json->stat->favorite}
'author' => $json->owner->name,
'mid' => $json->owner->mid,
'pic' => self::getImgName($json->pic),
'created' => isset($json->created) ? $json->created : $json->pubdate,
'face' => self::getImgName($head),
];
}
//格式化
private static function x_space_arc_search(&$json,&$head) : array{
return [
'bvid' => $json->bvid,
'title' => $json->title,
'desc' => $json->description,
'author' => $json->author,
'mid' => $json->mid,//可能为合作视频
'pic' => self::getImgName($json->pic),
'created' => isset($json->created) ? $json->created : $json->pubdate,
'face' => self::getImgName($head),
];
}
//获取周数自2019
private static function getWeekNumNow() : int{
$time = time() - strtotime("2019-03-22 18:05:00");
return (int)($time / (3600*24*7));
}
//图片链接
private static function getImgName($url) : string{
return str_replace("http://",'//',$url);
}
//返回json.data
private static function biliGet($url) : object {
$obj = json_decode(self::curlGet($url));
return $obj->data;
}
//请求url
private static function curlGet($url) : string{
//初始化
$ch = curl_init();
//设置选项,包括URL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);//跟随跳转
curl_setopt($ch, CURLOPT_USERAGENT,'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36');
curl_setopt($ch, CURLOPT_REFERER, 'https://www.bilibili.com');
//执行并获取HTML文档内容
$output = curl_exec($ch);
//释放curl句柄
curl_close($ch);
//打印获得的数据
return $output;
}
}
//更新json 并展示
if (!empty($_GET)) {
$obj = new bili($UP, $_GET['type']);
}else{
$obj = new bili($UP, bili::MY_FOLLOW);
}
echo $obj->execute();