Merge brepo 'hot-band'
This commit is contained in:
		
							
								
								
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2022 程序员小墨
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										20
									
								
								hotband/.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								hotband/.env.example
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
# 调试模式
 | 
			
		||||
# 1为开启调试
 | 
			
		||||
DEBUG_MODE=1
 | 
			
		||||
 | 
			
		||||
# 爬取数据保存的文件夹
 | 
			
		||||
# 目录开头与结尾的 [./] [/] [\] [\\] 均可带可不带
 | 
			
		||||
# 默认为 data 文件夹
 | 
			
		||||
DATA_FOLDER=data
 | 
			
		||||
 | 
			
		||||
# 是否在程序刚一启动时就抓取一次数据
 | 
			
		||||
# 1为是
 | 
			
		||||
EXECUTE_AT_STARTUP=1
 | 
			
		||||
 | 
			
		||||
# 数据是否推送到Git仓库
 | 
			
		||||
# 1为是
 | 
			
		||||
PUSH_TO_GIT=0
 | 
			
		||||
 | 
			
		||||
# 是否仅保存 latest.json 而不保存其他文件作为存档
 | 
			
		||||
# 1为是
 | 
			
		||||
LATEST_DATA_ONLY=0
 | 
			
		||||
							
								
								
									
										8
									
								
								hotband/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								hotband/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
.DS_Store
 | 
			
		||||
 | 
			
		||||
data/*
 | 
			
		||||
.env
 | 
			
		||||
node_modules
 | 
			
		||||
.VSCodeCounter
 | 
			
		||||
 | 
			
		||||
test.js
 | 
			
		||||
							
								
								
									
										247
									
								
								hotband/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								hotband/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,247 @@
 | 
			
		||||
# 热搜数据爬取工具
 | 
			
		||||
 | 
			
		||||
> 本仓库中代码仅供学习研究使用,不得用于违法用途,学习使用完毕后请于24小时内删除。
 | 
			
		||||
>
 | 
			
		||||
> 数据来自微博、B站(详见下方「数据来源」),本项目不对数据真实性做验证,使用数据时请遵守相关平台的相关限制要求。
 | 
			
		||||
 | 
			
		||||
## 简介
 | 
			
		||||
 | 
			
		||||
您可以将本项目代码部署在服务器上(在本地运行也可),程序会每隔一分钟拉取一次热搜数据,并保存为 `json` 格式文件。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 数据预览
 | 
			
		||||
 | 
			
		||||
在部署并启动项目后,您可以在浏览器中打开 `html/index.html` 文件实时预览当前热搜。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 数据来源
 | 
			
		||||
 | 
			
		||||
**微博热搜**
 | 
			
		||||
 | 
			
		||||
页面:https://weibo.com/hot/search
 | 
			
		||||
 | 
			
		||||
接口:https://weibo.com/ajax/statuses/hot_band
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**B站热搜**
 | 
			
		||||
 | 
			
		||||
页面:https://www.bilibili.com/blackboard/activity-trending-topic.html
 | 
			
		||||
 | 
			
		||||
接口:https://app.bilibili.com/x/v2/search/trending/ranking
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**B站排行榜**
 | 
			
		||||
 | 
			
		||||
页面:https://www.bilibili.com/v/popular/rank/all
 | 
			
		||||
 | 
			
		||||
接口:https://api.bilibili.com/x/web-interface/ranking/v2?type=all
 | 
			
		||||
 | 
			
		||||
(切换到其他榜单再切换回来会调用此接口)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 运行环境
 | 
			
		||||
 | 
			
		||||
原理上来说 Windows 下和 Linux 都可运行,目前仅在 Windows 下测试过,暂未在 Linux 系统下测试。
 | 
			
		||||
 | 
			
		||||
项目使用 node 开发,以下部署流程默认您已安装了 `Git`、`Nodejs`。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 部署
 | 
			
		||||
 | 
			
		||||
1. 克隆仓库(或直接下载压缩包)
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
git clone https://git.only4.work/coder-xiaomo/weibo-hotband
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
2. 安装依赖
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm i
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
3. 修改配置文件
 | 
			
		||||
 | 
			
		||||
将项目目录下的 `.env.example` 文件复制一份,并改名为 `.env`,使用文本编辑器打开(例如:记事本、VS Code、vim等均可),根据其中的注释说明来进行配置即可。
 | 
			
		||||
 | 
			
		||||
> 如果不创建 .env 文件,项目启动时会报如下错误并退出。
 | 
			
		||||
>
 | 
			
		||||
> ```bash
 | 
			
		||||
> [ERROR] .env file not found!
 | 
			
		||||
> ```
 | 
			
		||||
 | 
			
		||||
4. 启动项目
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# 直接运行
 | 
			
		||||
# node index.js
 | 
			
		||||
 | 
			
		||||
# 使用 pm2
 | 
			
		||||
# pm2 start index.js --name weibo-hotband-bot
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
5. 停止项目
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# 使用 node index.js 命令直接运行的项目可以通过 `Ctrl + C` 停止
 | 
			
		||||
 | 
			
		||||
# 使用 pm2 运行的可以使用以下两行命令来停止和从列表中删除项目
 | 
			
		||||
# pm2 stop weibo-hotband-bot
 | 
			
		||||
# pm2 delete weibo-hotband-bot
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 说明
 | 
			
		||||
 | 
			
		||||
项目爬取的数据默认保存在项目目录下的 data 文件夹中,您也可以通过修改 `.env` 文件中的 `DATA_FOLDER` 参数值来自定义数据保存路径。
 | 
			
		||||
 | 
			
		||||
### 微博热搜榜
 | 
			
		||||
 | 
			
		||||
> 微博热搜在 `weibo_hotband` 子文件夹下
 | 
			
		||||
 | 
			
		||||
在程序运行后,该文件夹下会出现 `latest.json` 文件及其余几个文件夹,这些子文件夹中的文件按照以下格式保存:`年/月/日/年月日_时分.json`。
 | 
			
		||||
 | 
			
		||||
每次爬取后,`latest.json`中的数据都会被覆盖为最新的热搜数据。
 | 
			
		||||
 | 
			
		||||
`origin` 文件夹中的数据是通过Api接口获取到的原始数据,没有经过任何处理。
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
`simplify` 文件夹中的数据是在原始数据的基础上,去除了部分冗余数据。
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
`final` 文件夹中的数据是从原始数据中抽离出的有用数据,并重新整理得到的。
 | 
			
		||||
 | 
			
		||||
<!--
 | 
			
		||||
`regulation` 文件夹中的数据主要用于观测原始值与显示值不同的热搜,这部分热搜猜测可能是经过微博平台调控的。(这部分数据没有太大意义,可以忽略)
 | 
			
		||||
-->
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### B站热搜榜
 | 
			
		||||
 | 
			
		||||
> 微博热搜在 `bilibili_hotband` 子文件夹下
 | 
			
		||||
 | 
			
		||||
在程序运行后,该文件夹下会出现 `latest.json` 文件及其余几个文件夹,这些子文件夹中的文件按照以下格式保存:`年/月/日/年月日_时分.json`。
 | 
			
		||||
 | 
			
		||||
每次爬取后,`latest.json`中的数据都会被覆盖为最新的热搜数据。
 | 
			
		||||
 | 
			
		||||
`origin` 文件夹中的数据是通过Api接口获取到的原始数据,此处仅仅去除了 `trackid`。
 | 
			
		||||
 | 
			
		||||
`final` 文件夹中的数据是从原始数据中抽离出的有用数据,并重新整理得到的。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### B站排行榜
 | 
			
		||||
 | 
			
		||||
> 微博热搜在 `bilibili_rank` 子文件夹下
 | 
			
		||||
 | 
			
		||||
在程序运行后,该文件夹下会出现 `latest.json` 文件及其余几个文件夹,这些子文件夹中的文件按照以下格式保存:`年/月/日/年月日_时分.json`。
 | 
			
		||||
 | 
			
		||||
每次爬取后,`latest.json`中的数据都会被覆盖为最新的热搜数据。
 | 
			
		||||
 | 
			
		||||
`origin` 文件夹中的数据是通过Api接口获取到的原始数据,没有经过任何处理。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 目录结构
 | 
			
		||||
 | 
			
		||||
### 项目目录结构
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
hotband                                      // 本项目
 | 
			
		||||
├─ data                                      // 爬取的数据(启动项目后自动创建)
 | 
			
		||||
├─ html                                      // html 页面
 | 
			
		||||
│  ├─ assets
 | 
			
		||||
│  │  ├─ css                                 // CSS 样式
 | 
			
		||||
│  │  │  └─
 | 
			
		||||
│  │  ├─ image                               // 前端图片资源
 | 
			
		||||
│  │  │  ├─ ...
 | 
			
		||||
│  │  └─ js
 | 
			
		||||
│  │     └─ isMobile.js
 | 
			
		||||
│  ├─ bilibili_hotband.html
 | 
			
		||||
│  ├─ bilibili_rank.html
 | 
			
		||||
│  └─ weibo_hotband.html
 | 
			
		||||
├─ src                                       // 数据爬取核心代码
 | 
			
		||||
│  ├─ utils                                  // 工具类代码
 | 
			
		||||
│  │  ├─ fileUtils.js
 | 
			
		||||
│  │  └─ requestUtils.js
 | 
			
		||||
│  ├─ execute_command.js                     // 执行命令行脚本(暂时没用到)
 | 
			
		||||
│  ├─ get_bilibili_hotband.js                // 获取 B站热搜榜 代码
 | 
			
		||||
│  ├─ get_bilibili_rank.js                   // 爬取 B站排行榜 代码
 | 
			
		||||
│  └─ get_weibo_hotband.js                   // 爬取 微博热搜榜 代码
 | 
			
		||||
├─ .env.example                              // 项目配置文件模板
 | 
			
		||||
├─ .env                                      // 项目配置文件(需要自行创建)
 | 
			
		||||
├─ index.html                                // html 页面打开文件
 | 
			
		||||
├─ index.js                                  // node 项目启动入口文件
 | 
			
		||||
├─ nodemon.json
 | 
			
		||||
├─ package-lock.json
 | 
			
		||||
├─ package.json
 | 
			
		||||
├─ pm2 restart.bat
 | 
			
		||||
├─ pm2 restart.sh
 | 
			
		||||
├─ pm2 start.bat
 | 
			
		||||
├─ pm2 start.sh
 | 
			
		||||
├─ pm2 stop.bat
 | 
			
		||||
├─ pm2 stop.sh
 | 
			
		||||
└─ README.md                                 // 项目自述文件
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### data 目录结构
 | 
			
		||||
 | 
			
		||||
data 文件夹下的目录结构如下
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
data
 | 
			
		||||
├─ bilibili-hotband
 | 
			
		||||
│  ├─ final / origin
 | 
			
		||||
│  │  └─ xxxx                             // 年
 | 
			
		||||
│  │     └─ xx                            // 月
 | 
			
		||||
│  │        └─ xx                         // 日
 | 
			
		||||
│  │           ├─ xxxxxxxx_xxxx.min.json  // 年月日_时分秒.min.json
 | 
			
		||||
│  └─ latest.json                         // 最新的json文件
 | 
			
		||||
├─ bilibili-rank
 | 
			
		||||
│  ├─ origin
 | 
			
		||||
│  │  └─ xxxx                             // 年
 | 
			
		||||
│  │     └─ xx                            // 月
 | 
			
		||||
│  │        └─ xx                         // 日
 | 
			
		||||
│  │           ├─ xxxxxxxx_xxxx.min.json  // 年月日_时分秒.min.json
 | 
			
		||||
│  └─ latest.json                         // 最新的json文件
 | 
			
		||||
└─ weibo-hotband
 | 
			
		||||
   ├─ origin / final / simplify
 | 
			
		||||
   │  └─ xxxx                             // 年
 | 
			
		||||
   │     └─ xx                            // 月
 | 
			
		||||
   │        └─ xx                         // 日
 | 
			
		||||
   │           ├─ xxxxxxxx_xxxx.min.json  // 年月日_时分秒.min.json
 | 
			
		||||
   ├─ regulation
 | 
			
		||||
   │  └─ xxxx                             // 年
 | 
			
		||||
   │     └─ xx                            // 月
 | 
			
		||||
   │        └─ xx                         // 日
 | 
			
		||||
   │           ├─ xxxxxxxx_xxxx.json      // 年月日_时分秒.json
 | 
			
		||||
   └─ latest.json                         // 最新的json文件
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 题外话:怎么生成目录结构?
 | 
			
		||||
 | 
			
		||||
> 有很多小伙伴在问像上方的目录结构是如何生成的,这里跟大家说下:
 | 
			
		||||
>
 | 
			
		||||
> 1. Windows 下可以通过 `tree` 命令来生成,例如:
 | 
			
		||||
>
 | 
			
		||||
>    ```bash
 | 
			
		||||
>    tree /f > xxx.txt
 | 
			
		||||
>    ```
 | 
			
		||||
>
 | 
			
		||||
> 2. 使用 VS Code 插件
 | 
			
		||||
>
 | 
			
		||||
>    我使用的是 [tree-generator](https://marketplace.visualstudio.com/items?itemName=xboxyan.tree-generator) 这个插件,安装之后直接在文件夹上右键即可生成
 | 
			
		||||
>
 | 
			
		||||
> 3. 另外还有一些其他方法也可以生成,大家可以自己探索。
 | 
			
		||||
							
								
								
									
										34
									
								
								hotband/html/assets/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								hotband/html/assets/css/main.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
#list {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  border-spacing: 0;
 | 
			
		||||
  border: 0.4px solid black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#list tr {
 | 
			
		||||
  height: min(1.85rem, 50px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#list td {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  border: 0.4px solid black;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 热搜的 label 样式 */
 | 
			
		||||
.hotband-label {
 | 
			
		||||
  color: white;
 | 
			
		||||
  padding: 3px;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  font-size: 10px;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bottom-placeholder {
 | 
			
		||||
  height: 90px;
 | 
			
		||||
  font-size:12px;
 | 
			
		||||
  color: #999;
 | 
			
		||||
  display: grid;
 | 
			
		||||
  place-items: center;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  line-height: 1.7em;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								hotband/html/assets/image/bilibili.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hotband/html/assets/image/bilibili.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1129 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="220" height="200"><path d="M234.909 9.656a80.468 80.468 0 0 1 68.398 0 167.374 167.374 0 0 1 41.843 30.578l160.937 140.82h115.07l160.936-140.82a168.983 168.983 0 0 1 41.843-30.578A80.468 80.468 0 0 1 930.96 76.445a80.468 80.468 0 0 1-17.703 53.914 449.818 449.818 0 0 1-35.406 32.187 232.553 232.553 0 0 1-22.531 18.508h100.585a170.593 170.593 0 0 1 118.289 53.109 171.397 171.397 0 0 1 53.914 118.288v462.693a325.897 325.897 0 0 1-4.024 70.007 178.64 178.64 0 0 1-80.468 112.656 173.007 173.007 0 0 1-92.539 25.75h-738.7a341.186 341.186 0 0 1-72.421-4.024A177.835 177.835 0 0 1 28.91 939.065a172.202 172.202 0 0 1-27.36-92.539V388.662a360.498 360.498 0 0 1 0-66.789A177.03 177.03 0 0 1 162.487 178.64h105.414c-16.899-12.07-31.383-26.555-46.672-39.43a80.468 80.468 0 0 1-25.75-65.984 80.468 80.468 0 0 1 39.43-63.57M216.4 321.873a80.468 80.468 0 0 0-63.57 57.937 108.632 108.632 0 0 0 0 30.578v380.615a80.468 80.468 0 0 0 55.523 80.469 106.218 106.218 0 0 0 34.601 5.632h654.208a80.468 80.468 0 0 0 76.444-47.476 112.656 112.656 0 0 0 8.047-53.109v-354.06a135.187 135.187 0 0 0 0-38.625 80.468 80.468 0 0 0-52.304-54.719 129.554 129.554 0 0 0-49.89-7.242H254.22a268.764 268.764 0 0 0-37.82 0z m0 0" fill="#20B0E3"></path><path d="M348.369 447.404a80.468 80.468 0 0 1 55.523 18.507 80.468 80.468 0 0 1 28.164 59.547v80.468a80.468 80.468 0 0 1-16.094 51.5 80.468 80.468 0 0 1-131.968-9.656 104.609 104.609 0 0 1-10.46-54.719v-80.468a80.468 80.468 0 0 1 70.007-67.593z m416.02 0a80.468 80.468 0 0 1 86.102 75.64v80.468a94.148 94.148 0 0 1-12.07 53.11 80.468 80.468 0 0 1-132.773 0 95.757 95.757 0 0 1-12.875-57.133V519.02a80.468 80.468 0 0 1 70.007-70.812z m0 0" fill="#20B0E3"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.9 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								hotband/html/assets/image/icon_rank.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								hotband/html/assets/image/icon_rank.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.9 KiB  | 
							
								
								
									
										1
									
								
								hotband/html/assets/image/weibo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hotband/html/assets/image/weibo.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2278" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M1012.49 451.553v0.159c-6.697 20.66-28.861 31.99-49.449 25.288a39.352 39.352 0 0 1-25.287-49.582l-0.067-0.031c20.536-63.6 7.516-136.156-40.315-189.363-47.892-53.212-118.502-73.554-183.731-59.659-21.222 4.537-42.133-9.047-46.638-30.3-4.506-21.253 9.021-42.194 30.239-46.73 91.709-19.563 191.114 8.98 258.467 83.881 67.36 74.839 85.515 176.85 56.781 266.337z" fill="#D32024"></path><path d="M740.429 304.348v-0.03c-18.217 3.973-36.178-7.732-40.06-26.01-3.947-18.31 7.763-36.373 25.98-40.254 44.692-9.548 93.143 4.322 125.885 40.781 32.866 36.496 41.631 86.17 27.607 129.772a33.833 33.833 0 0 1-42.562 21.847c-17.782-5.76-27.484-24.914-21.724-42.69h-0.062c6.887-21.346 2.565-45.635-13.46-63.473-16.026-17.818-39.752-24.546-61.604-19.943z m30.05 192.184c-14.46-4.352-24.352-7.326-16.774-26.352 16.333-41.313 18.027-76.964 0.317-102.385-33.31-47.734-124.451-45.133-228.838-1.28 0-0.061-32.799 14.367-24.412-11.704 16.056-51.774 13.645-95.186-11.361-120.192-56.658-56.878-207.304 2.12-336.477 131.64C56.187 463.32 0 566.14 0 655.1 0 825.18 217.503 928.594 430.28 928.594c278.917 0 464.527-162.504 464.527-291.59 0-77.936-65.546-122.193-124.329-140.472zM430.842 867.62c-169.774 16.84-316.35-60.155-327.368-171.96-11.049-111.74 117.72-216.034 287.488-232.873 169.805-16.84 316.355 60.16 327.368 171.904 11.018 111.866-117.683 216.09-287.488 232.929z" fill="#D32024"></path><path d="M447.805 548.859c-80.783-21.09-172.119 19.287-207.206 90.65-35.743 72.862-1.188 153.681 80.44 180.1 84.578 27.357 184.233-14.525 218.88-93.148 34.181-76.81-8.478-155.94-92.114-177.602zM386.12 734.792c-16.43 26.29-51.584 37.806-78.065 25.661-26.107-11.889-33.833-42.44-17.403-68.045 16.215-25.538 50.207-36.869 76.498-25.856 26.604 11.392 35.087 41.687 18.97 68.24z" fill="#D32024"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 2.0 KiB  | 
							
								
								
									
										24
									
								
								hotband/html/assets/js/isMobile.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								hotband/html/assets/js/isMobile.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
function isMobile() {
 | 
			
		||||
    var userAgentInfo = navigator.userAgent;
 | 
			
		||||
 | 
			
		||||
    var mobileAgents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
 | 
			
		||||
 | 
			
		||||
    var mobile_flag = false;
 | 
			
		||||
 | 
			
		||||
    //根据userAgent判断是否是手机
 | 
			
		||||
    for (var v = 0; v < mobileAgents.length; v++) {
 | 
			
		||||
        if (userAgentInfo.indexOf(mobileAgents[v]) > 0) {
 | 
			
		||||
            mobile_flag = true;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // var screen_width = window.screen.width;
 | 
			
		||||
    // var screen_height = window.screen.height;
 | 
			
		||||
 | 
			
		||||
    // //根据屏幕分辨率判断是否是手机
 | 
			
		||||
    // if (screen_width > 325 && screen_height < 750) {
 | 
			
		||||
    //     mobile_flag = true;
 | 
			
		||||
    // }
 | 
			
		||||
 | 
			
		||||
    return mobile_flag;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										310
									
								
								hotband/html/bilibili_hotband.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										310
									
								
								hotband/html/bilibili_hotband.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,310 @@
 | 
			
		||||
<!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">
 | 
			
		||||
    <title>B站热搜</title>
 | 
			
		||||
    <link rel="stylesheet" href="./assets/css/main.css">
 | 
			
		||||
    <style>
 | 
			
		||||
        #list td {
 | 
			
		||||
            font-size: 14px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #mobile-info {
 | 
			
		||||
            color: grey;
 | 
			
		||||
            font-size: 13px;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div style="text-align: center;">
 | 
			
		||||
        <h1>B站热搜榜</h1>
 | 
			
		||||
        <hr>
 | 
			
		||||
        <div>
 | 
			
		||||
            显示字段:
 | 
			
		||||
            <label for="show_keyword">
 | 
			
		||||
                <input type="checkbox" class="filter_checkbox" name="show_keyword" id="show_keyword" checked="true"
 | 
			
		||||
                    detailed-checked="true">关键词
 | 
			
		||||
            </label>
 | 
			
		||||
            <label for="show_word_type">
 | 
			
		||||
                <input type="checkbox" class="filter_checkbox" name="show_word_type" id="show_word_type"
 | 
			
		||||
                    detailed-checked="true">word_type
 | 
			
		||||
            </label>
 | 
			
		||||
            <label for="show_hot_id">
 | 
			
		||||
                <input type="checkbox" class="filter_checkbox" name="show_hot_id" id="show_hot_id"
 | 
			
		||||
                    detailed-checked="true">hot_id
 | 
			
		||||
            </label>
 | 
			
		||||
            <br>
 | 
			
		||||
            <button id="btn_show_all">全选</button>
 | 
			
		||||
            <button id="btn_show_none">全不选</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>
 | 
			
		||||
    <p id="mobile-info"></p>
 | 
			
		||||
    <div class="bottom-placeholder">
 | 
			
		||||
        <p>
 | 
			
		||||
            — 到底啦 —<br>
 | 
			
		||||
            数据来源: https://www.bilibili.com/blackboard/activity-trending-topic.html
 | 
			
		||||
        </p>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <script src="./assets/js/isMobile.js"></script>
 | 
			
		||||
    <script>
 | 
			
		||||
        let mobileFlag = isMobile();
 | 
			
		||||
        document.getElementById('mobile-info').innerHTML = `手机、电脑端热搜链接不同,当前为您呈现 <span style="font-weight: bold;">${mobileFlag ? '手机端' : '电脑端'}</span> 链接`;
 | 
			
		||||
    </script>
 | 
			
		||||
    <script>
 | 
			
		||||
        let iconMapper = {
 | 
			
		||||
            "http://i0.hdslb.com/bfs/feed-admin/e9e7a2d8497d4063421b685e72680bf1cfb99a0d.png": ["热", "#FF895C"],
 | 
			
		||||
            "http://i0.hdslb.com/bfs/feed-admin/4d579fb61f9655316582db193118bba3a721eec0.png": ["新", "#F87399"],
 | 
			
		||||
        }
 | 
			
		||||
        function getIconText(iconUrl) {
 | 
			
		||||
            if (!iconUrl) {
 | 
			
		||||
                return '';
 | 
			
		||||
            }
 | 
			
		||||
            else if (Object.keys(iconMapper).indexOf(iconUrl) != -1) {
 | 
			
		||||
                return iconMapper[iconUrl];
 | 
			
		||||
            } else {
 | 
			
		||||
                console.log("未知 iconUrl:", iconUrl);
 | 
			
		||||
                return [" ? ", 'grey'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
    <script>
 | 
			
		||||
        /**
 | 
			
		||||
         * 全局变量
 | 
			
		||||
         */
 | 
			
		||||
        // 拉取下来的数据
 | 
			
		||||
        let hotBandData;
 | 
			
		||||
 | 
			
		||||
        // 按钮
 | 
			
		||||
        const btnShowAll = document.getElementById('btn_show_all');
 | 
			
		||||
        const btnShowNone = document.getElementById('btn_show_none');
 | 
			
		||||
        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 showKeyword = document.getElementById("show_keyword");
 | 
			
		||||
        const showWordType = document.getElementById("show_word_type");
 | 
			
		||||
        const showHotId = document.getElementById("show_hot_id");
 | 
			
		||||
 | 
			
		||||
        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();
 | 
			
		||||
        });
 | 
			
		||||
        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 < 780) {
 | 
			
		||||
            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-hotband/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);
 | 
			
		||||
 | 
			
		||||
                    // 更新时间
 | 
			
		||||
                    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>
 | 
			
		||||
                    ${showKeyword.checked ? "<td>关键词</td>" : ""}
 | 
			
		||||
                    ${showWordType.checked ? "<td>word_type</td>" : ""}
 | 
			
		||||
                    ${showHotId.checked ? "<td>hot_id</td>" : ""}
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>`);
 | 
			
		||||
            str.push(`<tbody>`);
 | 
			
		||||
 | 
			
		||||
            for (var i = 0; i < hotBandList.length; i++) {
 | 
			
		||||
                const hotBand = hotBandList[i];
 | 
			
		||||
 | 
			
		||||
                let linkUrl = mobileFlag
 | 
			
		||||
                    ? `https://m.bilibili.com/search?keyword=${encodeURIComponent(hotBand.keyword)}`
 | 
			
		||||
                    : `https://search.bilibili.com/all?keyword=${encodeURIComponent(hotBand.keyword)}`;
 | 
			
		||||
 | 
			
		||||
                str.push(`<tr>
 | 
			
		||||
                    <!-- 编号 -->
 | 
			
		||||
                    <td>${hotBand.position}</td>
 | 
			
		||||
 | 
			
		||||
                    <!-- 热搜 -->
 | 
			
		||||
                    <td style="text-align: left;">
 | 
			
		||||
                        <nobr>
 | 
			
		||||
                            <div style="min-width: 20px; display: inline-block;">
 | 
			
		||||
                                <span class="hotband-label" style="background-color: ${getIconText(hotBand.icon)[1]}; ${hotBand.icon ? "" : "display: none;"}">${getIconText(hotBand.icon)[0]}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <span>
 | 
			
		||||
                                <a href="${linkUrl}" target="_blank">${hotBand.show_name}</a>
 | 
			
		||||
                            </span>
 | 
			
		||||
                            ${hotBand.show_live_icon ? `<span class="hotband-label" style="background-color: #f69; padding: 1px 5px;">直播中</span>` : ""}
 | 
			
		||||
                        </nobr>
 | 
			
		||||
                    </td>
 | 
			
		||||
 | 
			
		||||
                    ${showKeyword.checked ? `
 | 
			
		||||
                    <!-- 关键词 -->
 | 
			
		||||
                    <td>${hotBand.keyword}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showWordType.checked ? `
 | 
			
		||||
                    <!-- word_type -->
 | 
			
		||||
                    <td>${hotBand.word_type}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showHotId.checked ? `
 | 
			
		||||
                    <!-- hot_id -->
 | 
			
		||||
                    <td>${hotBand.hot_id}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
                </tr >`);
 | 
			
		||||
            }
 | 
			
		||||
            str.push(`</tbody>`);
 | 
			
		||||
            document.getElementById('list').innerHTML = str.join('');
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										466
									
								
								hotband/html/bilibili_rank.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										466
									
								
								hotband/html/bilibili_rank.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,466 @@
 | 
			
		||||
<!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>
 | 
			
		||||
							
								
								
									
										333
									
								
								hotband/html/weibo_hotband.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								hotband/html/weibo_hotband.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,333 @@
 | 
			
		||||
<!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">
 | 
			
		||||
    <title>微博热搜</title>
 | 
			
		||||
    <link rel="stylesheet" href="./assets/css/main.css">
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div style="text-align: center;">
 | 
			
		||||
        <h1>微博热搜榜</h1>
 | 
			
		||||
        <hr>
 | 
			
		||||
        <div>
 | 
			
		||||
            显示字段:
 | 
			
		||||
            <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://weibo.com/hot/search
 | 
			
		||||
        </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/weibo-hotband/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);
 | 
			
		||||
 | 
			
		||||
                    // 更新时间
 | 
			
		||||
                    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>
 | 
			
		||||
                    ${showEmoticon.checked ? "<td>表情</td>" : ""}
 | 
			
		||||
                    ${showNum.checked ? '<td>热度<br/><span style="font-size: 10px;">(展示/真实)</span></td>' : ""}
 | 
			
		||||
                    ${showCategory.checked ? "<td>分类</td>" : ""}
 | 
			
		||||
                    ${showOnboardTime.checked ? "<td>上线时间</td>" : ""}
 | 
			
		||||
                    ${showIsNew.checked ? "<td>是否新热搜</td>" : ""}
 | 
			
		||||
                    ${showDetail.checked ? "<td>热搜详情</td>" : ""}
 | 
			
		||||
                    ${showMid.checked ? "<td>mid</td>" : ""}
 | 
			
		||||
                </tr>
 | 
			
		||||
            </thead>`);
 | 
			
		||||
            str.push(`<tbody>`);
 | 
			
		||||
            for (var i = 0; i < hotBandList.length; i++) {
 | 
			
		||||
                const hotBand = hotBandList[i];
 | 
			
		||||
                let hotDelta = hotBand.num - hotBand.raw_hot;
 | 
			
		||||
                str.push(`<tr>
 | 
			
		||||
                    <!-- 编号 -->
 | 
			
		||||
                    <td>${i + 1}</td>
 | 
			
		||||
 | 
			
		||||
                    <!-- 热搜 -->
 | 
			
		||||
                    <td style="text-align: left; font-size: 14px;">
 | 
			
		||||
                        <nobr>
 | 
			
		||||
                            <div style="min-width: 20px; display: inline-block;">
 | 
			
		||||
                                <span class="hotband-label" style="background-color: ${hotBand.more.icon_desc_color};">${hotBand.label_name}</span>
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <a href="${hotBand.url}" target="_blank">${hotBand.word}</a>
 | 
			
		||||
                        </nobr>
 | 
			
		||||
                    </td>
 | 
			
		||||
 | 
			
		||||
                    ${showEmoticon.checked ? `
 | 
			
		||||
                    <!-- 表情 -->
 | 
			
		||||
                    <td>${hotBand.emoticon}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showNum.checked ? `
 | 
			
		||||
                    <!-- 热度 -->
 | 
			
		||||
                    <td style="line-height: 12px;">
 | 
			
		||||
                        <nobr><span style="font-size: 14px;">${hotDelta == 0 ? hotBand.num : `${hotBand.num} / ${hotBand.raw_hot}`}</span></nobr><br/>
 | 
			
		||||
                        <nobr>
 | 
			
		||||
                            <span style="font-size: 10px; color: ${hotDelta > 0 ? "red" : "green"}; font-weight: bold;">
 | 
			
		||||
                                ${hotDelta != 0 ? `(官方调控 ${hotDelta > 0 ? "+" : ""}${hotDelta})` : ""}
 | 
			
		||||
                            </span>
 | 
			
		||||
                        </nobr>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showCategory.checked ? `
 | 
			
		||||
                    <!-- 分类 -->
 | 
			
		||||
                    <td style="font-size: 10px;">${hotBand.category.map((c) => `<nobr>${c}</nobr>`).join(';')}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showOnboardTime.checked ? `
 | 
			
		||||
                    <!-- 热搜上线时间 -->
 | 
			
		||||
                    <td style="font-size: 10px;">${new Date(hotBand.onboard_time * 1000).toLocaleString()}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showIsNew.checked ? `
 | 
			
		||||
                    <!-- 是否新热搜 -->
 | 
			
		||||
                    <td>${hotBand.more.is_new == 1 ? "是" : ""}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showDetail.checked ? `
 | 
			
		||||
                    <!-- 热搜详情 -->
 | 
			
		||||
                    <td>
 | 
			
		||||
                        <div style="font-size:10px; max-width: 300px; display: inline-block;">${hotBand.more.detail}</div>
 | 
			
		||||
                    </td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
 | 
			
		||||
                    ${showMid.checked ? `
 | 
			
		||||
                    <!-- mid -->
 | 
			
		||||
                    <td style="font-size: 14px;">${hotBand.more.mid}</td>
 | 
			
		||||
                    ` : ""}
 | 
			
		||||
                </tr >`);
 | 
			
		||||
            }
 | 
			
		||||
            str.push(`</tbody>`);
 | 
			
		||||
            document.getElementById('list').innerHTML = str.join('');
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										191
									
								
								hotband/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								hotband/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,191 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
 | 
			
		||||
<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">
 | 
			
		||||
    <title>热搜榜单</title>
 | 
			
		||||
    <style>
 | 
			
		||||
        * {
 | 
			
		||||
            margin: 0;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        html,
 | 
			
		||||
        body {
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            overflow: hidden;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .container {
 | 
			
		||||
            display: grid;
 | 
			
		||||
            grid-template-rows: 1fr 50px;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .iframe-container,
 | 
			
		||||
        iframe {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #float-container.bt {
 | 
			
		||||
            bottom: 0;
 | 
			
		||||
            right: 0;
 | 
			
		||||
            left: 0;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            background-color: #fdfdfd;
 | 
			
		||||
            box-shadow: 0 -2px 10px 0 rgb(0 0 0 / 10%);
 | 
			
		||||
            display: grid;
 | 
			
		||||
            /* grid-template-columns: repeat(3, 1fr); */
 | 
			
		||||
            place-items: center;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #float-container.bt .navbar-item {
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            cursor: pointer;
 | 
			
		||||
            width: 80px;
 | 
			
		||||
            user-select: none;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #float-container.bt .navbar-item .navbar-image {
 | 
			
		||||
            width: 22px;
 | 
			
		||||
            height: 22px;
 | 
			
		||||
            filter: grayscale(1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #float-container.bt .navbar-item .navbar-title {
 | 
			
		||||
            font-size: 11px;
 | 
			
		||||
            color: #666;
 | 
			
		||||
            margin-top: -3px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #float-container.bt .navbar-item.active .navbar-image {
 | 
			
		||||
            filter: initial;
 | 
			
		||||
        }
 | 
			
		||||
    </style>
 | 
			
		||||
</head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <div id="iframe-container">
 | 
			
		||||
            <!-- <iframe id="main-iframe" src="html/weibo.html" frameborder="0"></iframe> -->
 | 
			
		||||
        </div>
 | 
			
		||||
        <div id="float-container" class="bt">
 | 
			
		||||
            <!-- <div class="navbar-item" targetPage="weibo_hotband">
 | 
			
		||||
                <img class="navbar-image" src="./html/assets/image/weibo.svg" />
 | 
			
		||||
                <p class="navbar-title">微博热搜</p>
 | 
			
		||||
            </div> -->
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script>
 | 
			
		||||
        let pages = {
 | 
			
		||||
            'weibo_hotband': {
 | 
			
		||||
                title: '微博热搜',
 | 
			
		||||
                // url: 'html/weibo_hotband.html',
 | 
			
		||||
                icon: './html/assets/image/weibo.svg',
 | 
			
		||||
                icon_scale: 1,
 | 
			
		||||
            },
 | 
			
		||||
            'bilibili_hotband': {
 | 
			
		||||
                title: 'B站热搜',
 | 
			
		||||
                // url: 'html/bilibili_hotband.html',
 | 
			
		||||
                icon: './html/assets/image/bilibili.svg',
 | 
			
		||||
                icon_scale: 0.87,
 | 
			
		||||
            },
 | 
			
		||||
            'bilibili_rank': {
 | 
			
		||||
                title: 'B站排行',
 | 
			
		||||
                // url: 'html/bilibili_rank.html',
 | 
			
		||||
                icon: './html/assets/image/icon_rank.png',
 | 
			
		||||
                icon_scale: 0.87,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
        // 渲染导航栏
 | 
			
		||||
        for (let key in pages) {
 | 
			
		||||
            let page = pages[key];
 | 
			
		||||
            let navbarItem = document.createElement('div');
 | 
			
		||||
            navbarItem.className = 'navbar-item';
 | 
			
		||||
            navbarItem.setAttribute('targetPage', key);
 | 
			
		||||
            navbarItem.innerHTML = `
 | 
			
		||||
                <img class="navbar-image" src="${page.icon}" ${page.icon_scale != 1 ? `style="transform: scale(${page.icon_scale});"` : ''} />
 | 
			
		||||
                <p class="navbar-title"> ${page.title}</p>
 | 
			
		||||
            `;
 | 
			
		||||
            document.getElementById('float-container').style.gridTemplateColumns = `repeat(${Object.keys(pages).length}, 1fr)`;
 | 
			
		||||
            document.getElementById('float-container').appendChild(navbarItem);
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
    <script>
 | 
			
		||||
        let navbarItems = document.querySelectorAll('.navbar-item');
 | 
			
		||||
        let iframeContainer = document.querySelector('#iframe-container');
 | 
			
		||||
        let iframe = document.querySelector('#main-iframe');
 | 
			
		||||
        navbarItems.forEach(item => {
 | 
			
		||||
            item.addEventListener('click', () => {
 | 
			
		||||
                // 已选中项再次点击,不执行操作
 | 
			
		||||
                if (item.classList.contains('active')) return;
 | 
			
		||||
 | 
			
		||||
                navbarItems.forEach(item => {
 | 
			
		||||
                    item.classList.remove('active');
 | 
			
		||||
                });
 | 
			
		||||
                item.classList.add('active');
 | 
			
		||||
 | 
			
		||||
                let target = item.getAttribute('targetPage');
 | 
			
		||||
                switchPage(target, true);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // 切换页面
 | 
			
		||||
        // 传入参数:目标页面,是否更新网址
 | 
			
		||||
        function switchPage(target, pushState) {
 | 
			
		||||
            let url = `html/${target}.html`;
 | 
			
		||||
            if (pushState) {
 | 
			
		||||
                // 更新url但不刷新页面
 | 
			
		||||
                history.pushState(null, null, `./index.html?target=${target}`);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // iframe 进入新页面
 | 
			
		||||
            if (iframe)
 | 
			
		||||
                iframe.remove();
 | 
			
		||||
            iframe = document.createElement('iframe');
 | 
			
		||||
            iframe.id = "main-iframe";
 | 
			
		||||
            iframe.src = url;
 | 
			
		||||
            iframe.setAttribute('frameborder', '0');
 | 
			
		||||
            iframeContainer.appendChild(iframe);
 | 
			
		||||
 | 
			
		||||
            iframe.addEventListener('load', function (event) {
 | 
			
		||||
                console.log(event);
 | 
			
		||||
                document.title = document.getElementById("main-iframe").contentWindow.document.title + " | 热搜榜单"
 | 
			
		||||
            }, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 根据 URL 参数切换页面
 | 
			
		||||
        function locationToPageByUrlParam() {
 | 
			
		||||
            // 获取 url 中的 target 参数
 | 
			
		||||
            let url = new URL(window.location.href);
 | 
			
		||||
            let target = url.searchParams.get('target');
 | 
			
		||||
            if (!Object.keys(pages).includes(target)) {
 | 
			
		||||
                target = 'weibo_hotband';
 | 
			
		||||
                history.replaceState(null, null, `./index.html?target=${target}`);
 | 
			
		||||
            }
 | 
			
		||||
            // 切换页面
 | 
			
		||||
            switchPage(target, false);
 | 
			
		||||
            // 更新下方导航栏选中状态
 | 
			
		||||
            let oldActive = document.querySelector('.active')
 | 
			
		||||
            if (oldActive) oldActive.classList.remove('active');
 | 
			
		||||
            navbarItems.forEach(item => {
 | 
			
		||||
                if (item.getAttribute('targetPage') === target) {
 | 
			
		||||
                    item.classList.add('active');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        window.addEventListener("popstate", function (e) {
 | 
			
		||||
            console.log("浏览器返回事件", e);
 | 
			
		||||
            locationToPageByUrlParam();
 | 
			
		||||
        }, false);
 | 
			
		||||
 | 
			
		||||
        locationToPageByUrlParam();
 | 
			
		||||
    </script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										110
									
								
								hotband/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								hotband/index.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,110 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const dotenv = require('dotenv');
 | 
			
		||||
const schedule = require('node-schedule');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
const os = require('os');
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 环境变量
 | 
			
		||||
 */
 | 
			
		||||
if (!fs.existsSync('.env')) {
 | 
			
		||||
    // 如果没有 .env 文件,则报错并退出
 | 
			
		||||
    console.error('[ERROR] .env file not found!');
 | 
			
		||||
    return;
 | 
			
		||||
}
 | 
			
		||||
process.env = {};           // 清除系统自带的环境变量
 | 
			
		||||
dotenv.config('./.env');    // 导入 .env 文件中的环境变量
 | 
			
		||||
// console.log(process.env);
 | 
			
		||||
 | 
			
		||||
const DEBUG_MODE = process.env.DEBUG_MODE == true;
 | 
			
		||||
const EXECUTE_AT_STARTUP = process.env.EXECUTE_AT_STARTUP == true;
 | 
			
		||||
const PUSH_TO_GIT = process.env.PUSH_TO_GIT == true;
 | 
			
		||||
 | 
			
		||||
const ROOT_PATH = path.join(__dirname, process.env.DATA_FOLDER ?? 'data');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 调试模式
 | 
			
		||||
 */
 | 
			
		||||
if (DEBUG_MODE) {
 | 
			
		||||
    console.log('DEBUG_MODE is on');
 | 
			
		||||
    console.log('Environment variables: ', process.env);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 引入模块
 | 
			
		||||
 */
 | 
			
		||||
const get_weibo_hotband = require('./src/get_weibo_hotband');
 | 
			
		||||
const get_bilibili_hotband = require('./src/get_bilibili_hotband');
 | 
			
		||||
const get_bilibili_rank = require('./src/get_bilibili_rank');
 | 
			
		||||
 | 
			
		||||
const execute_command = require('./src/execute_command');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 开始运行
 | 
			
		||||
 */
 | 
			
		||||
console.log("Start running ...");
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 程序主函数
 | 
			
		||||
 */
 | 
			
		||||
async function start() {
 | 
			
		||||
    // 爬取热搜数据
 | 
			
		||||
    await get_weibo_hotband.main();
 | 
			
		||||
    await get_bilibili_hotband.main();
 | 
			
		||||
    await get_bilibili_rank.main();
 | 
			
		||||
 | 
			
		||||
    // 调试模式下
 | 
			
		||||
    if (DEBUG_MODE) {
 | 
			
		||||
        // 推送到 Git 仓库
 | 
			
		||||
        await pushToGitRepo();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 调试模式下,程序一启动就首先运行一次
 | 
			
		||||
if (EXECUTE_AT_STARTUP) {
 | 
			
		||||
    process.stdout.write("程序启动时,立即运行一次\t");
 | 
			
		||||
    start();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 每分钟的第 5 秒执行一次
 | 
			
		||||
// 这里指定第 5 秒是为了稍微与微博服务器热榜更新时间错开,避免因为微秒级误差造成拉取两次相同的热榜数据
 | 
			
		||||
// refer: https://www.npmjs.com/package/node-schedule
 | 
			
		||||
const scheduleJob = schedule.scheduleJob('05 * * * * *', start);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 定时将热搜数据推送到 Git 仓库
 | 
			
		||||
 */
 | 
			
		||||
async function pushToGitRepo() {
 | 
			
		||||
    if (!PUSH_TO_GIT) return;
 | 
			
		||||
 | 
			
		||||
    let commands = [
 | 
			
		||||
        'git status',
 | 
			
		||||
        'git pull',
 | 
			
		||||
        'git add .',
 | 
			
		||||
        `git commit -m "${new Date(Date.now() + 8 * 3600 * 1000).toISOString().substring(0, 19).replace('T', ' ')} update"`,
 | 
			
		||||
        `git push origin master`,
 | 
			
		||||
        'git status',
 | 
			
		||||
    ];
 | 
			
		||||
    switch (os.type()) {
 | 
			
		||||
        case 'Windows_NT': // Windows
 | 
			
		||||
            commands.unshift('dir');
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case 'Darwin': // Mac OS X
 | 
			
		||||
        case 'Linux': // Linux
 | 
			
		||||
        default:
 | 
			
		||||
            commands.unshift('pwd');
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    let outputs = await execute_command.execute(ROOT_PATH, commands);
 | 
			
		||||
    console.log(commands, outputs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 每个小时同步一次
 | 
			
		||||
schedule.scheduleJob('0 0 * * * *', pushToGitRepo);
 | 
			
		||||
							
								
								
									
										8
									
								
								hotband/nodemon.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								hotband/nodemon.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
    "ignore": [
 | 
			
		||||
        ".git",
 | 
			
		||||
        ".svn",
 | 
			
		||||
        "node_modules/**/node_modules"
 | 
			
		||||
    ],
 | 
			
		||||
    "ext": "js"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								hotband/pack.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								hotband/pack.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
set f_year=2022
 | 
			
		||||
set f_month=10
 | 
			
		||||
 | 
			
		||||
ren data data_for_backup
 | 
			
		||||
cd ./data_for_backup
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cd ./bilibili-hotband
 | 
			
		||||
del /f /s/q latest.json
 | 
			
		||||
cd ./final/%f_year%/%f_month%
 | 
			
		||||
for /d %%i in (*) do (  "C:\Users\Administrator\Desktop\7-Zip(绿色版)\7z.exe" a "%%~ni.zip" "%%i" -sdel  )
 | 
			
		||||
cd ../../../
 | 
			
		||||
 | 
			
		||||
cd ./origin/%f_year%/%f_month%
 | 
			
		||||
for /d %%i in (*) do (  "C:\Users\Administrator\Desktop\7-Zip(绿色版)\7z.exe" a "%%~ni.zip" "%%i" -sdel  )
 | 
			
		||||
cd ../../../
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cd ../
 | 
			
		||||
cd ./bilibili-rank
 | 
			
		||||
del /f /s/q latest.json
 | 
			
		||||
cd ./origin/%f_year%/%f_month%
 | 
			
		||||
for /d %%i in (*) do (  "C:\Users\Administrator\Desktop\7-Zip(绿色版)\7z.exe" a "%%~ni.zip" "%%i" -sdel  )
 | 
			
		||||
cd ../../../
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cd ../
 | 
			
		||||
cd ./weibo-hotband
 | 
			
		||||
del /f /s/q latest.json
 | 
			
		||||
cd ./final/%f_year%/%f_month%
 | 
			
		||||
for /d %%i in (*) do (  "C:\Users\Administrator\Desktop\7-Zip(绿色版)\7z.exe" a "%%~ni.zip" "%%i" -sdel  )
 | 
			
		||||
cd ../../../
 | 
			
		||||
 | 
			
		||||
cd ./final/%f_year%/%f_month%
 | 
			
		||||
for /d %%i in (*) do (  "C:\Users\Administrator\Desktop\7-Zip(绿色版)\7z.exe" a "%%~ni.zip" "%%i" -sdel  )
 | 
			
		||||
cd ../../../
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
cd ../../
 | 
			
		||||
pause
 | 
			
		||||
							
								
								
									
										1
									
								
								hotband/pm2 restart.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hotband/pm2 restart.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pm2 restart weibo-hotband-bot
 | 
			
		||||
							
								
								
									
										1
									
								
								hotband/pm2 restart.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hotband/pm2 restart.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pm2 restart weibo-hotband-bot
 | 
			
		||||
							
								
								
									
										1
									
								
								hotband/pm2 start.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hotband/pm2 start.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pm2 start index.js --name weibo-hotband-bot
 | 
			
		||||
							
								
								
									
										1
									
								
								hotband/pm2 start.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								hotband/pm2 start.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
pm2 start index.js --name weibo-hotband-bot
 | 
			
		||||
							
								
								
									
										2
									
								
								hotband/pm2 stop.bat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								hotband/pm2 stop.bat
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
pm2 stop weibo-hotband-bot
 | 
			
		||||
pm2 delete weibo-hotband-bot
 | 
			
		||||
							
								
								
									
										2
									
								
								hotband/pm2 stop.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								hotband/pm2 stop.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
pm2 stop weibo-hotband-bot
 | 
			
		||||
pm2 delete weibo-hotband-bot
 | 
			
		||||
							
								
								
									
										40
									
								
								hotband/src/execute_command.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								hotband/src/execute_command.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const child_process = require('child_process');
 | 
			
		||||
const iconv = require("iconv-lite");
 | 
			
		||||
 | 
			
		||||
const encoding = "cp936";
 | 
			
		||||
const bufferEncoding = "binary";
 | 
			
		||||
 | 
			
		||||
async function execute(rootPath, cmds) {
 | 
			
		||||
    let outputs = [];
 | 
			
		||||
    for (let cmd of cmds) {
 | 
			
		||||
        let result = await new Promise(function (resolve) {
 | 
			
		||||
            // refer: https://www.webhek.com/post/execute-a-command-line-binary-with-node-js/
 | 
			
		||||
            child_process.exec(cmd, {
 | 
			
		||||
                cwd: rootPath, // 脚本执行目录
 | 
			
		||||
                encoding: bufferEncoding
 | 
			
		||||
            }, function (err, stdout, stderr) {
 | 
			
		||||
                if (err) {
 | 
			
		||||
                    resolve({
 | 
			
		||||
                        cmd: cmd,
 | 
			
		||||
                        err: err,
 | 
			
		||||
                        // err_stack: iconv.decode(Buffer.from(err.stack, bufferEncoding), encoding),
 | 
			
		||||
                        // err_message: iconv.decode(Buffer.from(err.message, bufferEncoding), encoding),
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    // 获取命令执行的输出
 | 
			
		||||
                    resolve({
 | 
			
		||||
                        cmd: cmd,
 | 
			
		||||
                        stdout: iconv.decode(Buffer.from(stdout, bufferEncoding), encoding),
 | 
			
		||||
                        stderr: iconv.decode(Buffer.from(stderr, bufferEncoding), encoding),
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
        outputs.push(result);
 | 
			
		||||
    }
 | 
			
		||||
    return outputs;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.execute = execute;
 | 
			
		||||
							
								
								
									
										99
									
								
								hotband/src/get_bilibili_hotband.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								hotband/src/get_bilibili_hotband.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
 | 
			
		||||
const fileUtils = require('./utils/fileUtils');
 | 
			
		||||
const requestUtils = require('./utils/requestUtils');
 | 
			
		||||
 | 
			
		||||
const API_URL = "https://app.bilibili.com/x/v2/search/trending/ranking";
 | 
			
		||||
const SUB_FOLDER = "bilibili-hotband";
 | 
			
		||||
 | 
			
		||||
const DATA_FOLDER = path.join(path.dirname(__dirname), process.env.DATA_FOLDER ?? 'data', SUB_FOLDER);
 | 
			
		||||
console.log("DATA_FOLDER", DATA_FOLDER);
 | 
			
		||||
fileUtils.createFolder(DATA_FOLDER); // 程序运行就保证 data 目录存在
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
    let requestTimestamp = Date.now();
 | 
			
		||||
    let now = new Date(requestTimestamp + 8 * 3600 * 1000).toISOString();
 | 
			
		||||
 | 
			
		||||
    let result = await requestUtils.getApiResult(API_URL);
 | 
			
		||||
    if (result.code != 0) {
 | 
			
		||||
        console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功,但服务器处理失败,等待3s后重试。");
 | 
			
		||||
        await new Promise((resolve) => {
 | 
			
		||||
            setTimeout(resolve, 3000); // 等待3秒
 | 
			
		||||
        });
 | 
			
		||||
        result = await requestUtils.getApiResult(API_URL);
 | 
			
		||||
        if (result.ok != 1) {
 | 
			
		||||
            console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功,但服务器处理失败,保存失败信息。");
 | 
			
		||||
            // ok 不为 1,那么久直接保存便于后续分析,不进行后续处理
 | 
			
		||||
            fileUtils.saveJSON({
 | 
			
		||||
                saveFolder: DATA_FOLDER,
 | 
			
		||||
                now: now,
 | 
			
		||||
                fileNameSuffix: `origin-error`,
 | 
			
		||||
                object: result,
 | 
			
		||||
                compress: true,
 | 
			
		||||
                uncompress: false
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功");
 | 
			
		||||
    // console.log("result", result);
 | 
			
		||||
 | 
			
		||||
    let data = result.data;
 | 
			
		||||
 | 
			
		||||
    // 去除 trackid
 | 
			
		||||
    delete data["trackid"];
 | 
			
		||||
    // console.log(data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存原始数据
 | 
			
		||||
     */
 | 
			
		||||
    fileUtils.saveJSON({
 | 
			
		||||
        saveFolder: DATA_FOLDER,
 | 
			
		||||
        now: now,
 | 
			
		||||
        fileNameSuffix: `origin`,
 | 
			
		||||
        object: result,
 | 
			
		||||
        compress: true,
 | 
			
		||||
        uncompress: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取需要的数据,进行转换
 | 
			
		||||
     */
 | 
			
		||||
    let convert = [];
 | 
			
		||||
    data.list.forEach(item => {
 | 
			
		||||
        // {
 | 
			
		||||
        //     "position": 1,
 | 
			
		||||
        //     "keyword": "关键词",
 | 
			
		||||
        //     "show_name": "热搜名称",
 | 
			
		||||
        //     "word_type": 8,
 | 
			
		||||
        //     "icon": "热搜的图标,也可能没有",
 | 
			
		||||
        //     "hot_id": 7399 // 热搜id
 | 
			
		||||
        // }
 | 
			
		||||
        convert.push(item);
 | 
			
		||||
    });
 | 
			
		||||
    fileUtils.saveJSON({
 | 
			
		||||
        saveFolder: DATA_FOLDER,
 | 
			
		||||
        now: now,
 | 
			
		||||
        fileNameSuffix: `final`,
 | 
			
		||||
        object: convert,
 | 
			
		||||
        compress: true,
 | 
			
		||||
        uncompress: false,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新最新的
 | 
			
		||||
     */
 | 
			
		||||
    fs.writeFileSync(`${DATA_FOLDER}/latest.json`, JSON.stringify({
 | 
			
		||||
        update_time: requestTimestamp,
 | 
			
		||||
        update_time_friendly: now.substring(0, 19).replace(/T/g, " "),
 | 
			
		||||
        data: data.list,
 | 
			
		||||
        exp_str: data.exp_str,
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.main = main;
 | 
			
		||||
							
								
								
									
										90
									
								
								hotband/src/get_bilibili_rank.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								hotband/src/get_bilibili_rank.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
 | 
			
		||||
const fileUtils = require('./utils/fileUtils');
 | 
			
		||||
const requestUtils = require('./utils/requestUtils');
 | 
			
		||||
 | 
			
		||||
const API_URL = "https://api.bilibili.com/x/web-interface/ranking/v2?type=all";
 | 
			
		||||
const SUB_FOLDER = "bilibili-rank";
 | 
			
		||||
 | 
			
		||||
const DATA_FOLDER = path.join(path.dirname(__dirname), process.env.DATA_FOLDER ?? 'data', SUB_FOLDER);
 | 
			
		||||
console.log("DATA_FOLDER", DATA_FOLDER);
 | 
			
		||||
fileUtils.createFolder(DATA_FOLDER); // 程序运行就保证 data 目录存在
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
    let requestTimestamp = Date.now();
 | 
			
		||||
    let now = new Date(requestTimestamp + 8 * 3600 * 1000).toISOString();
 | 
			
		||||
 | 
			
		||||
    let result = await requestUtils.getApiResult(API_URL);
 | 
			
		||||
    if (result.code != 0) {
 | 
			
		||||
        console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功,但服务器处理失败,等待3s后重试。");
 | 
			
		||||
        await new Promise((resolve) => {
 | 
			
		||||
            setTimeout(resolve, 3000); // 等待3秒
 | 
			
		||||
        });
 | 
			
		||||
        result = await requestUtils.getApiResult(API_URL);
 | 
			
		||||
        if (result.ok != 1) {
 | 
			
		||||
            console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功,但服务器处理失败,保存失败信息。");
 | 
			
		||||
            // ok 不为 1,那么久直接保存便于后续分析,不进行后续处理
 | 
			
		||||
            fileUtils.saveJSON({
 | 
			
		||||
                saveFolder: DATA_FOLDER,
 | 
			
		||||
                now: now,
 | 
			
		||||
                fileNameSuffix: `origin-error`,
 | 
			
		||||
                object: result,
 | 
			
		||||
                compress: true,
 | 
			
		||||
                uncompress: false
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功");
 | 
			
		||||
    // console.log("result", result);
 | 
			
		||||
 | 
			
		||||
    let data = result.data;
 | 
			
		||||
 | 
			
		||||
    // // 去除 trackid
 | 
			
		||||
    // delete data["trackid"];
 | 
			
		||||
    // console.log(data);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存原始数据
 | 
			
		||||
     */
 | 
			
		||||
    fileUtils.saveJSON({
 | 
			
		||||
        saveFolder: DATA_FOLDER,
 | 
			
		||||
        now: now,
 | 
			
		||||
        fileNameSuffix: `origin`,
 | 
			
		||||
        object: result,
 | 
			
		||||
        compress: true,
 | 
			
		||||
        uncompress: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * 获取需要的数据,进行转换
 | 
			
		||||
    //  */
 | 
			
		||||
    // let convert = [];
 | 
			
		||||
    // data.list.forEach(item => {
 | 
			
		||||
    //     convert.push(item);
 | 
			
		||||
    // });
 | 
			
		||||
    // fileUtils.saveJSON({
 | 
			
		||||
    //     saveFolder: DATA_FOLDER,
 | 
			
		||||
    //     now: now,
 | 
			
		||||
    //     fileNameSuffix: `final`,
 | 
			
		||||
    //     object: convert,
 | 
			
		||||
    //     compress: true,
 | 
			
		||||
    //     uncompress: false,
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新最新的
 | 
			
		||||
     */
 | 
			
		||||
    fs.writeFileSync(`${DATA_FOLDER}/latest.json`, JSON.stringify({
 | 
			
		||||
        update_time: requestTimestamp,
 | 
			
		||||
        update_time_friendly: now.substring(0, 19).replace(/T/g, " "),
 | 
			
		||||
        note: data.note,
 | 
			
		||||
        data: data.list,
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.main = main;
 | 
			
		||||
							
								
								
									
										228
									
								
								hotband/src/get_weibo_hotband.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								hotband/src/get_weibo_hotband.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
 | 
			
		||||
const fileUtils = require('./utils/fileUtils');
 | 
			
		||||
const requestUtils = require('./utils/requestUtils');
 | 
			
		||||
 | 
			
		||||
const API_URL = "https://weibo.com/ajax/statuses/hot_band";
 | 
			
		||||
const SUB_FOLDER = "weibo-hotband";
 | 
			
		||||
 | 
			
		||||
const DATA_FOLDER = path.join(path.dirname(__dirname), process.env.DATA_FOLDER ?? 'data', SUB_FOLDER);
 | 
			
		||||
console.log("DATA_FOLDER", DATA_FOLDER);
 | 
			
		||||
fileUtils.createFolder(DATA_FOLDER); // 程序运行就保证 data 目录存在
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
    let requestTimestamp = Date.now();
 | 
			
		||||
    let now = new Date(requestTimestamp + 8 * 3600 * 1000).toISOString();
 | 
			
		||||
 | 
			
		||||
    let result = await requestUtils.getApiResult(API_URL);
 | 
			
		||||
    if (result.ok != 1) {
 | 
			
		||||
        console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功,但服务器处理失败,等待3s后重试。");
 | 
			
		||||
        await new Promise((resolve) => {
 | 
			
		||||
            setTimeout(resolve, 3000); // 等待3秒
 | 
			
		||||
        });
 | 
			
		||||
        result = await requestUtils.getApiResult(API_URL);
 | 
			
		||||
        if (result.ok != 1) {
 | 
			
		||||
            console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功,但服务器处理失败,保存失败信息。");
 | 
			
		||||
            // ok 不为 1,那么就直接保存便于后续分析,不进行后续处理
 | 
			
		||||
            fileUtils.saveJSON({
 | 
			
		||||
                saveFolder: DATA_FOLDER,
 | 
			
		||||
                now: now,
 | 
			
		||||
                fileNameSuffix: `origin-error`,
 | 
			
		||||
                object: result,
 | 
			
		||||
                compress: true,
 | 
			
		||||
                uncompress: false
 | 
			
		||||
            });
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    console.log(new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString(), SUB_FOLDER, "请求成功");
 | 
			
		||||
    // console.log("result", result);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存原始数据
 | 
			
		||||
     */
 | 
			
		||||
    fileUtils.saveJSON({
 | 
			
		||||
        saveFolder: DATA_FOLDER,
 | 
			
		||||
        now: now,
 | 
			
		||||
        fileNameSuffix: `origin`,
 | 
			
		||||
        object: result,
 | 
			
		||||
        compress: true,
 | 
			
		||||
        uncompress: false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let data = JSON.parse(JSON.stringify(result.data));
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
        fileUtils.saveJSON({
 | 
			
		||||
            saveFolder: DATA_FOLDER,
 | 
			
		||||
            now: now,
 | 
			
		||||
            fileNameSuffix: `origin-parse-error`,
 | 
			
		||||
            object: result,
 | 
			
		||||
            compress: true,
 | 
			
		||||
            uncompress: false
 | 
			
		||||
        });
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 过滤掉不需要的数据
 | 
			
		||||
     */
 | 
			
		||||
    // hotgov
 | 
			
		||||
    if (data.hotgov) {
 | 
			
		||||
        delete data.hotgov["mblog"];
 | 
			
		||||
        // 重复字段只保留一个
 | 
			
		||||
        delete data.hotgov["note"]; // note  word
 | 
			
		||||
        delete data.hotgov["small_icon_desc"]; // icon_desc  small_icon_desc
 | 
			
		||||
        delete data.hotgov["small_icon_desc_color"]; // icon_desc_color  small_icon_desc_color
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // band_list
 | 
			
		||||
    for (let i = 0; i < data.band_list.length; i++) {
 | 
			
		||||
        const item = data.band_list[i];
 | 
			
		||||
 | 
			
		||||
        // 过滤广告
 | 
			
		||||
        if (item.is_ad) {
 | 
			
		||||
            data.band_list.splice(i, 1);
 | 
			
		||||
            i--;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 过滤空字段
 | 
			
		||||
        delete item["ad_info"];
 | 
			
		||||
 | 
			
		||||
        // 重复字段只保留一个
 | 
			
		||||
        delete item["note"]; // note  word
 | 
			
		||||
        delete item["icon_desc"]; delete item["small_icon_desc"]; // label_name  icon_desc  small_icon_desc
 | 
			
		||||
        delete item["small_icon_desc_color"]; // icon_desc_color  small_icon_desc_color
 | 
			
		||||
        delete item["flag_desc"]; // flag_desc  subject_label  这两个有值的时候相同,没有值的时候,前一个为 undefined,后一个为 ""
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取需要的数据,进行转换
 | 
			
		||||
     */
 | 
			
		||||
    let convert = [];
 | 
			
		||||
    data.band_list.forEach(item => {
 | 
			
		||||
        let detail = "";
 | 
			
		||||
        let pic_ids = [];
 | 
			
		||||
        if (item.mblog) { // 有些热搜没有 mblog
 | 
			
		||||
            var regex = /(<([^>]+)>)/ig
 | 
			
		||||
            detail = item.mblog.text.replace(regex, "");
 | 
			
		||||
            if (item.mblog.pics) {
 | 
			
		||||
                pic_ids = item.mblog.pics.map(pic => `${pic}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        convert.push({
 | 
			
		||||
            // 热搜排行顺序
 | 
			
		||||
            rank: item.rank,
 | 
			
		||||
            realpos: item.realpos,
 | 
			
		||||
 | 
			
		||||
            // 热搜信息
 | 
			
		||||
            word: item.word,                    // 热搜标题
 | 
			
		||||
            word_scheme: item.word_scheme,      // 热搜话题 "#热搜标题#"
 | 
			
		||||
            emoticon: item.emoticon,            // 热搜小表情,如 "[泪]"
 | 
			
		||||
            label_name: item.label_name,        // 热搜标签,如 "爆" "热" "新" ""
 | 
			
		||||
            onboard_time: item.onboard_time,    // 热搜上线时间,秒级时间戳,如 1658565575
 | 
			
		||||
 | 
			
		||||
            /**
 | 
			
		||||
             * 热搜数据
 | 
			
		||||
             *
 | 
			
		||||
             * 大部分的 num 和 raw_hot 是相同的,页面上显示的是 num,可能是人工调控的热搜
 | 
			
		||||
             *
 | 
			
		||||
             * 两者差值通过观测似乎最大是 1250000
 | 
			
		||||
             * 例如 【爆】唐山打架事件8名违法嫌疑人已到案 这条热搜一开始 delta 首先不断增大,最大达到 1250000
 | 
			
		||||
             * 然后热搜数量增加到 12600000 左右的时候,delta 逐渐减小到 1040000 左右
 | 
			
		||||
             */
 | 
			
		||||
            num: item.num,
 | 
			
		||||
            raw_hot: item.raw_hot,
 | 
			
		||||
            detla: item.num - item.raw_hot, // 计算值
 | 
			
		||||
 | 
			
		||||
            url: `https://s.weibo.com/weibo?q=${encodeURIComponent(item.word_scheme)}`, // 热搜话题链接
 | 
			
		||||
 | 
			
		||||
            // 分类
 | 
			
		||||
            category: item.category ? item.category.split(',') : "",
 | 
			
		||||
            subject_label: item.subject_label,
 | 
			
		||||
 | 
			
		||||
            // 其他
 | 
			
		||||
            more: {
 | 
			
		||||
                is_new: item.is_new,
 | 
			
		||||
                subject_querys: item.subject_querys,
 | 
			
		||||
                mid: item.mid,
 | 
			
		||||
                icon_desc_color: item.icon_desc_color,
 | 
			
		||||
                detail: detail,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    fileUtils.saveJSON({
 | 
			
		||||
        saveFolder: DATA_FOLDER,
 | 
			
		||||
        now: now,
 | 
			
		||||
        fileNameSuffix: `final`,
 | 
			
		||||
        object: convert,
 | 
			
		||||
        compress: true,
 | 
			
		||||
        // uncompress: true,
 | 
			
		||||
        uncompress: false,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * 只统计微博调控信息
 | 
			
		||||
    //  */
 | 
			
		||||
    // let convert2 = [];
 | 
			
		||||
    // let total = 0;
 | 
			
		||||
    // data.band_list.forEach(item => {
 | 
			
		||||
    //     total += item.num;
 | 
			
		||||
    //     total -= item.raw_hot;
 | 
			
		||||
    //     if (item.num - item.raw_hot == 0) return;
 | 
			
		||||
    //     convert2.push([
 | 
			
		||||
    //         `[${item.realpos}] ${item.word}【${item.label_name}】`,
 | 
			
		||||
    //         `原始:${item.raw_hot} 显示:${item.num} 调控: ${item.num - item.raw_hot}`
 | 
			
		||||
    //     ]);
 | 
			
		||||
    // });
 | 
			
		||||
    // fileUtils.saveJSON({
 | 
			
		||||
    //     saveFolder: DATA_FOLDER,
 | 
			
		||||
    //     now: now,
 | 
			
		||||
    //     fileNameSuffix: `regulation`,
 | 
			
		||||
    //     object: {
 | 
			
		||||
    //         total_delta: total, // 所有调控值之和
 | 
			
		||||
    //         data: convert2
 | 
			
		||||
    //     },
 | 
			
		||||
    //     compress: false,
 | 
			
		||||
    //     uncompress: true
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * 保存预处理后数据
 | 
			
		||||
    //  */
 | 
			
		||||
    // // 过滤掉不需要的数据
 | 
			
		||||
    // // band_list
 | 
			
		||||
    // data.band_list.forEach(function (item) {
 | 
			
		||||
    //     delete item["mblog"];
 | 
			
		||||
    // });
 | 
			
		||||
    // fileUtils.saveJSON({
 | 
			
		||||
    //     saveFolder: DATA_FOLDER,
 | 
			
		||||
    //     now: now,
 | 
			
		||||
    //     fileNameSuffix: `simplify`,
 | 
			
		||||
    //     object: data,
 | 
			
		||||
    //     compress: true,
 | 
			
		||||
    //     // uncompress: true,
 | 
			
		||||
    //     // compress: false,
 | 
			
		||||
    //     uncompress: false,
 | 
			
		||||
    // });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新最新的
 | 
			
		||||
     */
 | 
			
		||||
    fs.writeFileSync(`${DATA_FOLDER}/latest.json`, JSON.stringify({
 | 
			
		||||
        update_time: requestTimestamp,
 | 
			
		||||
        update_time_friendly: now.substring(0, 19).replace(/T/g, " "),
 | 
			
		||||
        // regulation: convert2,
 | 
			
		||||
        data: convert
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
exports.main = main;
 | 
			
		||||
							
								
								
									
										51
									
								
								hotband/src/utils/fileUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								hotband/src/utils/fileUtils.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
const path = require('path');
 | 
			
		||||
 | 
			
		||||
const LATEST_DATA_ONLY = process.env.LATEST_DATA_ONLY == true;
 | 
			
		||||
 | 
			
		||||
// 创建目录
 | 
			
		||||
async function createFolder(folderToCreate) {
 | 
			
		||||
    let currentFolder = path.join(folderToCreate);
 | 
			
		||||
    let parentFolder = path.join(currentFolder, '../');
 | 
			
		||||
    // console.log({ currentFolder: currentFolder, parentFolder: parentFolder });
 | 
			
		||||
    if (!fs.existsSync(currentFolder)) {
 | 
			
		||||
        // 文件夹不存在,创建文件夹
 | 
			
		||||
        createFolder(parentFolder);  // 保证父级文件夹存在
 | 
			
		||||
        fs.mkdirSync(currentFolder); // 创建当前级文件夹
 | 
			
		||||
    } else {
 | 
			
		||||
        // 否则就什么也不做
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 保存 JSON
 | 
			
		||||
function saveJSON({ saveFolder, now, fileNameSuffix, object, compress = true, uncompress = true }) {
 | 
			
		||||
    if (LATEST_DATA_ONLY) return;
 | 
			
		||||
 | 
			
		||||
    let year = now.substring(0, 4);
 | 
			
		||||
    let month = now.substring(5, 7);
 | 
			
		||||
    let day = now.substring(8, 10);
 | 
			
		||||
    let hour = now.substring(11, 13);
 | 
			
		||||
    let minute = now.substring(14, 16);
 | 
			
		||||
    // console.log(now);
 | 
			
		||||
    // console.log( "year, month, day, hour, minute: " + year + ", " + month + ", " + day + ", " + hour + ", " + minute);
 | 
			
		||||
 | 
			
		||||
    // 创建当前文件夹
 | 
			
		||||
    let folder = `${saveFolder}/${fileNameSuffix}/${year}/${month}/${day}`;
 | 
			
		||||
    createFolder(folder);
 | 
			
		||||
    let fileName = `${folder}/${year}${month}${day}_${hour}${minute}`;
 | 
			
		||||
 | 
			
		||||
    // 生成文件名
 | 
			
		||||
    // '2022-07-23T10:11:38.650Z' => '20220723_1011'
 | 
			
		||||
    // let fileName = now.replace(/T/, '_').replace(/:\d{2}.\d{3}Z/, '').replace(/[-:]/g, '');
 | 
			
		||||
    // console.log(`fileName is ${fileName}`);
 | 
			
		||||
 | 
			
		||||
    if (compress)
 | 
			
		||||
        fs.writeFileSync(`${fileName}.min.json`, JSON.stringify(object));
 | 
			
		||||
    if (uncompress)
 | 
			
		||||
        fs.writeFileSync(`${fileName}.json`, JSON.stringify(object, "", "\t"));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    createFolder,
 | 
			
		||||
    saveJSON,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								hotband/src/utils/requestUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								hotband/src/utils/requestUtils.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
const request = require('request');
 | 
			
		||||
 | 
			
		||||
// 请求 APi 接口
 | 
			
		||||
async function getApiResult(url) {
 | 
			
		||||
    var return_data = await new Promise((resolve) => {
 | 
			
		||||
        request({
 | 
			
		||||
            method: 'GET',
 | 
			
		||||
            url: url,
 | 
			
		||||
            json: true,
 | 
			
		||||
        }, (error, response, result) => {
 | 
			
		||||
            if (!error && (response.statusCode == 200)) {
 | 
			
		||||
                // 请求成功
 | 
			
		||||
                resolve(result);
 | 
			
		||||
            } else {
 | 
			
		||||
                // 请求失败
 | 
			
		||||
                console.log(`error is ${error}`);
 | 
			
		||||
                resolve({});
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    // console.log(`return_data is ${JSON.stringify(return_data)}`);
 | 
			
		||||
    return return_data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    getApiResult,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										73
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -11,7 +11,9 @@
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "cheerio": "^1.0.0-rc.12",
 | 
			
		||||
        "crypto": "^1.0.1",
 | 
			
		||||
        "dotenv": "^16.0.1",
 | 
			
		||||
        "fs": "^0.0.1-security",
 | 
			
		||||
        "iconv-lite": "^0.6.3",
 | 
			
		||||
        "minimist": "^1.2.6",
 | 
			
		||||
        "mysql": "^2.18.1",
 | 
			
		||||
        "NeteaseCloudMusicApi": "^4.8.2",
 | 
			
		||||
@@ -224,6 +226,17 @@
 | 
			
		||||
        "npm": "1.2.8000 || >= 1.4.16"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/body-parser/node_modules/iconv-lite": {
 | 
			
		||||
      "version": "0.4.24",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
			
		||||
      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/boolbase": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz",
 | 
			
		||||
@@ -568,6 +581,14 @@
 | 
			
		||||
        "domhandler": "^5.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/dotenv": {
 | 
			
		||||
      "version": "16.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ecc-jsbn": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
 | 
			
		||||
@@ -1143,11 +1164,11 @@
 | 
			
		||||
      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/iconv-lite": {
 | 
			
		||||
      "version": "0.4.24",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
			
		||||
      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
 | 
			
		||||
      "version": "0.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3"
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
@@ -1833,6 +1854,17 @@
 | 
			
		||||
        "node": ">= 0.8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/raw-body/node_modules/iconv-lite": {
 | 
			
		||||
      "version": "0.4.24",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
			
		||||
      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/readable-stream": {
 | 
			
		||||
      "version": "2.3.7",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz",
 | 
			
		||||
@@ -2588,6 +2620,16 @@
 | 
			
		||||
        "raw-body": "2.5.1",
 | 
			
		||||
        "type-is": "~1.6.18",
 | 
			
		||||
        "unpipe": "1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "iconv-lite": {
 | 
			
		||||
          "version": "0.4.24",
 | 
			
		||||
          "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
			
		||||
          "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "safer-buffer": ">= 2.1.2 < 3"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "boolbase": {
 | 
			
		||||
@@ -2865,6 +2907,11 @@
 | 
			
		||||
        "domhandler": "^5.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "dotenv": {
 | 
			
		||||
      "version": "16.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "ecc-jsbn": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
 | 
			
		||||
@@ -3313,11 +3360,11 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "iconv-lite": {
 | 
			
		||||
      "version": "0.4.24",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
			
		||||
      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
 | 
			
		||||
      "version": "0.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3"
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "ieee754": {
 | 
			
		||||
@@ -3853,6 +3900,16 @@
 | 
			
		||||
        "http-errors": "2.0.0",
 | 
			
		||||
        "iconv-lite": "0.4.24",
 | 
			
		||||
        "unpipe": "1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "iconv-lite": {
 | 
			
		||||
          "version": "0.4.24",
 | 
			
		||||
          "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz",
 | 
			
		||||
          "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "safer-buffer": ">= 2.1.2 < 3"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "readable-stream": {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,8 @@
 | 
			
		||||
    "node-schedule": "^2.1.0",
 | 
			
		||||
    "path": "^0.12.7",
 | 
			
		||||
    "request": "^2.88.2",
 | 
			
		||||
    "solarlunar": "^2.0.7"
 | 
			
		||||
    "solarlunar": "^2.0.7",
 | 
			
		||||
    "dotenv": "^16.0.1",
 | 
			
		||||
    "iconv-lite": "^0.6.3"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user