feat: 添加全局控制解析

This commit is contained in:
zhiyu1998 2024-11-25 15:24:49 +08:00
parent ea3c4ab85d
commit 6cb29f8012
11 changed files with 405 additions and 21 deletions

View File

@ -1,6 +1,6 @@
import config from "../model/config.js";
import schedule from 'node-schedule'; import schedule from 'node-schedule';
import { REDIS_YUNZAI_ISOVERSEA, REDIS_YUNZAI_WHITELIST } from "../constants/constant.js"; import { REDIS_YUNZAI_ISOVERSEA, REDIS_YUNZAI_WHITELIST } from "../constants/constant.js";
import config from "../model/config.js";
import { deleteFolderRecursive, readCurrentDir } from "../utils/file.js"; import { deleteFolderRecursive, readCurrentDir } from "../utils/file.js";
import { redisExistAndGetKey, redisGetKey, redisSetKey } from "../utils/redis-util.js"; import { redisExistAndGetKey, redisGetKey, redisSetKey } from "../utils/redis-util.js";

View File

@ -27,6 +27,7 @@ import {
TWITTER_BEARER_TOKEN, TWITTER_BEARER_TOKEN,
XHS_NO_WATERMARK_HEADER XHS_NO_WATERMARK_HEADER
} from "../constants/constant.js"; } from "../constants/constant.js";
import { REDIS_YUNZAI_RESOLVE_CONTROLLER, RESOLVE_CONTROLLER_NAME_ENUM } from "../constants/resolve.js";
import { import {
ANIME_SERIES_SEARCH_LINK, ANIME_SERIES_SEARCH_LINK,
ANIME_SERIES_SEARCH_LINK2, ANIME_SERIES_SEARCH_LINK2,
@ -99,7 +100,7 @@ import { contentEstimator } from "../utils/link-share-summary-util.js";
import { deepSeekChat, llmRead } from "../utils/llm-util.js"; import { deepSeekChat, llmRead } from "../utils/llm-util.js";
import { getDS } from "../utils/mihoyo.js"; import { getDS } from "../utils/mihoyo.js";
import { OpenaiBuilder } from "../utils/openai-builder.js"; import { OpenaiBuilder } from "../utils/openai-builder.js";
import { redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js"; import { redisExistAndGetKey, redisExistKey, redisGetKey, redisSetKey } from "../utils/redis-util.js";
import { saveTDL, startTDL } from "../utils/tdl-util.js"; import { saveTDL, startTDL } from "../utils/tdl-util.js";
import { genVerifyFp } from "../utils/tiktok.js"; import { genVerifyFp } from "../utils/tiktok.js";
import Translate from "../utils/trans-strategy.js"; import Translate from "../utils/trans-strategy.js";
@ -341,6 +342,11 @@ export class tools extends plugin {
// 抖音解析 // 抖音解析
async douyin(e) { async douyin(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.douyin))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.douyin } 已拦截`);
return true;
}
const urlRex = /(http:\/\/|https:\/\/)(v|live).douyin.com\/[A-Za-z\d._?%&+\-=\/#]*/; const urlRex = /(http:\/\/|https:\/\/)(v|live).douyin.com\/[A-Za-z\d._?%&+\-=\/#]*/;
// 检测无效链接例如v.douyin.com // 检测无效链接例如v.douyin.com
if (!urlRex.test(e.msg)) { if (!urlRex.test(e.msg)) {
@ -610,6 +616,11 @@ export class tools extends plugin {
// tiktok解析 // tiktok解析
async tiktok(e) { async tiktok(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.tiktok))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.tiktok } 已拦截`);
return true;
}
// 判断海外 // 判断海外
const isOversea = await this.isOverseasServer(); const isOversea = await this.isOverseasServer();
// 如果不是海外用户且没有梯子直接返回 // 如果不是海外用户且没有梯子直接返回
@ -712,6 +723,11 @@ export class tools extends plugin {
// B 站解析 // B 站解析
async bili(e) { async bili(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.bili))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.bili } 已拦截`);
return true;
}
const urlRex = /(?:https?:\/\/)?www\.bilibili\.com\/[A-Za-z\d._?%&+\-=\/#]*/g; const urlRex = /(?:https?:\/\/)?www\.bilibili\.com\/[A-Za-z\d._?%&+\-=\/#]*/g;
const bShortRex = /(http:|https:)\/\/b23.tv\/[A-Za-z\d._?%&+\-=\/#]*/g; const bShortRex = /(http:|https:)\/\/b23.tv\/[A-Za-z\d._?%&+\-=\/#]*/g;
let url = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim().replaceAll("\\", ""); let url = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim().replaceAll("\\", "");
@ -1232,6 +1248,11 @@ export class tools extends plugin {
// 使用现有api解析小蓝鸟 // 使用现有api解析小蓝鸟
async twitter_x(e) { async twitter_x(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.twitter_x))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.twitter_x } 已拦截`);
return true;
}
if (!(await this.isTrustUser(e.user_id))) { if (!(await this.isTrustUser(e.user_id))) {
e.reply("你没有权限使用此命令"); e.reply("你没有权限使用此命令");
return; return;
@ -1297,6 +1318,11 @@ export class tools extends plugin {
// acfun解析 // acfun解析
async acfun(e) { async acfun(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.acfun))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.acfun } 已拦截`);
return true;
}
const path = `${ this.getCurDownloadPath(e) }/temp/`; const path = `${ this.getCurDownloadPath(e) }/temp/`;
await mkdirIfNotExists(path); await mkdirIfNotExists(path);
@ -1324,6 +1350,11 @@ export class tools extends plugin {
// 小红书解析 // 小红书解析
async xhs(e) { async xhs(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.xhs))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.xhs } 已拦截`);
return true;
}
// 正则说明匹配手机链接、匹配小程序、匹配PC链接 // 正则说明匹配手机链接、匹配小程序、匹配PC链接
let msgUrl = let msgUrl =
/(http:|https:)\/\/(xhslink|xiaohongshu).com\/[A-Za-z\d._?%&+\-=\/#@]*/.exec( /(http:|https:)\/\/(xhslink|xiaohongshu).com\/[A-Za-z\d._?%&+\-=\/#@]*/.exec(
@ -1447,6 +1478,11 @@ export class tools extends plugin {
// 波点音乐解析 // 波点音乐解析
async bodianMusic(e) { async bodianMusic(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.bodianMusic))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.bodianMusic } 已拦截`);
return true;
}
// 音频例子https://h5app.kuwo.cn/m/bodian/playMusic.html?uid=3216773&musicId=192015898&opusId=&extendType=together // 音频例子https://h5app.kuwo.cn/m/bodian/playMusic.html?uid=3216773&musicId=192015898&opusId=&extendType=together
// 视频例子https://h5app.kuwo.cn/m/bodian/play.html?uid=3216773&mvId=118987&opusId=770096&extendType=together // 视频例子https://h5app.kuwo.cn/m/bodian/play.html?uid=3216773&mvId=118987&opusId=770096&extendType=together
const id = const id =
@ -1644,6 +1680,11 @@ export class tools extends plugin {
// 网易云解析 // 网易云解析
async netease(e) { async netease(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.netease))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.netease } 已拦截`);
return true;
}
let message = let message =
e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim();
// 处理短号此时会变成y.music.163.com // 处理短号此时会变成y.music.163.com
@ -1901,6 +1942,11 @@ export class tools extends plugin {
// 微博解析 // 微博解析
async weibo(e) { async weibo(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.weibo))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.weibo } 已拦截`);
return true;
}
let weiboId; let weiboId;
const weiboUrl = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim().replaceAll("\\", ""); const weiboUrl = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim().replaceAll("\\", "");
// 对已知情况进行判断 // 对已知情况进行判断
@ -1994,6 +2040,11 @@ export class tools extends plugin {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
async general(e) { async general(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.general))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.general } 已拦截`);
return true;
}
try { try {
const adapter = await GeneralLinkAdapter.create(e.msg); const adapter = await GeneralLinkAdapter.create(e.msg);
e.reply(`${ this.identifyPrefix }识别:${ adapter.name }${ adapter.desc ? `, ${ adapter.desc }` : '' }`); e.reply(`${ this.identifyPrefix }识别:${ adapter.name }${ adapter.desc ? `, ${ adapter.desc }` : '' }`);
@ -2026,6 +2077,11 @@ export class tools extends plugin {
// 油管解析 // 油管解析
async sy2b(e) { async sy2b(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.sy2b))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.sy2b } 已拦截`);
return true;
}
const timeRange = ytbFormatTime(this.youtubeClipTime); const timeRange = ytbFormatTime(this.youtubeClipTime);
const isOversea = await this.isOverseasServer(); const isOversea = await this.isOverseasServer();
if (!isOversea && !(await testProxy(this.proxyAddr, this.proxyPort))) { if (!isOversea && !(await testProxy(this.proxyAddr, this.proxyPort))) {
@ -2098,6 +2154,11 @@ export class tools extends plugin {
// 米游社 // 米游社
async miyoushe(e) { async miyoushe(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.miyoushe))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.miyoushe } 已拦截`);
return true;
}
let url = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); let url = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim();
let msg = /(?:https?:\/\/)?(m|www)\.miyoushe\.com\/[A-Za-z\d._?%&+\-=\/#]*/.exec(url)?.[0]; let msg = /(?:https?:\/\/)?(m|www)\.miyoushe\.com\/[A-Za-z\d._?%&+\-=\/#]*/.exec(url)?.[0];
const id = /\/(\d+)$/.exec(msg)?.[0].replace("\/", ""); const id = /\/(\d+)$/.exec(msg)?.[0].replace("\/", "");
@ -2169,6 +2230,11 @@ export class tools extends plugin {
// 微视 // 微视
async weishi(e) { async weishi(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.weishi))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.weishi } 已拦截`);
return true;
}
let url = e.msg; let url = e.msg;
const urlRegex = /https?:\/\/video\.weishi\.qq\.com\/\S+/g; const urlRegex = /https?:\/\/video\.weishi\.qq\.com\/\S+/g;
// 执行匹配 // 执行匹配
@ -2216,6 +2282,11 @@ export class tools extends plugin {
} }
async zuiyou(e) { async zuiyou(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.zuiyou))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.zuiyou } 已拦截`);
return true;
}
// #最右#分享一条有趣的内容给你,不好看算我输。请戳链接>>https://share.xiaochuankeji.cn/hybrid/share/post?pid=365367131&zy_to=applink&share_count=1&m=dc114ccc8e55492642f6a702b510c1f6&d=9e18ca2dace030af656baea96321e0ea353fe5c46097a7f3962b93f995641e962796dd5faa231feea5531ac65547045f&app=zuiyou&recommend=r0&name=n0&title_type=t0 // #最右#分享一条有趣的内容给你,不好看算我输。请戳链接>>https://share.xiaochuankeji.cn/hybrid/share/post?pid=365367131&zy_to=applink&share_count=1&m=dc114ccc8e55492642f6a702b510c1f6&d=9e18ca2dace030af656baea96321e0ea353fe5c46097a7f3962b93f995641e962796dd5faa231feea5531ac65547045f&app=zuiyou&recommend=r0&name=n0&title_type=t0
let msg = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim(); let msg = e.msg === undefined ? e.message.shift().data.replaceAll("\\", "") : e.msg.trim();
const url = /(?:https?:\/\/)?(share|share.xiaochuankeji)\.cn\/[A-Za-z\d._?%&+\-=\/#]*/.exec(msg)[0]; const url = /(?:https?:\/\/)?(share|share.xiaochuankeji)\.cn\/[A-Za-z\d._?%&+\-=\/#]*/.exec(msg)[0];
@ -2279,6 +2350,11 @@ export class tools extends plugin {
} }
async freyr(e) { async freyr(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.freyr))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.freyr } 已拦截`);
return true;
}
// https://music.apple.com/cn/album/hectopascal-from-yagate-kimi-ni-naru-piano-arrangement/1468323115?i=1468323724 // https://music.apple.com/cn/album/hectopascal-from-yagate-kimi-ni-naru-piano-arrangement/1468323115?i=1468323724
// 过滤参数 // 过滤参数
const message = e.msg.replace("&ls", ""); const message = e.msg.replace("&ls", "");
@ -2415,6 +2491,11 @@ export class tools extends plugin {
// q q m u s i c 解析 // q q m u s i c 解析
async qqMusic(e) { async qqMusic(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.qqMusic))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.qqMusic } 已拦截`);
return true;
}
// case1: Taylor Swift/Bleachers《Anti-Hero (Feat. Bleachers) (Explicit)》 https://c6.y.qq.com/base/fcgi-bin/u?__=lg19lFgQerbo @QQ音乐 // case1: Taylor Swift/Bleachers《Anti-Hero (Feat. Bleachers) (Explicit)》 https://c6.y.qq.com/base/fcgi-bin/u?__=lg19lFgQerbo @QQ音乐
/** case 2: /** case 2:
* {"app":"com.tencent.structmsg","config":{"ctime":1722497864,"forward":1,"token":"987908ab4a1c566d3645ef0ca52a162a","type":"normal"},"extra":{"app_type":1,"appid":100497308,"uin":542716863},"meta":{"news":{"action":"","android_pkg_name":"","app_type":1,"appid":100497308,"ctime":1722497864,"desc":"Taylor Swift/Bleachers","jumpUrl":"https://i.y.qq.com/v8/playsong.html?hosteuin=7KvA7i6sNeCi&sharefrom=gedan&from_id=1674373010&from_idtype=10014&from_name=(7rpl)&songid=382775503&songmid=&type=0&platform=1&appsongtype=1&_wv=1&source=qq&appshare=iphone&media_mid=000dKYJS3KCzpu&ADTAG=qfshare","preview":"https://pic.ugcimg.cn/1070bf5a6962b75263eee1404953c9b2/jpg1","source_icon":"https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0","source_url":"","tag":"QQ音乐","title":"Anti-Hero (Feat. Bleachers) (E…","uin":542716863}},"prompt":"[分享]Anti-Hero (Feat. Bleachers) (E…","ver":"0.0.0.1","view":"news"} * {"app":"com.tencent.structmsg","config":{"ctime":1722497864,"forward":1,"token":"987908ab4a1c566d3645ef0ca52a162a","type":"normal"},"extra":{"app_type":1,"appid":100497308,"uin":542716863},"meta":{"news":{"action":"","android_pkg_name":"","app_type":1,"appid":100497308,"ctime":1722497864,"desc":"Taylor Swift/Bleachers","jumpUrl":"https://i.y.qq.com/v8/playsong.html?hosteuin=7KvA7i6sNeCi&sharefrom=gedan&from_id=1674373010&from_idtype=10014&from_name=(7rpl)&songid=382775503&songmid=&type=0&platform=1&appsongtype=1&_wv=1&source=qq&appshare=iphone&media_mid=000dKYJS3KCzpu&ADTAG=qfshare","preview":"https://pic.ugcimg.cn/1070bf5a6962b75263eee1404953c9b2/jpg1","source_icon":"https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0","source_url":"","tag":"QQ音乐","title":"Anti-Hero (Feat. Bleachers) (E…","uin":542716863}},"prompt":"[分享]Anti-Hero (Feat. Bleachers) (E…","ver":"0.0.0.1","view":"news"}
@ -2461,6 +2542,11 @@ export class tools extends plugin {
// 汽水音乐 // 汽水音乐
async qishuiMusic(e) { async qishuiMusic(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.qishuiMusic))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.qishuiMusic } 已拦截`);
return true;
}
const normalRegex = /^(.*?)\s*https?:\/\//; const normalRegex = /^(.*?)\s*https?:\/\//;
const musicInfo = normalRegex.exec(e.msg)?.[1].trim().replace("@汽水音乐", ""); const musicInfo = normalRegex.exec(e.msg)?.[1].trim().replace("@汽水音乐", "");
logger.info(`[R插件][qishuiMusic] 识别音乐为:${ musicInfo }`); logger.info(`[R插件][qishuiMusic] 识别音乐为:${ musicInfo }`);
@ -2483,6 +2569,11 @@ export class tools extends plugin {
// 小飞机下载 // 小飞机下载
async aircraft(e) { async aircraft(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.aircraft))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.aircraft } 已拦截`);
return true;
}
if (!(await this.isTrustUser(e.user_id))) { if (!(await this.isTrustUser(e.user_id))) {
e.reply("你没有权限使用此命令"); e.reply("你没有权限使用此命令");
return; return;
@ -2539,6 +2630,11 @@ export class tools extends plugin {
// 贴吧 // 贴吧
async tieba(e) { async tieba(e) {
// 切面判断是否需要解析
if (!(await this.isEnableResolve(RESOLVE_CONTROLLER_NAME_ENUM.tieba))) {
logger.info(`[R插件][全局解析控制] ${ RESOLVE_CONTROLLER_NAME_ENUM.tieba } 已拦截`);
return true;
}
// 提取链接和ID // 提取链接和ID
const msg = /https:\/\/tieba\.baidu\.com\/p\/[A-Za-z0-9]+/.exec(e.msg)?.[0]; const msg = /https:\/\/tieba\.baidu\.com\/p\/[A-Za-z0-9]+/.exec(e.msg)?.[0];
const id = /\/p\/([A-Za-z0-9]+)/.exec(msg)?.[1]; const id = /\/p\/([A-Za-z0-9]+)/.exec(msg)?.[1];
@ -3022,6 +3118,26 @@ export class tools extends plugin {
} }
} }
/**
* 判断是否启用解析
* @param resolveName
* @returns {Promise<boolean>}
*/
async isEnableResolve(resolveName) {
const controller = await redisExistAndGetKey(REDIS_YUNZAI_RESOLVE_CONTROLLER);
// 如果不存在说明用户没有启动过webui那么直接放行
if (controller == null) {
return true;
}
const foundItem = controller.find(item => item.label === resolveName);
// 未知解析,可能是写错,放行
if (!foundItem) {
logger.warn(`[R插件][启用解析] 未知解析,可能存在写错`);
return true;
}
return foundItem.value === 1;
}
/** /**
* 判断是否是海外服务器 * 判断是否是海外服务器
* @return {Promise<Boolean>} * @return {Promise<Boolean>}

23
constants/resolve.js Normal file
View File

@ -0,0 +1,23 @@
export const REDIS_YUNZAI_RESOLVE_CONTROLLER = "Yz:rconsole:resolve:controller"
export const RESOLVE_CONTROLLER_NAME_ENUM = {
"douyin": "抖音",
"bili": "哔哩哔哩",
"tiktok": "TikTok",
"twitter_x": "Twitter",
"acfun": "Acfun",
"xhs": "小红书",
"bodianMusic": "波点",
"general": "通用(包含快手等)",
"sy2b": "YouTube",
"miyoushe": "米游社",
"netease": "网易云音乐",
"weibo": "微博",
"weishi": "微视",
"zuiyou": "最右",
"freyr": "AM+Spotify",
"qqMusic": "扣扣音乐",
"qishuiMusic": "汽水音乐",
"aircraft": "小飞机",
"tieba": "贴吧",
}

View File

@ -0,0 +1,49 @@
import { REDIS_RESOLVE_CONTROLLER } from "../../../../constants/redis.js";
import { GLOBAL_RESOLE_CONTROLLER } from "../../../../constants/resolve.js";
import { redis } from "../../../../utils/redis.js";
export async function GET(req, res) {
let resolveList = await redis.get(REDIS_RESOLVE_CONTROLLER);
if (resolveList == null) {
// Redis中不存在就初始化进去
await redis.set(REDIS_RESOLVE_CONTROLLER, JSON.stringify(GLOBAL_RESOLE_CONTROLLER));
return new Response(JSON.stringify(GLOBAL_RESOLE_CONTROLLER), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
return new Response(resolveList, {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
export async function POST(req) {
try {
const data = await req.json();
const { selectedTags } = data;
// 获取所有可能的标签
const allTags = GLOBAL_RESOLE_CONTROLLER.map(item => item.label);
// 更新控制器状态
const updatedController = GLOBAL_RESOLE_CONTROLLER.map(item => ({
...item,
value: selectedTags.includes(item.label) ? 1 : 0
}));
// 保存到Redis
await redis.set(REDIS_RESOLVE_CONTROLLER, JSON.stringify(updatedController));
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
return new Response(JSON.stringify({ success: false, error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}

View File

@ -2,6 +2,7 @@ import { exec } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import fs from 'fs/promises'; import fs from 'fs/promises';
import path from 'path'; import path from 'path';
import { REDIS_UPDATE_PATH, REDIS_UPDATE_STATUS } from "../../../../constants/redis.js";
import { redis } from "../../../../utils/redis.js"; import { redis } from "../../../../utils/redis.js";
const execAsync = promisify(exec); const execAsync = promisify(exec);
@ -20,7 +21,7 @@ function handleGitError(error, stderr) {
if (error.message.includes('Timed out')) { if (error.message.includes('Timed out')) {
return '连接超时,请检查网络后重试'; return '连接超时,请检查网络后重试';
} }
if (error.message.includes('Could not resolve host')) { if (error.message.includes('Could not resolveControl host')) {
return '无法解析主机地址,请检查网络'; return '无法解析主机地址,请检查网络';
} }
if (error.message.includes('Permission denied')) { if (error.message.includes('Permission denied')) {
@ -60,8 +61,8 @@ async function cleanupUpdate(tempDir) {
// 清理临时文件 // 清理临时文件
await fs.rm(tempDir, { recursive: true, force: true }); await fs.rm(tempDir, { recursive: true, force: true });
// 清理Redis中的更新状态 // 清理Redis中的更新状态
await redis.del('rconsole:update:status'); await redis.del(REDIS_UPDATE_STATUS);
await redis.del('rconsole:update:paths'); await redis.del(REDIS_UPDATE_PATH);
console.log('清理完成'); console.log('清理完成');
} catch (error) { } catch (error) {
console.error('清理失败:', error); console.error('清理失败:', error);
@ -77,7 +78,7 @@ export async function GET(req) {
// 如果是检查请求 // 如果是检查请求
if (isCheck) { if (isCheck) {
const updateStatus = await redis.get('rconsole:update:status'); const updateStatus = await redis.get(REDIS_UPDATE_STATUS);
return new Response(JSON.stringify({ return new Response(JSON.stringify({
needsRestore: updateStatus === 'restoring' needsRestore: updateStatus === 'restoring'
}), { }), {
@ -87,8 +88,8 @@ export async function GET(req) {
// 如果是恢复请求 // 如果是恢复请求
if (isRestore) { if (isRestore) {
const updateStatus = await redis.get('rconsole:update:status'); const updateStatus = await redis.get(REDIS_UPDATE_STATUS);
const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}'); const paths = JSON.parse(await redis.get(REDIS_UPDATE_PATH) || '{}');
if (updateStatus === 'restoring' && paths.tempDir && paths.configDir) { if (updateStatus === 'restoring' && paths.tempDir && paths.configDir) {
try { try {
@ -123,10 +124,10 @@ export async function GET(req) {
const configDir = path.join(projectRoot, 'config'); const configDir = path.join(projectRoot, 'config');
// 检查是否有未完成的更新 // 检查是否有未完成的更新
const updateStatus = await redis.get('rconsole:update:status'); const updateStatus = await redis.get(REDIS_UPDATE_STATUS);
if (updateStatus === 'restoring') { if (updateStatus === 'restoring') {
// 如果有未完成的更新,尝试恢复 // 如果有未完成的更新,尝试恢复
const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}'); const paths = JSON.parse(await redis.get(REDIS_UPDATE_PATH) || '{}');
if (paths.tempDir && paths.configDir) { if (paths.tempDir && paths.configDir) {
try { try {
await copyConfig(paths.tempDir, paths.configDir); await copyConfig(paths.tempDir, paths.configDir);
@ -140,11 +141,11 @@ export async function GET(req) {
console.log('开始新的更新流程'); console.log('开始新的更新流程');
// 保存路径信息到Redis // 保存路径信息到Redis
await redis.set('rconsole:update:paths', JSON.stringify({ await redis.set(REDIS_UPDATE_PATH, JSON.stringify({
tempDir, tempDir,
configDir configDir
})); }));
await redis.set('rconsole:update:status', 'started'); await redis.set(REDIS_UPDATE_STATUS, 'started');
// 确保临时目录存在 // 确保临时目录存在
await ensureDirectory(tempDir); await ensureDirectory(tempDir);
@ -156,7 +157,7 @@ export async function GET(req) {
await copyConfig(configDir, tempDir); await copyConfig(configDir, tempDir);
configBackedUp = true; configBackedUp = true;
console.log('配置文件备份成功'); console.log('配置文件备份成功');
await redis.set('rconsole:update:status', 'backed_up'); await redis.set(REDIS_UPDATE_STATUS, 'backed_up');
} catch (error) { } catch (error) {
console.log('无配置文件需要备份或备份失败:', error.message); console.log('无配置文件需要备份或备份失败:', error.message);
} }
@ -169,7 +170,7 @@ export async function GET(req) {
} }
// 标记状态为需要恢复 // 标记状态为需要恢复
await redis.set('rconsole:update:status', 'restoring'); await redis.set(REDIS_UPDATE_STATUS, 'restoring');
console.log('执行git pull...'); console.log('执行git pull...');
const { stdout } = await execAsync(`git -C "${projectRoot}" pull --no-rebase`); const { stdout } = await execAsync(`git -C "${projectRoot}" pull --no-rebase`);
@ -225,7 +226,7 @@ export async function GET(req) {
console.error('更新过程出错:', error); console.error('更新过程出错:', error);
// 确保清理所有临时状态 // 确保清理所有临时状态
const paths = JSON.parse(await redis.get('rconsole:update:paths') || '{}'); const paths = JSON.parse(await redis.get(REDIS_UPDATE_PATH) || '{}');
if (paths.tempDir) { if (paths.tempDir) {
await cleanupUpdate(paths.tempDir); await cleanupUpdate(paths.tempDir);
} }
@ -237,4 +238,4 @@ export async function GET(req) {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}); });
} }
} }

View File

@ -0,0 +1,65 @@
import React, { useState, useEffect } from "react";
const TagSelector = ({ options = [], initialTags = [], onChange }) => {
const [selectedTags, setSelectedTags] = useState(initialTags);
useEffect(() => {
setSelectedTags(initialTags);
}, [initialTags]);
const addTag = (tag) => {
if (!selectedTags.includes(tag)) {
const updatedTags = [...selectedTags, tag];
setSelectedTags(updatedTags);
if (onChange) onChange(updatedTags);
}
};
const removeTag = (tag) => {
const updatedTags = selectedTags.filter((t) => t !== tag);
setSelectedTags(updatedTags);
if (onChange) onChange(updatedTags);
};
return (
<div className="grid md:grid-cols-2 gap-4 mb-6">
<div className="flex flex-wrap gap-2 mb-4 border p-2 rounded">
{selectedTags.length > 0 ? (
selectedTags.map((tag, index) => (
<span
key={index}
className="badge badge-secondary gap-2 cursor-pointer"
onClick={() => removeTag(tag)}
>
{tag}
</span>
))
) : (
<span className="text-gray-500">暂无标签请选择一个选项</span>
)}
</div>
<select
className="select select-bordered w-full"
onChange={(e) => {
if (e.target.value) {
addTag(e.target.value);
e.target.value = "";
}
}}
value=""
>
<option value="" disabled>
选择一个选项
</option>
{options.filter(option => !selectedTags.includes(option)).map((option, index) => (
<option key={index} value={option}>
{option}
</option>
))}
</select>
</div>
);
};
export default TagSelector;

View File

@ -1,8 +1,8 @@
import { useState, useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { readYamlConfig, updateYamlConfig } from '../../utils/yamlHelper'; import { readYamlConfig, updateYamlConfig } from '../../utils/yamlHelper';
import { ConfigInput, ConfigSelect, ConfigToggle } from '../common/ConfigItem';
import TagSelector from "../TagSelector.jsx";
import Toast from "../toast.jsx"; import Toast from "../toast.jsx";
import { ConfigToggle, ConfigInput, ConfigSelect } from '../common/ConfigItem';
import { AI_MODEL_LIST } from "../../../constants/constant.js";
// //
const GENERIC_CONFIG = { const GENERIC_CONFIG = {
@ -146,6 +146,8 @@ const DEFAULT_CONFIG = Object.values(GENERIC_CONFIG).reduce((acc, group) => {
export default function Generic() { export default function Generic() {
const [config, setConfig] = useState(DEFAULT_CONFIG); const [config, setConfig] = useState(DEFAULT_CONFIG);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [resolveOptions, setResolveOptions] = useState([]);
const [selectedResolveTags, setSelectedResolveTags] = useState([]);
useEffect(() => { useEffect(() => {
const loadConfig = async () => { const loadConfig = async () => {
@ -161,6 +163,25 @@ export default function Generic() {
loadConfig(); loadConfig();
}, []); }, []);
useEffect(() => {
//
const fetchResolveControl = async () => {
try {
const response = await fetch('/r/api/resolveControl');
const data = await response.json();
const enabledTags = data
.filter(item => item.value === 1)
.map(item => item.label);
setSelectedResolveTags(enabledTags);
setResolveOptions(data.map(item => item.label));
} catch (error) {
console.error('获取解析控制器配置失败:', error);
}
};
fetchResolveControl();
}, []);
const handleSave = async () => { const handleSave = async () => {
setLoading(true); setLoading(true);
try { try {
@ -182,6 +203,24 @@ export default function Generic() {
setConfig(prev => ({ ...prev, [key]: value })); setConfig(prev => ({ ...prev, [key]: value }));
}; };
const handleResolveTagsChange = async (tags) => {
try {
const response = await fetch('/r/api/resolveControl', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ selectedTags: tags }),
});
if (response.ok) {
setSelectedResolveTags(tags);
}
} catch (error) {
console.error('更新解析控制器配置失败:', error);
}
};
// //
const renderInputGroup = (inputs, title) => ( const renderInputGroup = (inputs, title) => (
<div className="grid md:grid-cols-2 gap-4 mb-6"> <div className="grid md:grid-cols-2 gap-4 mb-6">
@ -218,6 +257,14 @@ export default function Generic() {
{/* 基础配置 */} {/* 基础配置 */}
{renderInputGroup(GENERIC_CONFIG.basicInputs)} {renderInputGroup(GENERIC_CONFIG.basicInputs)}
{/* 解析控制 */}
<h4 className="font-semibold mt-6 mb-4">全局解析控制</h4>
<TagSelector
options={resolveOptions}
initialTags={selectedResolveTags}
onChange={handleResolveTagsChange}
/>
{/* 代理配置 */} {/* 代理配置 */}
<h4 className="font-semibold mt-6 mb-4">代理设置</h4> <h4 className="font-semibold mt-6 mb-4">代理设置</h4>
{renderInputGroup(GENERIC_CONFIG.proxyInputs)} {renderInputGroup(GENERIC_CONFIG.proxyInputs)}

View File

@ -80,7 +80,7 @@ export function BotConfig() {
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
className={`btn btn-primary ${updating ? 'loading' : ''}`} className={`btn btn-ghost ${updating ? 'loading' : ''}`}
onClick={() => handleUpdate(false)} onClick={() => handleUpdate(false)}
disabled={updating}> disabled={updating}>
普通更新 普通更新

View File

@ -0,0 +1,5 @@
export const REDIS_UPDATE_STATUS = "Yz:rconsole:update:status"
export const REDIS_UPDATE_PATH = "Yz:rconsole:update:paths"
export const REDIS_RESOLVE_CONTROLLER = "Yz:rconsole:resolve:controller"

View File

@ -0,0 +1,78 @@
export const GLOBAL_RESOLE_CONTROLLER = [
{
label: "哔哩哔哩",
value: 1
},
{
label: "抖音",
value: 1
},
{
label: "TikTok",
value: 1
},
{
label: "YouTube",
value: 1
},
{
label: "Acfun",
value: 1
},
{
label: "小红书",
value: 1
},
{
label: "波点",
value: 1
},
{
label: "网易云音乐",
value: 1
},
{
label: "通用(包含快手等)",
value: 1
},
{
label: "Twitter",
value: 1
},
{
label: "米游社",
value: 1
},
{
label: "微博",
value: 1
},
{
label: "微视",
value: 1
},
{
label: "zuiyou",
value: 1
},
{
label: "AM+Spotify",
value: 1
},
{
label: "扣扣音乐",
value: 1
},
{
label: "汽水音乐",
value: 1
},
{
label: "小飞机",
value: 1
},
{
label: "贴吧",
value: 1
}
]

View File

@ -116,7 +116,7 @@ async function downloadM3u8Videos(m3u8FullUrls, outputFolderName) {
}); });
}); });
/** 写入下载链接列表文件 */ /** 写入下载链接列表文件 */
// fs.writeFileSync(path.resolve(outPath, "urls.txt"), str下载参数文件); // fs.writeFileSync(path.resolveControl(outPath, "urls.txt"), str下载参数文件);
return Promise.all(strDownloadParamFiles); return Promise.all(strDownloadParamFiles);
} }