From 534761c7db0826710bf9f7ac9048a9c4e80b1207 Mon Sep 17 00:00:00 2001
From: huangxd- <649924757@qq.com>
Date: Sat, 18 Oct 2025 11:49:51 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81netlify=E4=B8=80?=
=?UTF-8?q?=E9=94=AE=E9=83=A8=E7=BD=B2=E5=B9=B6=E4=BF=AE=E5=A4=8D=E4=B8=80?=
=?UTF-8?q?=E4=BA=9Bbug=20(#58)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* 修复如果不冷启动一直使用animes缓存中旧数据的问题
* 修复有对接redis的场景下手动解析链接只返回第一次请求的链接的问题
* 手动搜索场景通过获取播放链接title作为集标题显示
* 增加netlify一键部署
* 修改readme
---
README.md | 37 ++++++++--
danmu_api/worker.js | 147 +++++++++++++++++++++++++++------------
netlify.toml | 18 +++++
netlify/functions/api.js | 3 +
4 files changed, 156 insertions(+), 49 deletions(-)
create mode 100644 netlify.toml
create mode 100644 netlify/functions/api.js
diff --git a/README.md b/README.md
index 97ed01e..50b40e1 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ LogVar 弹幕 API 服务器
- `POST /api/v2/comment/by-url`:通过视频URL直接获取弹幕。
- `GET /api/logs`:获取最近的日志(最多 500 行,格式为 `[时间戳] 级别: 消息`)。
- **日志记录**:捕获 `console.log`(info 级别)和 `console.error`(error 级别),JSON 内容格式化输出。
-- **部署支持**:支持本地运行、Docker 容器化、Vercel 一键部署、Cloudflare 一键部署和 Docker 一键启动。
+- **部署支持**:支持本地运行、Docker 容器化、Vercel 一键部署、Netlify 一键部署、Cloudflare 一键部署和 Docker 一键启动。
- **手动选择记忆**:支持记住之前搜索title时手动选择的anime,并在后续的match自动匹配时优选该anime【实验性】。
- **手动搜索支持输入播放链接获取弹幕**:支持手动搜索的播放器输入爱优腾芒哔播放链接可获取弹幕,如`senplayer`。
@@ -161,6 +161,20 @@ LogVar 弹幕 API 服务器
> hk有可能访问不了360或其他源,可以尝试切其他region,如新加坡等
- vercel在国内被墙,请配合代理使用或绑定自定义域名
+## 部署到 Netlify 【推荐】
+
+### 一键部署
+点击以下按钮即可将项目快速部署到 Netlify:
+
+
+
+> 默认访问domain:https://{你的部署项目名}.netlify.app
+
+- **设置环境变量**:部署后,在 Netlify 仪表板中:
+ 1. 点击Project configuration。
+ 2. 在“Environment variables”部分点击 “Add a variable” 添加 `TOKEN` 变量,输入你的 API 令牌值。
+ 3. 保存更改并重新部署。
+
## 部署到 腾讯云 edgeone pages
### 一键部署
@@ -198,7 +212,7 @@ LogVar 弹幕 API 服务器
### 手动部署
创建一个worker,将`danmu_api/worker.js`里的代码直接拷贝到你创建的`worker.js`里,然后点击部署。
-> cf部署可能不稳定,推荐用vercel部署。
+> cf部署可能不稳定,推荐用vercel/netlify部署。
## API食用指南
支持 forward/senplayer/hills/小幻/yamby/eplayerx/afusekt 等支持弹幕API的播放器。
@@ -239,7 +253,7 @@ LogVar 弹幕 API 服务器
| EPISODE_TITLE_FILTER | 【可选】剧集标题正则过滤,按正则关键字对剧集或综艺的集标题进行过滤,适用于过滤一些预告或综艺非正式集,只支持match自动匹配,默认值如下 |
| BLOCKED_WORDS | 【可选】弹幕屏蔽词列表,默认为空,示例如下 |
| GROUP_MINUTE | 【可选】合并去重分钟数,表示按n分钟分组后对弹幕合并去重,默认为1,最大值为30,0表示不去重 |
-| PROXY_URL | 【可选】代理地址,示例: `http://127.0.0.1:7897` ,目前只对巴哈姆特生效(注意:如果巴哈姆特请求不通,会拖慢搜索返回速度,所以除vercel/cloudflare之外默认不开启bahamut源,开启请先在SOURCE_ORDER环境变量中添加`bahamut`)如果你使用docker部署并且访问不了bahamut源,请配置代理地址;vercel/cf中理应都自然能联通,不用填写 |
+| PROXY_URL | 【可选】代理地址,示例: `http://127.0.0.1:7897` ,目前只对巴哈姆特生效(注意:如果巴哈姆特请求不通,会拖慢搜索返回速度,所以除vercel/netlify/cloudflare之外默认不开启bahamut源,开启请先在SOURCE_ORDER环境变量中添加`bahamut`)如果你使用docker部署并且访问不了bahamut源,请配置代理地址;vercel/netlify/cf中理应都自然能联通,不用填写 |
| RATE_LIMIT_MAX_REQUESTS | 【可选】限流配置:1分钟内同一IP最大请求次数,默认为`3`,设置为`0`表示不限流 |
| UPSTASH_REDIS_REST_URL | 【可选】update redis url,需配合UPSTASH_REDIS_REST_TOKEN使用,用于持久化存储,不会因为冷启动而丢失过去的查询信息(在cf/eo/claw上配置后应该能更稳定点,也能解决小幻掉匹配的问题,但会稍微影响请求速度),获取方法请参考:`https://cloud.tencent.cn/developer/article/2424508` |
| UPSTASH_REDIS_REST_TOKEN | 【可选】update redis token,需配合UPSTASH_REDIS_REST_URL使用,用于持久化存储,不会因为冷启动而丢失过去的查询信息(在cf/eo/claw上配置后应该能更稳定点,也能解决小幻掉匹配的问题,但会稍微影响请求速度),获取方法请参考:`https://cloud.tencent.cn/developer/article/2424508` |
@@ -275,6 +289,16 @@ LogVar 弹幕 API 服务器
/(全体成员|报到|报道|来啦|签到|刷|打卡|我在|来了|考古|爱了|挖坟|留念|你好|回来|哦哦|重温|复习|重刷|再看|在看|前排|沙发|有人看|板凳|末排|我老婆|我老公|撅了|后排|周目|重看|包养|DVD|同上|同样|我也是|俺也|算我|爱豆|我家爱豆|我家哥哥|加我|三连|币|新人|入坑|补剧|冲了|硬了|看完|舔屏|万人|牛逼|煞笔|傻逼|卧槽|tm|啊这|哇哦)/ # 屏蔽常见互动、报到或口语化弹幕词汇
```
+## 采集源及对应平台列表
+| 采集源 | 平台列表 |
+| ----------- | ----------- |
+| 360 | qiyi, bilibili1, imgo, youku, qq |
+| vod | qiyi, bilibili1, imgo, youku, qq |
+| tencent | tencent |
+| renren | renren |
+| hanjutv | hanjutv |
+| bahamut | bahamut |
+
## 项目结构
```
danmu_api/
@@ -287,13 +311,18 @@ danmu_api/
│ ├── server.js # 本地node启动脚本
│ ├── worker.js # 主 API 服务器代码
│ ├── worker.test.js # 测试文件
+├── netlify/
+│ └── functions/
+│ └── api.js # netlify 中间处理逻辑
├── node-functions/
│ ├── [[...path]]..js # edgeone pages 所有路由跳转指向index
│ └── index.js # edgeone pages 中间处理逻辑
+├── .env.example
├── .gitignore
├── Dockerfile
├── edgeone.json # edgeone pages 配置文件
├── LICENSE
+├── netlify.toml # netlify 配置文件
├── package.json
├── README.md
├── vercel.json # vercel 配置文件
@@ -309,7 +338,7 @@ danmu_api/
- cloudflare貌似被哔风控了。
- 如果想更换兜底第三方弹幕服务器,请添加环境变量`OTHER_SERVER`,示例`https://api.danmu.icu`。
- 如果想更换vod站点,请添加环境变量`VOD_SERVERS`,示例`vod@https://www.caiji.cyou,vod2@https://zy.xmm.hk`(支持多个服务器并发查询)。
-- 推荐vercel部署,cloudflare/edgeone/claw不稳定,当然最稳定还是自己本地docker部署最佳。
+- 推荐vercel/netlify部署,cloudflare/edgeone/claw不稳定,当然最稳定还是自己本地docker部署最佳。
- /api/v2/comment接口默认限流:1分钟内同一IP只能请求3次,可通过环境变量`RATE_LIMIT_MAX_REQUESTS`调整(设置为0表示不限流)。
### 关联项目
diff --git a/danmu_api/worker.js b/danmu_api/worker.js
index 9a9b7d4..07910e8 100644
--- a/danmu_api/worker.js
+++ b/danmu_api/worker.js
@@ -110,7 +110,7 @@ let sourceOrderArr = [];
function resolveSourceOrder(env, deployPlatform) {
// 获取环境变量中的 SOURCE_ORDER 配置
let sourceOrder = DEFAULT_SOURCE_ORDER;
- if (deployPlatform === "cloudflare" || deployPlatform === "vercel") {
+ if (deployPlatform === "cloudflare" || deployPlatform === "vercel" || deployPlatform === "netlify") {
sourceOrder += ",bahamut";
}
@@ -349,9 +349,6 @@ function addAnime(anime) {
return false;
}
- // 创建 anime 的副本以避免修改原始对象
- const animeCopy = { ...anime, links: [] }; // 初始化 links 为空数组
-
// 遍历 links,调用 addEpisode,并收集返回的对象
const newLinks = [];
anime.links.forEach(link => {
@@ -365,18 +362,29 @@ function addAnime(anime) {
}
});
- // 替换 animeCopy 的 links
- animeCopy.links = newLinks;
+ // 创建新的 anime 副本
+ const animeCopy = { ...anime, links: newLinks };
- // 添加到 animes
+ // 检查是否已存在相同 animeId 的 anime
+ const existingAnimeIndex = animes.findIndex(a => a.animeId === anime.animeId);
+
+ if (existingAnimeIndex !== -1) {
+ // 如果存在,先删除旧的
+ animes.splice(existingAnimeIndex, 1);
+ log("info", `Removed old anime at index: ${existingAnimeIndex}`);
+ }
+
+ // 将新的添加到数组末尾(最新位置)
animes.push(animeCopy);
- log("info", `Added anime: ${JSON.stringify(animeCopy)}`);
+ log("info", `Added anime to latest position: ${anime.animeId}`);
// 检查是否超过 MAX_ANIMES,超过则删除最早的
if (animes.length > MAX_ANIMES) {
removeEarliestAnime();
}
+ console.log("animes: ", animes);
+
return true;
}
@@ -644,6 +652,43 @@ async function httpPost(url, body, options = {}) {
}
}
+async function getPageTitle(url) {
+ try {
+ // 使用 httpGet 获取网页内容
+ const response = await httpGet(url, {
+ headers: {
+ 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15'
+ }
+ });
+
+ // response.data 包含 HTML 内容
+ const html = response.data;
+
+ // 方法1: 使用正则表达式提取