支持自定义弹幕源 (#121)
* 解决api配置反代后的ui路径问题 * ui修改body和header背景色 * 修复segmentcomment接口调试时format选择xml不生效的问题 * Revert "修复segmentcomment接口调试时format选择xml不生效的问题" This reverts commit 997a101cb0537d95ebdb62e9f1267af7e94475a9. * 修复分片弹幕请求xml还是返回json的问题 * 支持自定义弹幕源 * 修复在反代场景下api端点显示 * 修复导航栏标签切换问题 * 修复iqiyi源的弹幕emoji不显示的问题 * 完善readme中的项目结构 * 修改小版本号
This commit is contained in:
1 parent
28893cf9b2
commit
f199f69d8b
18 files changed
+368
-67
No files matched your search
@@ -303,7 +303,7 @@ LogVar 弹幕 API 服务器
|
||||
> cf部署可能不稳定,推荐用vercel/netlify部署。
|
||||
|
||||
## API食用指南
|
||||
支持 forward/senplayer/hills/小幻/yamby/eplayerx/afusekt/uz影视/dscloud/lenna/danmaku-anywhere/omnibox/ChaiChaiEmbyTV 等支持弹幕API的播放器。
|
||||
支持 forward/senplayer/hills/小幻/yamby/eplayerx/afusekt/uz影视/dscloud/lenna/danmaku-anywhere/omnibox/ChaiChaiEmbyTV/moontv 等支持弹幕API的播放器。
|
||||
|
||||
配合 dd-danmaku 扩展新增对 Emby Web 端弹幕的支持,具体使用方法参考 [PR #98](https://github.com/huangxd-/danmu_api/pull/98) 。
|
||||
|
||||
@@ -364,13 +364,14 @@ API 支持返回 Bilibili 标准 XML 格式的弹幕数据,通过查询参数
|
||||
| TOKEN | 【可选】自定义用户token,不填默认为`87654321` |
|
||||
| ADMIN_TOKEN | 【可选】系统管理访问令牌,如果未配置此值,则无法访问系统管理功能,需要先配置后在URL中填入此token才能打开系统管理 |
|
||||
| OTHER_SERVER | 【可选】兜底第三方弹幕服务器,不填默认为`https://api.danmu.icu`,其他可选:`https://fc.lyz05.cn`,`https://dmku.hls.one`,`https://se.678.ooo`,`https://danmu.56uxi.com`,`https://dm.lxlad.com` |
|
||||
| CUSTOM_SOURCE_API_URL | 【可选】自定义弹幕源API地址,默认为空,配置后还需在SOURCE_ORDER添加custom源 |
|
||||
| VOD_SERVERS | 【可选】VOD服务器列表,支持多个服务器并发查询,格式:`名称@URL,名称@URL,...`,示例:`金蝉@https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top`,不填默认为`金蝉@https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top` |
|
||||
| VOD_RETURN_MODE | 【可选】VOD返回模式,可选值:`all`(返回所有站点结果)、`fastest`(只返回最快的站点结果),默认为`fastest`。当配置多个VOD站点时,`all`模式会返回所有站点的结果(结果较多),`fastest`模式只返回首先响应成功的站点结果(结果较少,避免重复) |
|
||||
| VOD_REQUEST_TIMEOUT | 【可选】VOD服务器单个请求超时时间(毫秒),防止慢速或失效的采集站阻塞搜索,默认为`10000`(10秒),建议值:`5000-15000`。由于`fastest`模式只返回最快响应的站点,可以设置较大的超时时间给慢速站点更多机会 |
|
||||
| BILIBILI_COOKIE | 【可选】b站cookie(填入后能抓取完整弹幕),如 `buvid3=E2BCA ... eao6; theme-avatar-tip-show=SHOWED`,请自行通过浏览器或抓包工具抓取,热心网友测试后,实际最少只需取 `SESSDATA=xxxx` 字段 |
|
||||
| YOUKU_CONCURRENCY | 【可选】youku弹幕请求并发数,用于加快youku弹幕请求速度,不填默认为`8`,最高`16` |
|
||||
| SOURCE_ORDER | 【可选】源排序,用于按源对返回资源的排序(注意:先后顺序会影响自动匹配最终的返回),默认是`360,vod,renren,hanjutv`,表示360数据排在最前,hanjutv数据排在最后,示例:`360,renren`:只返回360数据和renren数据,且360数据靠前;当前可选择的源字段有 `360,vod,tmdb,douban,tencent,youku,iqiyi,imgo,bilibili,renren,hanjutv,bahamut,dandan` |
|
||||
| PLATFORM_ORDER | 【可选】自动匹配优选平台,按顺序优先返回指定平台弹幕,默认为空,即返回第一个满足条件的平台,示例:`bilibili1,qq`,表示如果有b站的播放源,则优先返回b站的弹幕,否则就返回腾讯的弹幕,两者都没有,则返回第一个满足条件的平台;当前可选择的平台字段有 `qiyi, bilibili1, imgo, youku, qq, renren, hanjutv, bahamut, dandan` |
|
||||
| SOURCE_ORDER | 【可选】源排序,用于按源对返回资源的排序(注意:先后顺序会影响自动匹配最终的返回),默认是`360,vod,renren,hanjutv`,表示360数据排在最前,hanjutv数据排在最后,示例:`360,renren`:只返回360数据和renren数据,且360数据靠前;当前可选择的源字段有 `360,vod,tmdb,douban,tencent,youku,iqiyi,imgo,bilibili,renren,hanjutv,bahamut,dandan,custom` |
|
||||
| PLATFORM_ORDER | 【可选】自动匹配优选平台,按顺序优先返回指定平台弹幕,默认为空,即返回第一个满足条件的平台,示例:`bilibili1,qq`,表示如果有b站的播放源,则优先返回b站的弹幕,否则就返回腾讯的弹幕,两者都没有,则返回第一个满足条件的平台;当前可选择的平台字段有 `qiyi, bilibili1, imgo, youku, qq, renren, hanjutv, bahamut, dandan, custom` |
|
||||
| EPISODE_TITLE_FILTER | 【可选】剧集标题正则过滤,按正则关键字对剧集或综艺的集标题进行过滤,适用于过滤一些预告或综艺非正式集,只支持match自动匹配,默认值如下 |
|
||||
| ENABLE_EPISODE_FILTER | 【可选】是否在手动选择接口中启用集标题过滤,默认为`false`(禁用),启用后 GET /api/v2/bangumi/{id} 和 GET /api/v2/search/anime 接口会过滤掉预告、花絮等特殊集,以及名称包含特殊关键词的动漫。 |
|
||||
| STRICT_TITLE_MATCH | 【可选】是否启用严格标题匹配模式,默认为`false`(宽松模糊匹配),启用后只匹配标题开头或完全匹配的结果。例如:搜索"遮天"时,`false`会匹配"古惑仔3之只手遮天",`true`只匹配"遮天"、"遮天 第一季"等。可选值:`true`、`false` |
|
||||
@@ -444,18 +445,12 @@ API 支持返回 Bilibili 标准 XML 格式的弹幕数据,通过查询参数
|
||||
| hanjutv | hanjutv |
|
||||
| bahamut | bahamut |
|
||||
| dandan | dandan |
|
||||
| custom | custom |
|
||||
|
||||
## 项目结构
|
||||
```
|
||||
danmu_api/
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ ├── docker-image.yml # Docker镜像构建和推送工作流
|
||||
│ └── sync_fork.yml # Fork同步上游仓库工作流(vercel自动同步配置文件)
|
||||
├── config/
|
||||
│ ├── .env.example # .env 配置文件示例
|
||||
│ └── config.yaml.example # YAML 配置文件示例(无法编辑 .env 时使用)
|
||||
├── .gitignore
|
||||
├── build-forward-widget.js # 构建forward弹幕插件脚本
|
||||
├── Dockerfile
|
||||
├── edgeone.json # edgeone pages 配置文件
|
||||
├── LICENSE
|
||||
@@ -464,6 +459,9 @@ danmu_api/
|
||||
├── README.md
|
||||
├── vercel.json # vercel 配置文件
|
||||
├── wrangler.toml # cloudflare worker 配置文件
|
||||
├── config/
|
||||
│ ├── .env.example # .env 配置文件示例
|
||||
│ └── config.yaml.example # YAML 配置文件示例(无法编辑 .env 时使用)
|
||||
├── danmu_api/
|
||||
│ ├── esm-shim.js # Node.js低版本兼容层
|
||||
│ ├── server.js # 本地node启动脚本
|
||||
@@ -490,6 +488,7 @@ danmu_api/
|
||||
│ │ ├── bahamut.js # 巴哈姆特源
|
||||
│ │ ├── base.js # 弹幕源获取基类
|
||||
│ │ ├── bilibili.js # b站源
|
||||
│ │ ├── custom.js # 自定义弹幕源
|
||||
│ │ ├── dandan.js # 弹弹play源
|
||||
│ │ ├── douban.js # 豆瓣源
|
||||
│ │ ├── hanjutv.js # 韩剧TV源
|
||||
@@ -530,6 +529,10 @@ danmu_api/
|
||||
│ ├── time-util.js # 时间日期工具
|
||||
│ ├── tmdb-util.js # TMDB API请求处理工具
|
||||
│ └── zh-util.js # 中文繁简转换工具
|
||||
├── forward/
|
||||
│ ├── custom-polyfill.js # 自定义polyfill
|
||||
│ ├── forward-widget.js # forward弹幕插件
|
||||
│ └── forward-widget.test.js # forward弹幕插件测试文件
|
||||
├── netlify/
|
||||
│ └── functions/
|
||||
│ └── api.js # netlify 中间处理逻辑
|
||||
@@ -555,7 +558,9 @@ danmu_api/
|
||||
- 一键部署需要将项目推送到公开的 Git 仓库(如 GitHub),并更新按钮中的仓库地址。
|
||||
- 运行 Docker 容器时,需通过 `-e TOKEN=87654321` 传递 `TOKEN` 环境变量。
|
||||
- cloudflare貌似被哔风控了。
|
||||
- cloudflare貌似有单次请求数量限制,会导致后半部分没有弹幕。
|
||||
- 如果想更换兜底第三方弹幕服务器,请添加环境变量`OTHER_SERVER`,示例`https://api.danmu.icu`。
|
||||
- 如果想使用自定义弹幕源,请添加环境变量`CUSTOM_SOURCE_API_URL`,并在`SOURCE_ORDER`环境变量中添加`custom`源。
|
||||
- 如果想更换vod站点,请添加环境变量`VOD_SERVERS`,示例`金蝉@https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top`(支持多个服务器并发查询)。
|
||||
- 当配置多个VOD站点时,可通过`VOD_RETURN_MODE`环境变量控制返回结果方式:`all`(返回所有站点结果)或`fastest`(默认,只返回最快的站点结果,避免结果过多)。
|
||||
- 推荐vercel/netlify部署,cloudflare/edgeone/claw不稳定,当然最稳定还是自己本地docker部署最佳。
|
||||
|
||||
+6
-2
@@ -30,6 +30,10 @@ TOKEN=87654321
|
||||
# 默认值:https://api.danmu.icu
|
||||
OTHER_SERVER=https://api.danmu.icu
|
||||
|
||||
# 自定义弹幕源API地址
|
||||
# 默认为空,配置后还需在SOURCE_ORDER添加custom源
|
||||
# CUSTOM_SOURCE_API_URL=
|
||||
|
||||
# ==================== VOD 配置 ====================
|
||||
|
||||
# VOD 服务器配置(支持多服务器)
|
||||
@@ -68,7 +72,7 @@ YOUKU_CONCURRENCY=8
|
||||
|
||||
# ==================== 源排序配置 ====================
|
||||
|
||||
# 数据源查询顺序(可选值:360, vod, tmdb, douban, tencent, youku, iqiyi, imgo, bilibili, renren, hanjutv, bahamut, dandan)
|
||||
# 数据源查询顺序(可选值:360, vod, tmdb, douban, tencent, youku, iqiyi, imgo, bilibili, renren, hanjutv, bahamut, dandan, custom)
|
||||
# 默认值:360,vod,renren,hanjutv(不包含 tencent, youku, iqiyi, imgo 和 bilibili)
|
||||
# 如需使用腾讯源、优酷源、爱奇艺源、芒果源或B站源,请手动添加:SOURCE_ORDER=360,vod,tencent,youku,iqiyi,imgo,bilibili,renren,hanjutv
|
||||
# 如需使用巴哈姆特源,请手动添加:SOURCE_ORDER=360,vod,renren,hanjutv,bahamut
|
||||
@@ -81,7 +85,7 @@ SOURCE_ORDER=360,vod,renren,hanjutv
|
||||
|
||||
# 平台优先级排序(用于自动匹配)
|
||||
# 默认值:空(自动匹配)
|
||||
# 可选值:qiyi, bilibili1, imgo, youku, qq, renren, hanjutv, bahamut, dandan
|
||||
# 可选值:qiyi, bilibili1, imgo, youku, qq, renren, hanjutv, bahamut, dandan, custom
|
||||
# PLATFORM_ORDER=
|
||||
|
||||
# ==================== 剧集标题过滤 ====================
|
||||
|
||||
@@ -26,6 +26,10 @@ TOKEN: "87654321"
|
||||
# 默认值:https://api.danmu.icu
|
||||
OTHER_SERVER: "https://api.danmu.icu"
|
||||
|
||||
# 自定义弹幕源API地址
|
||||
# 默认为空,配置后还需在SOURCE_ORDER添加custom源
|
||||
# CUSTOM_SOURCE_API_URL:
|
||||
|
||||
# ==================== VOD 配置 ====================
|
||||
|
||||
# VOD 服务器配置(支持多服务器)
|
||||
@@ -64,7 +68,7 @@ YOUKU_CONCURRENCY: 8
|
||||
|
||||
# ==================== 源排序配置 ====================
|
||||
|
||||
# 数据源查询顺序(可选值:360, vod, tmdb, douban, tencent, youku, iqiyi, imgo, bilibili, renren, hanjutv, bahamut, dandan)
|
||||
# 数据源查询顺序(可选值:360, vod, tmdb, douban, tencent, youku, iqiyi, imgo, bilibili, renren, hanjutv, bahamut, dandan, custom)
|
||||
# 默认值:360,vod,renren,hanjutv(不包含 tencent, youku, iqiyi, imgo 和 bilibili)
|
||||
# 如需使用腾讯源、优酷源、爱奇艺源、芒果源或B站源,请手动添加:SOURCE_ORDER=360,vod,tencent,youku,iqiyi,imgo,bilibili,renren,hanjutv
|
||||
# 如需使用巴哈姆特源,请手动添加:SOURCE_ORDER=360,vod,renren,hanjutv,bahamut
|
||||
@@ -77,7 +81,7 @@ SOURCE_ORDER: "360,vod,renren,hanjutv"
|
||||
|
||||
# 平台优先级排序(用于自动匹配)
|
||||
# 默认值:空(自动匹配)
|
||||
# 可选值:qiyi, bilibili1, imgo, youku, qq, renren, hanjutv, bahamut, dandan
|
||||
# 可选值:qiyi, bilibili1, imgo, youku, qq, renren, hanjutv, bahamut, dandan, custom
|
||||
# PLATFORM_ORDER: ""
|
||||
|
||||
# ==================== 剧集标题过滤 ====================
|
||||
|
||||
@@ -18,6 +18,7 @@ import RenrenSource from "../sources/renren.js";
|
||||
import HanjutvSource from "../sources/hanjutv.js";
|
||||
import BahamutSource from "../sources/bahamut.js";
|
||||
import DandanSource from "../sources/dandan.js";
|
||||
import CustomSource from "../sources/custom.js";
|
||||
import TencentSource from "../sources/tencent.js";
|
||||
import IqiyiSource from "../sources/iqiyi.js";
|
||||
import MangoSource from "../sources/mango.js";
|
||||
@@ -36,6 +37,7 @@ const renrenSource = new RenrenSource();
|
||||
const hanjutvSource = new HanjutvSource();
|
||||
const bahamutSource = new BahamutSource();
|
||||
const dandanSource = new DandanSource();
|
||||
const customSource = new CustomSource();
|
||||
const tencentSource = new TencentSource();
|
||||
const youkuSource = new YoukuSource();
|
||||
const iqiyiSource = new IqiyiSource();
|
||||
@@ -184,6 +186,7 @@ export async function searchAnime(url, preferAnimeId = null, preferSource = null
|
||||
if (source === "hanjutv") return hanjutvSource.search(queryTitle);
|
||||
if (source === "bahamut") return bahamutSource.search(queryTitle);
|
||||
if (source === "dandan") return dandanSource.search(queryTitle);
|
||||
if (source === "custom") return customSource.search(queryTitle);
|
||||
if (source === "tencent") return tencentSource.search(queryTitle);
|
||||
if (source === "youku") return youkuSource.search(queryTitle);
|
||||
if (source === "iqiyi") return iqiyiSource.search(queryTitle);
|
||||
@@ -205,8 +208,8 @@ export async function searchAnime(url, preferAnimeId = null, preferSource = null
|
||||
// 解构出返回的结果
|
||||
const {
|
||||
vod: animesVodResults, 360: animes360, tmdb: animesTmdb, douban: animesDouban, renren: animesRenren,
|
||||
hanjutv: animesHanjutv, bahamut: animesBahamut, dandan: animesDandan, tencent: animesTencent, youku: animesYouku,
|
||||
iqiyi: animesIqiyi, imgo: animesImgo, bilibili: animesBilibili
|
||||
hanjutv: animesHanjutv, bahamut: animesBahamut, dandan: animesDandan, custom: animesCustom,
|
||||
tencent: animesTencent, youku: animesYouku, iqiyi: animesIqiyi, imgo: animesImgo, bilibili: animesBilibili
|
||||
} = resultData;
|
||||
|
||||
// 按顺序处理每个来源的结果
|
||||
@@ -241,6 +244,9 @@ export async function searchAnime(url, preferAnimeId = null, preferSource = null
|
||||
} else if (key === 'dandan') {
|
||||
// 等待处理弹弹play来源
|
||||
await dandanSource.handleAnimes(animesDandan, queryTitle, curAnimes);
|
||||
} else if (key === 'custom') {
|
||||
// 等待处理自定义弹幕源来源
|
||||
await customSource.handleAnimes(animesCustom, queryTitle, curAnimes);
|
||||
} else if (key === 'tencent') {
|
||||
// 等待处理Tencent来源
|
||||
await tencentSource.handleAnimes(animesTencent, queryTitle, curAnimes);
|
||||
@@ -935,6 +941,8 @@ export async function getComment(path, queryFormat, segmentFlag) {
|
||||
danmus = await bahamutSource.getComments(url, plat, segmentFlag);
|
||||
} else if (plat === "dandan") {
|
||||
danmus = await dandanSource.getComments(url, plat, segmentFlag);
|
||||
} else if (plat === "custom") {
|
||||
danmus = await customSource.getComments(url, plat, segmentFlag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1105,6 +1113,8 @@ export async function getSegmentComment(segment, queryFormat) {
|
||||
danmus = await renrenSource.getSegmentComments(segment);
|
||||
} else if (platform === "dandan") {
|
||||
danmus = await dandanSource.getSegmentComments(segment);
|
||||
} else if (platform === "custom") {
|
||||
danmus = await customSource.getSegmentComments(segment);
|
||||
} else if (platform === "other_server") {
|
||||
danmus = await otherSource.getSegmentComments(segment);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ export class Envs {
|
||||
static accessedEnvVars = new Map();
|
||||
|
||||
static VOD_ALLOWED_PLATFORMS = ['qiyi', 'bilibili1', 'imgo', 'youku', 'qq']; // vod允许的播放平台
|
||||
static ALLOWED_PLATFORMS = ['qiyi', 'bilibili1', 'imgo', 'youku', 'qq', 'renren', 'hanjutv', 'bahamut', 'dandan']; // 全部源允许的播放平台
|
||||
static ALLOWED_SOURCES = ['360', 'vod', 'tmdb', 'douban', 'tencent', 'youku', 'iqiyi', 'imgo', 'bilibili', 'renren', 'hanjutv', 'bahamut', 'dandan']; // 允许的源
|
||||
static ALLOWED_PLATFORMS = ['qiyi', 'bilibili1', 'imgo', 'youku', 'qq', 'renren', 'hanjutv', 'bahamut', 'dandan', 'custom']; // 全部源允许的播放平台
|
||||
static ALLOWED_SOURCES = ['360', 'vod', 'tmdb', 'douban', 'tencent', 'youku', 'iqiyi', 'imgo', 'bilibili', 'renren', 'hanjutv', 'bahamut', 'dandan', 'custom']; // 允许的源
|
||||
|
||||
/**
|
||||
* 获取环境变量
|
||||
@@ -206,6 +206,7 @@ export class Envs {
|
||||
// 源配置
|
||||
'SOURCE_ORDER': { category: 'source', type: 'multi-select', options: this.ALLOWED_SOURCES, description: '源排序配置,默认360,vod,renren,hanjutv' },
|
||||
'OTHER_SERVER': { category: 'source', type: 'text', description: '第三方弹幕服务器,默认https://api.danmu.icu' },
|
||||
'CUSTOM_SOURCE_API_URL': { category: 'source', type: 'text', description: '自定义弹幕源API地址,默认为空,配置后还需在SOURCE_ORDER添加custom源' },
|
||||
'VOD_SERVERS': { category: 'source', type: 'text', description: 'VOD站点配置,格式:名称@URL,名称@URL,默认金蝉@https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top' },
|
||||
'VOD_RETURN_MODE': { category: 'source', type: 'select', options: ['all', 'fastest'], description: 'VOD返回模式:all(所有站点)或 fastest(最快的站点),默认fastest' },
|
||||
'VOD_REQUEST_TIMEOUT': { category: 'source', type: 'number', description: 'VOD请求超时时间,默认10000', min: 5000, max: 30000 },
|
||||
@@ -254,6 +255,7 @@ export class Envs {
|
||||
adminToken: this.get('ADMIN_TOKEN', '', 'string', true), // admin token,用于系统管理访问控制
|
||||
sourceOrderArr: this.resolveSourceOrder(), // 源排序
|
||||
otherServer: this.get('OTHER_SERVER', 'https://api.danmu.icu', 'string'), // 第三方弹幕服务器
|
||||
customSourceApiUrl: this.get('CUSTOM_SOURCE_API_URL', '', 'string', true), // 自定义弹幕源API地址,默认为空,配置后还需在SOURCE_ORDER添加custom源
|
||||
vodServers: this.resolveVodServers(), // vod站点配置,格式:名称@URL,名称@URL
|
||||
vodReturnMode: this.get('VOD_RETURN_MODE', 'fastest', 'string').toLowerCase(), // vod返回模式:all(所有站点)或 fastest(最快的站点)
|
||||
vodRequestTimeout: this.get('VOD_REQUEST_TIMEOUT', '10000', 'string'), // vod超时时间(默认10秒)
|
||||
|
||||
@@ -13,7 +13,7 @@ export const Globals = {
|
||||
accessedEnvVars: {},
|
||||
|
||||
// 静态常量
|
||||
VERSION: '1.10.0',
|
||||
VERSION: '1.10.1',
|
||||
MAX_LOGS: 500, // 日志存储,最多保存 500 行
|
||||
MAX_ANIMES: 100,
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import BaseSource from './base.js';
|
||||
import { globals } from '../configs/globals.js';
|
||||
import { log } from "../utils/log-util.js";
|
||||
import { httpGet } from "../utils/http-util.js";
|
||||
import { addAnime, removeEarliestAnime } from "../utils/cache-util.js";
|
||||
import { simplized } from "../utils/zh-util.js";
|
||||
import { SegmentListResponse } from '../models/dandan-model.js';
|
||||
|
||||
// =====================
|
||||
// 获取自定义源弹幕
|
||||
// =====================
|
||||
export default class CustomSource extends BaseSource {
|
||||
async search(keyword) {
|
||||
try {
|
||||
const resp = await httpGet(`${globals.customSourceApiUrl}/api/v2/search/anime?keyword=${keyword}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
},
|
||||
});
|
||||
|
||||
// 判断 resp 和 resp.data 是否存在
|
||||
if (!resp || !resp.data) {
|
||||
log("info", "customSourceSearchresp: 请求失败或无数据返回");
|
||||
return [];
|
||||
}
|
||||
|
||||
// 判断 seriesData 是否存在
|
||||
if (!resp.data.animes) {
|
||||
log("info", "customSourceSearchresp: seriesData 或 seriesList 不存在");
|
||||
return [];
|
||||
}
|
||||
|
||||
// 正常情况下输出 JSON 字符串
|
||||
log("info", `customnSourceSearchresp: ${JSON.stringify(resp.data.animes)}`);
|
||||
|
||||
return resp.data.animes;
|
||||
} catch (error) {
|
||||
// 捕获请求中的错误
|
||||
log("error", "getCustomSourceAnimes error:", {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
stack: error.stack,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getEpisodes(id) {
|
||||
try {
|
||||
const resp = await httpGet(`${globals.customSourceApiUrl}/api/v2/bangumi/${id}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
},
|
||||
});
|
||||
|
||||
// 判断 resp 和 resp.data 是否存在
|
||||
if (!resp || !resp.data) {
|
||||
log("info", "getCustomSourceEposides: 请求失败或无数据返回");
|
||||
return [];
|
||||
}
|
||||
|
||||
// 判断 seriesData 是否存在
|
||||
if (!resp.data.bangumi || !resp.data.bangumi.episodes) {
|
||||
log("info", `getCustomSourceEposides: episodes 不存在. Response: ${JSON.stringify(resp.data)}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// 正常情况下输出 JSON 字符串
|
||||
log("info", `getCustomSourceEposides: ${JSON.stringify(resp.data.bangumi.episodes)}`);
|
||||
|
||||
return resp.data.bangumi.episodes;
|
||||
} catch (error) {
|
||||
// 捕获请求中的错误
|
||||
log("error", "getCustomSourceEposides error:", {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
stack: error.stack,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async handleAnimes(sourceAnimes, queryTitle, curAnimes) {
|
||||
const tmpAnimes = [];
|
||||
|
||||
// 添加错误处理,确保sourceAnimes是数组
|
||||
if (!sourceAnimes || !Array.isArray(sourceAnimes)) {
|
||||
log("error", "[Custom Source] sourceAnimes is not a valid array");
|
||||
return [];
|
||||
}
|
||||
|
||||
// 使用 map 和 async 时需要返回 Promise 数组,并等待所有 Promise 完成
|
||||
const processCustomSourceAnimes = await Promise.all(sourceAnimes
|
||||
.map(async (anime) => {
|
||||
try {
|
||||
const eps = await this.getEpisodes(anime.bangumiId);
|
||||
let links = [];
|
||||
for (const ep of eps) {
|
||||
const epTitle = ep.episodeTitle && ep.episodeTitle.trim() !== "" ? `${ep.episodeTitle}` : `第${ep.episodeNumber}集`;
|
||||
links.push({
|
||||
"name": epTitle,
|
||||
"url": ep.episodeId.toString(),
|
||||
"title": `【custom】 ${epTitle}`
|
||||
});
|
||||
}
|
||||
|
||||
if (links.length > 0) {
|
||||
let transformedAnime = {
|
||||
animeId: anime.animeId,
|
||||
bangumiId: String(anime.bangumiId),
|
||||
animeTitle: `${anime.animeTitle}(${new Date(anime.startDate).getFullYear()})【${anime.typeDescription}】from custom`,
|
||||
type: anime.type,
|
||||
typeDescription: anime.typeDescription,
|
||||
imageUrl: anime.imageUrl,
|
||||
startDate: anime.startDate,
|
||||
episodeCount: links.length,
|
||||
rating: anime.rating,
|
||||
isFavorited: true,
|
||||
source: "custom",
|
||||
};
|
||||
|
||||
tmpAnimes.push(transformedAnime);
|
||||
|
||||
addAnime({...transformedAnime, links: links});
|
||||
|
||||
if (globals.animes.length > globals.MAX_ANIMES) removeEarliestAnime();
|
||||
}
|
||||
} catch (error) {
|
||||
log("error", `[Custom Source] Error processing anime: ${error.message}`);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.sortAndPushAnimesByYear(tmpAnimes, curAnimes);
|
||||
|
||||
return processCustomSourceAnimes;
|
||||
}
|
||||
|
||||
async getEpisodeDanmu(id) {
|
||||
let allDanmus = [];
|
||||
|
||||
try {
|
||||
const resp = await httpGet(`${globals.customSourceApiUrl}/api/v2/comment/${id}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
},
|
||||
retries: 1,
|
||||
});
|
||||
|
||||
// 将当前请求的 episodes 拼接到总数组
|
||||
if (resp.data && resp.data.comments) {
|
||||
allDanmus = resp.data.comments;
|
||||
}
|
||||
|
||||
return allDanmus;
|
||||
} catch (error) {
|
||||
// 捕获请求中的错误
|
||||
log("error", "fetchCustomSourceEpisodeDanmu error:", {
|
||||
message: error.message,
|
||||
name: error.name,
|
||||
stack: error.stack,
|
||||
});
|
||||
return allDanmus; // 返回已收集的 episodes
|
||||
}
|
||||
}
|
||||
|
||||
async getEpisodeDanmuSegments(id) {
|
||||
log("info", "获取Custom Source弹幕分段列表...", id);
|
||||
|
||||
return new SegmentListResponse({
|
||||
"type": "custom",
|
||||
"segmentList": [{
|
||||
"type": "custom",
|
||||
"segment_start": 0,
|
||||
"segment_end": 30000,
|
||||
"url": id
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
async getEpisodeSegmentDanmu(segment) {
|
||||
return this.getEpisodeDanmu(segment.url);
|
||||
}
|
||||
|
||||
formatComments(comments) {
|
||||
return comments.map(c => ({
|
||||
cid: c.cid,
|
||||
p: `${c.p.replace(/([A-Za-z]+)([0-9a-fA-F]{6})/, (_, platform, hexColor) => {
|
||||
// 转换 hexColor 为十进制颜色值
|
||||
const r = parseInt(hexColor.substring(0, 2), 16);
|
||||
const g = parseInt(hexColor.substring(2, 4), 16);
|
||||
const b = parseInt(hexColor.substring(4, 6), 16);
|
||||
const decimalColor = r * 256 * 256 + g * 256 + b;
|
||||
return `${platform}${decimalColor}`;
|
||||
})}`,
|
||||
// 根据 globals.danmuSimplified 控制是否繁转简
|
||||
m: globals.danmuSimplified ? simplized(c.m) : c.m,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import BaseSource from './base.js';
|
||||
import { log } from "../utils/log-util.js";
|
||||
import { buildQueryString, httpGet} from "../utils/http-util.js";
|
||||
import { printFirst200Chars, titleMatches } from "../utils/common-util.js";
|
||||
import { md5, convertToAsciiSum } from "../utils/codec-util.js";
|
||||
import { md5, convertToAsciiSum, decodeHtmlEntities } from "../utils/codec-util.js";
|
||||
import { generateValidStartDate } from "../utils/time-util.js";
|
||||
import { addAnime, removeEarliestAnime } from "../utils/cache-util.js";
|
||||
import { globals } from '../configs/globals.js';
|
||||
@@ -898,7 +898,7 @@ export default class IqiyiSource extends BaseSource {
|
||||
};
|
||||
content.timepoint = parseFloat(item["showTime"]);
|
||||
content.color = parseInt(item["color"], 16);
|
||||
content.content = item["content"];
|
||||
content.content = decodeHtmlEntities(item["content"]);
|
||||
content.size = 25;
|
||||
return content;
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ export const baseCssContent = /* css */ `
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
background: linear-gradient(135deg, #a0b9e8ff 0%, #e39db4ff 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ body {
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
background: linear-gradient(135deg, #1a2980 0%, #26d0ce 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
|
||||
@@ -24,7 +24,8 @@ export const componentsCssContent = /* css */ `
|
||||
|
||||
.nav-btn.active {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
color: #1a2980;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 环境变量样式 */
|
||||
|
||||
@@ -192,6 +192,16 @@ function testApi() {
|
||||
if (config.method === 'GET') {
|
||||
const queryString = new URLSearchParams(params).toString();
|
||||
url = url + '?' + queryString;
|
||||
} else if (config.method === 'POST' && apiKey === 'getSegmentComment') {
|
||||
// 对于 getSegmentComment 接口,需要将 format 参数添加到 URL 查询参数中
|
||||
const queryParams = {};
|
||||
if (params.format) {
|
||||
queryParams.format = params.format;
|
||||
}
|
||||
if (Object.keys(queryParams).length > 0) {
|
||||
const queryString = new URLSearchParams(queryParams).toString();
|
||||
url = url + '?' + queryString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
-14
@@ -118,12 +118,16 @@ let originalToken = '';
|
||||
|
||||
// 构建带token的API请求路径
|
||||
function buildApiUrl(path, isSystemPath = false) {
|
||||
// 如果是系统管理路径且有admin token,则使用admin token
|
||||
let res;
|
||||
let _reverseProxy = "";
|
||||
// 如果是系统管理路径且有admin token,则使用admin token
|
||||
if (isSystemPath && currentAdminToken && currentAdminToken.trim() !== '' && currentAdminToken.trim() !== '*'.repeat(currentAdminToken.length)) {
|
||||
return '/' + currentAdminToken + path;
|
||||
res = '/' + currentAdminToken + path;
|
||||
} else {
|
||||
// 否则使用普通token
|
||||
res = (currentToken ? '/' + currentToken : "") + path;
|
||||
}
|
||||
// 否则使用普通token
|
||||
return (currentToken ? '/' + currentToken : "") + path;
|
||||
return _reverseProxy + res;
|
||||
}
|
||||
|
||||
// 从API加载真实环境变量数据
|
||||
@@ -180,6 +184,8 @@ function updateApiEndpoint() {
|
||||
return fetch(buildApiUrl('/api/config', true))
|
||||
.then(response => response.json())
|
||||
.then(config => {
|
||||
let _reverseProxy = "";
|
||||
|
||||
// 获取当前页面的协议、主机和端口
|
||||
const protocol = window.location.protocol;
|
||||
const host = window.location.host;
|
||||
@@ -187,7 +193,7 @@ function updateApiEndpoint() {
|
||||
const adminToken = config.originalEnvVars?.ADMIN_TOKEN;
|
||||
|
||||
// 获取URL路径并提取token
|
||||
const urlPath = window.location.pathname;
|
||||
const urlPath = window.location.pathname.replace(_reverseProxy, "");
|
||||
const pathParts = urlPath.split('/').filter(part => part !== '');
|
||||
const urlToken = pathParts.length > 0 ? pathParts[0] : '';
|
||||
let apiToken = '********';
|
||||
@@ -204,7 +210,7 @@ function updateApiEndpoint() {
|
||||
}
|
||||
|
||||
// 构造API端点URL
|
||||
const apiEndpoint = protocol + '//' + host + '/' + apiToken;
|
||||
const apiEndpoint = protocol + '//' + host + _reverseProxy + '/' + apiToken;
|
||||
const apiEndpointElement = document.getElementById('api-endpoint');
|
||||
if (apiEndpointElement) {
|
||||
apiEndpointElement.textContent = apiEndpoint;
|
||||
@@ -216,7 +222,7 @@ function updateApiEndpoint() {
|
||||
// 出错时显示默认值
|
||||
const protocol = window.location.protocol;
|
||||
const host = window.location.host;
|
||||
const apiEndpoint = protocol + '//' + host + '/********';
|
||||
const apiEndpoint = protocol + '//' + host + _reverseProxy + '/********';
|
||||
const apiEndpointElement = document.getElementById('api-endpoint');
|
||||
if (apiEndpointElement) {
|
||||
apiEndpointElement.textContent = apiEndpoint;
|
||||
@@ -250,11 +256,13 @@ function getDockerVersion() {
|
||||
}
|
||||
|
||||
// 切换导航
|
||||
function switchSection(section) {
|
||||
function switchSection(section, event = null) {
|
||||
// 检查是否尝试访问受token保护的section(日志查看、接口调试、系统配置需要token访问)
|
||||
if (section === 'logs' || section === 'api' || section === 'env' || section === 'push') {
|
||||
let _reverseProxy = "";
|
||||
|
||||
// 获取URL路径并提取token
|
||||
const urlPath = window.location.pathname;
|
||||
const urlPath = window.location.pathname.replace(_reverseProxy, "");
|
||||
const pathParts = urlPath.split('/').filter(part => part !== '');
|
||||
const urlToken = pathParts.length > 0 ? pathParts[0] : '';
|
||||
|
||||
@@ -285,7 +293,10 @@ function switchSection(section) {
|
||||
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
|
||||
|
||||
document.getElementById(\`\${section}-section\`).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
// 在异步回调中使用传入的event参数来设置按钮的active状态
|
||||
if (event && event.target) {
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
addLog(\`切换到\${section === 'env' ? '环境变量' : section === 'preview' ? '配置预览' : section === 'logs' ? '日志查看' : section === 'push' ? '推送弹幕' : '接口调试'}模块\`, 'info');
|
||||
}
|
||||
@@ -296,7 +307,9 @@ function switchSection(section) {
|
||||
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
|
||||
|
||||
document.getElementById(\`\${section}-section\`).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
if (event && event.target) {
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
addLog(\`切换到\${section === 'env' ? '环境变量' : section === 'preview' ? '配置预览' : section === 'logs' ? '日志查看' : section === 'push' ? '推送弹幕' : '接口调试'}模块\`, 'info');
|
||||
|
||||
@@ -313,17 +326,21 @@ function switchSection(section) {
|
||||
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
|
||||
|
||||
document.getElementById(\`\${section}-section\`).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
if (event && event.target) {
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
addLog(\`切换到\${section === 'env' ? '环境变量' : section === 'preview' ? '配置预览' : section === 'logs' ? '日志查看' : section === 'push' ? '推送弹幕' : '接口调试'}模块\`, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// 切换类别
|
||||
function switchCategory(category) {
|
||||
function switchCategory(category, event = null) {
|
||||
currentCategory = category;
|
||||
document.querySelectorAll('.category-btn').forEach(b => b.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
if (event && event.target) {
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
renderEnvList();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ function renderPreview() {
|
||||
const preview = document.getElementById('preview-area');
|
||||
|
||||
// 从API获取真实配置数据
|
||||
fetch('/api/config')
|
||||
fetch(buildApiUrl('/api/config'))
|
||||
.then(response => response.json())
|
||||
.then(config => {
|
||||
// 使用从API获取的分类环境变量
|
||||
|
||||
@@ -255,8 +255,10 @@ function confirmDeploymentByLogs() {
|
||||
|
||||
// 检查URL中的token是否与currentAdminToken匹配
|
||||
function checkAdminToken() {
|
||||
let _reverseProxy = "";
|
||||
|
||||
// 获取URL路径并提取token
|
||||
const urlPath = window.location.pathname;
|
||||
const urlPath = window.location.pathname.replace(_reverseProxy, "");
|
||||
const pathParts = urlPath.split('/').filter(part => part !== '');
|
||||
const urlToken = pathParts.length > 0 ? pathParts[0] : currentToken; // 如果没有路径段,使用默认token
|
||||
|
||||
|
||||
+11
-11
@@ -49,11 +49,11 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-buttons">
|
||||
<button class="nav-btn active" onclick="switchSection('preview')">配置预览</button>
|
||||
<button class="nav-btn" onclick="switchSection('logs')">日志查看</button>
|
||||
<button class="nav-btn" onclick="switchSection('api')">接口调试</button>
|
||||
<button class="nav-btn" onclick="switchSection('push')">推送弹幕</button>
|
||||
<button class="nav-btn" onclick="switchSection('env')" id="env-nav-btn">系统配置</button>
|
||||
<button class="nav-btn active" onclick="switchSection('preview', event)">配置预览</button>
|
||||
<button class="nav-btn" onclick="switchSection('logs', event)">日志查看</button>
|
||||
<button class="nav-btn" onclick="switchSection('api', event)">接口调试</button>
|
||||
<button class="nav-btn" onclick="switchSection('push', event)">推送弹幕</button>
|
||||
<button class="nav-btn" onclick="switchSection('env', event)" id="env-nav-btn">系统配置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -200,12 +200,12 @@ export const HTML_TEMPLATE = /* html */ `
|
||||
</div>
|
||||
|
||||
<div class="env-categories">
|
||||
<button class="category-btn active" onclick="switchCategory('api')">🔗 API配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('source')">📜 源配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('match')">🔍 匹配配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('danmu')">🔣 弹幕配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('cache')">💾 缓存配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('system')">⚙️ 系统配置</button>
|
||||
<button class="category-btn active" onclick="switchCategory('api', event)">🔗 API配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('source', event)">📜 源配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('match', event)">🔍 匹配配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('danmu', event)">🔣 弹幕配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('cache', event)">💾 缓存配置</button>
|
||||
<button class="category-btn" onclick="switchCategory('system', event)">⚙️ 系统配置</button>
|
||||
</div>
|
||||
|
||||
<div class="env-list" id="env-list"></div>
|
||||
|
||||
@@ -714,7 +714,7 @@ export function generateSign(path, timestamp, params, secretKey) {
|
||||
|
||||
signStr += secretKey;
|
||||
return md5(signStr);
|
||||
}
|
||||
}
|
||||
|
||||
// ========== X-CA-Sign 生成函数 ==========
|
||||
export function generateXCaSign(path, timestamp, queryString, secretKey) {
|
||||
@@ -722,4 +722,34 @@ export function generateXCaSign(path, timestamp, queryString, secretKey) {
|
||||
if (queryString) signStr += `?${queryString}`;
|
||||
|
||||
return createHmacSha256(secretKey, signStr);
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容版本的 fromCodePoint
|
||||
function fromCodePoint(codePoint) {
|
||||
// BMP 范围内的字符可以直接用 fromCharCode
|
||||
if (codePoint <= 0xFFFF) {
|
||||
return String.fromCharCode(codePoint);
|
||||
}
|
||||
|
||||
// 超出 BMP 的字符需要转换为代理对
|
||||
codePoint -= 0x10000;
|
||||
const highSurrogate = (codePoint >> 10) + 0xD800;
|
||||
const lowSurrogate = (codePoint & 0x3FF) + 0xDC00;
|
||||
return String.fromCharCode(highSurrogate, lowSurrogate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将HTML实体转换为实际字符
|
||||
* @param {string} str - 包含HTML实体的字符串
|
||||
* @returns {string} 转换后的字符串
|
||||
*/
|
||||
export function decodeHtmlEntities(str) {
|
||||
// 处理十进制HTML实体 〹
|
||||
return str.replace(/&#(\d+);/g, (match, num) => {
|
||||
return fromCodePoint(parseInt(num, 10));
|
||||
})
|
||||
// 处理十六进制HTML实体 ሺ
|
||||
.replace(/&#x([0-9a-fA-F]+);/g, (match, hex) => {
|
||||
return fromCodePoint(parseInt(hex, 16));
|
||||
});
|
||||
}
|
||||
+25
-13
@@ -16,7 +16,7 @@ WidgetMetadata = {
|
||||
// 源配置
|
||||
{
|
||||
name: "sourceOrder",
|
||||
title: "源排序配置,默认'360,vod,renren,hanjutv',可选['360', 'vod', 'tmdb', 'douban', 'tencent', 'youku', 'iqiyi', 'imgo', 'bilibili', 'renren', 'hanjutv', 'bahamut', 'dandan']",
|
||||
title: "源排序配置,默认'360,vod,renren,hanjutv',可选['360', 'vod', 'tmdb', 'douban', 'tencent', 'youku', 'iqiyi', 'imgo', 'bilibili', 'renren', 'hanjutv', 'bahamut', 'dandan', 'custom']",
|
||||
type: "input",
|
||||
placeholders: [
|
||||
{
|
||||
@@ -68,6 +68,17 @@ WidgetMetadata = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "customSourceApiUrl",
|
||||
title: "自定义弹幕源API地址,默认为空,配置后还需在SOURCE_ORDER添加custom源",
|
||||
type: "input",
|
||||
placeholders: [
|
||||
{
|
||||
title: "自定义",
|
||||
value: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "vodServers",
|
||||
title: "VOD站点配置,格式:名称@URL,名称@URL,默认金蝉'https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top'",
|
||||
@@ -140,12 +151,12 @@ WidgetMetadata = {
|
||||
// 匹配配置
|
||||
{
|
||||
name: "platformOrder",
|
||||
title: "平台优选配置,可选['qiyi', 'bilibili1', 'imgo', 'youku', 'qq', 'renren', 'hanjutv', 'bahamut', 'dandan']",
|
||||
title: "平台优选配置,可选['qiyi', 'bilibili1', 'imgo', 'youku', 'qq', 'renren', 'hanjutv', 'bahamut', 'dandan', 'custom']",
|
||||
type: "input",
|
||||
placeholders: [
|
||||
{
|
||||
title: "配置1",
|
||||
value: "qq,qiyi,imgo,bilibili1,youku,renren,hanjutv,bahamut,dandan",
|
||||
value: "qq,qiyi,imgo,bilibili1,youku,renren,hanjutv,bahamut,dandan,custom",
|
||||
},
|
||||
{
|
||||
title: "配置2",
|
||||
@@ -410,7 +421,7 @@ if (typeof window !== 'undefined') {
|
||||
|
||||
// 初始化全局配置
|
||||
let globals;
|
||||
async function initGlobals(sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
async function initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey) {
|
||||
// 将传入的参数设置到环境变量中,以便Globals可以访问它们
|
||||
@@ -418,6 +429,7 @@ async function initGlobals(sourceOrder, otherServer, vodServers, vodReturnMode,
|
||||
|
||||
if (sourceOrder !== undefined) env.SOURCE_ORDER = sourceOrder;
|
||||
if (otherServer !== undefined) env.OTHER_SERVER = otherServer;
|
||||
if (customSourceApiUrl !== undefined) env.CUSTOM_SOURCE_API_URL = customSourceApiUrl;
|
||||
if (vodServers !== undefined) env.VOD_SERVERS = vodServers;
|
||||
if (vodReturnMode !== undefined) env.VOD_RETURN_MODE = vodReturnMode;
|
||||
if (vodRequestTimeout !== undefined) env.VOD_REQUEST_TIMEOUT = vodRequestTimeout;
|
||||
@@ -498,11 +510,11 @@ async function removeCaches() {
|
||||
const PREFIX_URL = "http://localhost:9321"
|
||||
|
||||
async function searchDanmu(params) {
|
||||
const { tmdbId, type, title, season, link, videoUrl, sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
const { tmdbId, type, title, season, link, videoUrl, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey } = params;
|
||||
|
||||
await initGlobals(sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey);
|
||||
|
||||
@@ -571,11 +583,11 @@ async function searchDanmu(params) {
|
||||
}
|
||||
|
||||
async function getDetailById(params) {
|
||||
const { animeId, sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
const { animeId, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey } = params;
|
||||
|
||||
await initGlobals(sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey);
|
||||
|
||||
@@ -590,11 +602,11 @@ async function getDetailById(params) {
|
||||
}
|
||||
|
||||
async function getCommentsById(params) {
|
||||
const { commentId, link, videoUrl, season, episode, tmdbId, type, title, segmentTime, sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
const { commentId, link, videoUrl, season, episode, tmdbId, type, title, segmentTime, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey } = params;
|
||||
|
||||
await initGlobals(sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey);
|
||||
|
||||
@@ -611,7 +623,7 @@ async function getCommentsById(params) {
|
||||
log("info", "segmentList:", segmentList);
|
||||
|
||||
if (lastCommentId === commentId && segmentList) {
|
||||
return await getDanmuWithSegmentTime({ segmentTime, tmdbId, season, episode, otherServer, vodServers, bilibiliCookie, sourceOrder, blockedWords, groupMinute,
|
||||
return await getDanmuWithSegmentTime({ segmentTime, tmdbId, season, episode, otherServer, customSourceApiUrl, vodServers, bilibiliCookie, sourceOrder, blockedWords, groupMinute,
|
||||
vodReturnMode, vodRequestTimeout, platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey })
|
||||
} else {
|
||||
@@ -637,11 +649,11 @@ async function getCommentsById(params) {
|
||||
}
|
||||
|
||||
async function getDanmuWithSegmentTime(params) {
|
||||
const { segmentTime, tmdbId, season, episode, sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
const { segmentTime, tmdbId, season, episode, sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey } = params;
|
||||
|
||||
await initGlobals(sourceOrder, otherServer, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
await initGlobals(sourceOrder, otherServer, customSourceApiUrl, vodServers, vodReturnMode, vodRequestTimeout, bilibiliCookie,
|
||||
platformOrder, episodeTitleFilter, enableEpisodeFilter, strictTitleMatch, blockedWords, groupMinute,
|
||||
danmuLimit, danmuSimplified, convertTopBottomToScroll, convertColor, proxyUrl, tmdbApiKey);
|
||||
|
||||
|
||||
@@ -218,6 +218,7 @@ async function testNewFlow() {
|
||||
episodeName: '第2期上:首次大约会!温柔医生为爱冲锋',
|
||||
sourceOrder: 'douban',
|
||||
otherServer: 'https://api.danmu.icu',
|
||||
customSourceApiUrl: '',
|
||||
vodServers: '金蝉@https://zy.jinchancaiji.com,789@https://www.caiji.cyou,听风@https://gctf.tfdh.top',
|
||||
vodReturnMode: 'fastest',
|
||||
vodRequestTimeout: 10000,
|
||||
|
||||
Reference in new issue
Block a user