1
0
Code Issues Pull Requests Projects Releases Wiki Activity GitHub Gitee
tools/hotband/html/bilibili_rank.html

466 lines
18 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 不携带 referer -->
<meta name="referrer" content="never">
<title>B站排行</title>
<link rel="stylesheet" href="./assets/css/main.css">
<style>
#title {
margin-bottom: 0;
}
#dynamic-note {
color: grey;
margin: 5px auto;
font-size: 13px;
}
td {
font-size: 12px;
max-width: 200px;
word-wrap: break-word;
}
</style>
</head>
<body>
<div style="text-align: center;">
<h1 id="title">B站排行榜</h1>
<p id="dynamic-note"></p>
<hr>
<div style="display: none;">
显示字段:
<label for="show_emoticon">
<input type="checkbox" class="filter_checkbox" name="show_emoticon" id="show_emoticon"
detailed-checked="true">热搜表情
</label>
<label for="show_num">
<input type="checkbox" class="filter_checkbox" name="show_num" id="show_num" checked="true"
concise-checked="true" detailed-checked="true">热度
</label>
<label for="show_category">
<input type="checkbox" class="filter_checkbox" name="show_category" id="show_category" checked="true"
detailed-checked="true">分类
</label>
<label for="show_onboard_time">
<input type="checkbox" class="filter_checkbox" name="show_onboard_time" id="show_onboard_time"
checked="true" detailed-checked="true">上线时间
</label>
<label for="show_is_new">
<input type="checkbox" class="filter_checkbox" name="show_is_new" id="show_is_new"
detailed-checked="true">是否新热搜
</label>
<label for="show_detail">
<input type="checkbox" class="filter_checkbox" name="show_detail" id="show_detail"
detailed-checked="true">热搜详情
</label>
<label for="show_mid">
<input type="checkbox" class="filter_checkbox" name="show_mid" id="show_mid">mid
</label>
<br>
<button id="btn_show_all">全选</button>
<button id="btn_show_none">全不选</button>
|
<button id="btn_show_concise">简洁</button>
<button id="btn_show_default">普通</button>
<button id="btn_show_detailed">详细</button>
</div>
</div>
<p id="latestUpdateTime" style="font-size: 12px; display: inline-block; vertical-align: middle;"></p>
<nobr>
<button id="btn_refresh">重新拉取</button>
</nobr>
<nobr>
<label for="auto_refresh">
<input type="checkbox" name="auto_refresh" id="auto_refresh">自动拉取<span id="auto_refresh_countdown"></span>
</label>
</nobr>
<nobr>
<span id="update-finish-info" style="color: green; font-weight: bold; display: none;">拉取成功,数据已更新</span>
</nobr>
<table id="list"></table>
<div class="bottom-placeholder">
<p>
— 到底啦 —<br>
数据来源: https://www.bilibili.com/v/popular/rank/all
</p>
</div>
<script>
/**
* 全局变量
*/
// 拉取下来的数据
let hotBandData;
// 按钮
const btnShowAll = document.getElementById('btn_show_all');
const btnShowNone = document.getElementById('btn_show_none');
const btnShowConcise = document.getElementById('btn_show_concise');
const btnShowDefault = document.getElementById('btn_show_default');
const btnShowDetailed = document.getElementById('btn_show_detailed');
const btnRefresh = document.getElementById('btn_refresh');
// 复选框
const filterCheckbox = document.getElementsByClassName("filter_checkbox");
const showEmoticon = document.getElementById("show_emoticon");
const showNum = document.getElementById("show_num");
const showCategory = document.getElementById("show_category");
const showOnboardTime = document.getElementById("show_onboard_time");
const showIsNew = document.getElementById("show_is_new");
const showDetail = document.getElementById("show_detail");
const showMid = document.getElementById("show_mid");
const autoRefresh = document.getElementById("auto_refresh");
// 绑定按钮点击事件
btnShowAll.addEventListener('click', function () {
for (let i = 0; i < filterCheckbox.length; i++) {
const element = filterCheckbox[i];
element.checked = true;
}
render();
});
btnShowNone.addEventListener('click', function () {
for (let i = 0; i < filterCheckbox.length; i++) {
const element = filterCheckbox[i];
element.checked = false;
}
render();
});
btnShowConcise.addEventListener('click', function () {
for (let i = 0; i < filterCheckbox.length; i++) {
const element = filterCheckbox[i];
element.checked = element.getAttribute('concise-checked') === 'true';
}
render();
});
btnShowDefault.addEventListener('click', function () {
for (let i = 0; i < filterCheckbox.length; i++) {
const element = filterCheckbox[i];
element.checked = element.getAttribute('checked') === 'true';
}
render();
});
btnShowDetailed.addEventListener('click', function () {
for (let i = 0; i < filterCheckbox.length; i++) {
const element = filterCheckbox[i];
element.checked = element.getAttribute('detailed-checked') === 'true';
}
render();
});
btnRefresh.onclick = function () {
getData();
document.getElementById("update-finish-info").style.display = "";
// btnRefresh.style.display = "none";
btnRefresh.style.visibility = "hidden";
setTimeout(function () {
document.getElementById("update-finish-info").style.display = "none";
// btnRefresh.style.display = "";
btnRefresh.style.visibility = "";
}, 1000);
};
// 绑定复选框改变事件
for (let i = 0; i < filterCheckbox.length; i++) {
// console.log(filterCheckbox[i]);
filterCheckbox[i].onchange = function () {
render();
};
}
let autoRefreshIntreval = null;
const autoRefreshCountDownElement = document.getElementById('auto_refresh_countdown');
const countDown = 20; // 自动拉取间隔时间,单位:秒
let autoRefreshCountDown = countDown;
autoRefresh.onchange = function () {
if (autoRefresh.checked) {
btnRefresh.style.display = "none";
btnRefresh.click();
autoRefreshIntreval = setInterval(function () {
if ((--autoRefreshCountDown) > 0) {
autoRefreshCountDownElement.innerHTML = `(${autoRefreshCountDown}s)`;
} else {
autoRefreshCountDown = countDown;
btnRefresh.click();
autoRefreshCountDownElement.innerHTML = ``;
}
}, 1000);
} else {
clearInterval(autoRefreshIntreval);
btnRefresh.style.display = "";
autoRefreshCountDownElement.innerHTML = ``;
}
};
// 根据屏幕判断要显示哪些字段
// 此时还未拉取数据,所以进入 render 函数会直接返回,不会多次渲染
let initWidth = document.body.offsetWidth;
// console.log(initWidth);
/* if (initWidth < 400) {
btnShowNone.click();
btnShowNone.innerHTML += "(默认)";
} else */ if (initWidth < 600) {
btnShowConcise.click();
btnShowConcise.innerHTML += "(默认)";
} else if (initWidth < 1900) {
btnShowDefault.click();
btnShowDefault.innerHTML += "(默认)";
} else {
btnShowDetailed.click();
btnShowDetailed.innerHTML += "(默认)";
}
// 网页加载后加载榜单
getData();
// 定时刷新
// setInterval(getData, 10 * 1000);
function getData() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "../data/bilibili-rank/latest.json?t=" + Date.now(), true);
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) return;
if (xhr.status == 200) {
try {
hotBandData = JSON.parse(xhr.responseText);
if (!hotBandData.data || typeof hotBandData.data !== 'object')
throw new Error("data is undefined or not an object");
} catch (e) {
console.error("[error]", "\n", e, "\n", "\n", "[xhr.responseText]", "\n", xhr.responseText);
alert("latest.json 文件解析失败,请检查文件");
return;
}
console.log(hotBandData);
// 更新动态 note
document.getElementById("dynamic-note").innerHTML = hotBandData.note;
// 更新时间
document.getElementById("latestUpdateTime").innerHTML =
"数据拉取时间:" + new Date().toLocaleString() + "<br/>" +
"热榜更新时间:" + new Date(hotBandData.update_time).toLocaleString();
// 渲染榜单
render();
} else if (xhr.status == 404) {
alert("data 目录下未找到 latest.json 文件,可能的原因:\n您还没有运行脚本拉取数据请先运行脚本然后刷新页面");
}
}
}
function render() {
if (!hotBandData) return;
/**
* 渲染热搜列表
*/
let hotBandList = hotBandData.data;
var str = [];
// 渲染表格
str.push(`<thead>
<tr class="thead" style="top: 0; background-color: white; position: sticky;">
<td>编号</td>
<td>标题</td>
<td>时长</td>
<td>封面</td>
<td>第一帧</td>
<td>视频ID</td>
<td>分类</td>
<td>发布时间</td>
<td>简介</td>
<td>作者(mid)</td>
<td>统计</td>
<td>视频宽高</td>
<td>定位</td>
<td>videos</td>
<td>tid</td>
<td>copyright</td>
<td>state</td>
<td>rights</td>
<td>dynamic</td>
<td>score</td>
<td>mission_id</td>
<td>season_id</td>
<td>up_from_v2</td>
<td>others</td>
</tr>
</thead>`);
str.push(`<tbody>`);
for (var i = 0; i < hotBandList.length; i++) {
const hotBand = hotBandList[i];
let link = hotBand.short_link == hotBand.short_link_v2
? hotBand.short_link
: `${hotBand.short_link}<br/>${hotBand.short_link_v2}`;
/**
* 视频总秒数转化为友好显示时间
*/
// refer: https://blog.csdn.net/weixin_43838488/article/details/122337474
function formatZero(num, len) {
if (String(num).length > len) {
return num;
}
return (Array(len).join(0) + num).slice(-len)
}
let duration = hotBand.duration - 1; // 根据观测基本上所有视频都是少1s(有些少了2s或者其他)所以这里减1
let durationArr = [0, 0, 0];
durationArr[0] = Math.floor(duration / (60 * 60));
durationArr[1] = Math.floor((duration - durationArr[0] * 60 * 60) / 60);
durationArr[2] = Math.floor(duration - durationArr[0] * 60 * 60 - durationArr[1] * 60);
let durationStr = "";
if (durationArr[0] === 0) {
// 小时为0
durationStr = `${formatZero(durationArr[1], 2)}:${formatZero(durationArr[2], 2)}`;
} else {
// 小时不为0
durationStr = `${formatZero(durationArr[0], 2)}:${formatZero(durationArr[1], 2)}:${formatZero(durationArr[2], 2)}`;
}
//功能:求最大公约数
//参数: x 、y number
//返回值: number
function gcd(x, y) {
if (isNaN(x) || isNaN(y)) return null;
if (x % y === 0) {
return y;
}
return gcd(y, x % y)
//三目运算符写法:
//return x % y === 0 ? y : gcd(y , x % y) ;
}
let dimension_gcd = gcd(hotBand.dimension.width, hotBand.dimension.height);
str.push(`<tr>
<!-- 编号 -->
<td>${i + 1}</td>
<!-- 标题 -->
<td>
<a href="${link}" target="_blank">${hotBand.title}</a>
</td>
<!-- 时长 -->
<td>${durationStr}<br>(${hotBand.duration}s)</td>
<!-- 封面 -->
<td>
<img src="${hotBand.pic}" style="width: 120px;"/>
</td>
<!-- 第一帧 -->
<td>
<img src="${hotBand.first_frame}" style="width: 120px;"/>
</td>
<!-- aid -->
<td style="text-align: left;">
<nobr>aid: ${hotBand.aid}</nobr>
<nobr>bvid: ${hotBand.bvid}</nobr>
<nobr>cid: ${hotBand.cid}</nobr>
</td>
<!-- 分类 -->
<td>${hotBand.tname}</td>
<!-- 发布时间 -->
<td style="font-size: 10px;">
<nobr>pubdate: ${new Date(hotBand.pubdate * 1000).toLocaleString()}</nobr>
<nobr>ctime: ${new Date(hotBand.ctime * 1000).toLocaleString()}</nobr>
</td>
<!-- 简介 -->
<td>${hotBand.desc}</td>
<!-- 作者 -->
<td>
<img src="${hotBand.owner.face}" style="width: 30px;"/><br>
${hotBand.owner.name}<br>
(${hotBand.owner.mid})
<!-- ${JSON.stringify(hotBand.owner)} -->
</td>
<!-- 统计 -->
<td style="text-align: left;">
<nobr>播放: ${hotBand.stat.view}</nobr>
<nobr>弹幕: ${hotBand.stat.danmaku}</nobr>
<nobr>评论: ${hotBand.stat.reply}</nobr>
<nobr>喜欢: ${hotBand.stat.favorite}</nobr>
<nobr>投币: ${hotBand.stat.coin}</nobr>
<nobr>分享: ${hotBand.stat.share}</nobr>
<!--<nobr>当前排名: ${hotBand.stat.now_rank}</nobr>-->
<nobr>历史<!--最高-->排名: ${hotBand.stat.his_rank}</nobr>
<nobr>喜欢数: ${hotBand.stat.like}</nobr>
<nobr>不喜欢数: ${hotBand.stat.dislike}</nobr>
<!-- ${JSON.stringify(hotBand.stat)} -->
</td>
<!-- 视频宽高 -->
<td>
${hotBand.dimension.width}:${hotBand.dimension.height}<br>
(${hotBand.dimension.width / dimension_gcd}:${hotBand.dimension.height / dimension_gcd})
<!-- ${hotBand.dimension.rotate} -->
<!-- ${JSON.stringify(hotBand.dimension)} -->
</td>
<!-- 定位 -->
<td>
<nobr>${hotBand.pub_location}</nobr>
</td>
<!-- videos -->
<td>${hotBand.videos}</td>
<!-- tid -->
<td>${hotBand.tid}</td>
<!-- copyright -->
<td>${hotBand.copyright}</td>
<!-- state -->
<td>${hotBand.state}</td>
<!-- rights -->
<td>${JSON.stringify(hotBand.rights)}</td>
<!-- dynamic -->
<td>${hotBand.dynamic}</td>
<!-- score -->
<td>${hotBand.score}</td>
<!-- mission_id -->
<td>${hotBand.mission_id ? hotBand.mission_id : ''}</td>
<!-- season_id -->
<td>${hotBand.season_id ? hotBand.season_id : ''}</td>
<!-- up_from_v2 -->
<td>${hotBand.up_from_v2 ? hotBand.up_from_v2 : ''}</td>
<!-- others -->
<td>
${hotBand.others
? `<div style="max-height: 200px; overflow: scroll;"><span>${JSON.stringify(hotBand.others)}</span></div>`
: ''}
</td>
</tr >`);
}
str.push(`</tbody>`);
document.getElementById('list').innerHTML = str.join('');
}
</script>
</body>
</html>