feat: 支持自定义订阅节点并兼容现有数据库
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { parseDocument, stringify } from "yaml";
|
||||
|
||||
export class CustomProxyValidationError extends Error {}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/** Accept a single proxy, a one-item list, or the legacy `proxies:` wrapper. */
|
||||
export function parseCustomProxyYaml(
|
||||
source: string,
|
||||
): Record<string, unknown> & { name: string; type: string } {
|
||||
if (!source.trim()) throw new CustomProxyValidationError("请填写节点 YAML。");
|
||||
if (source.length > 128 * 1024)
|
||||
throw new CustomProxyValidationError("单个节点 YAML 不能超过 128 KiB。");
|
||||
const doc = parseDocument(source, { prettyErrors: false, uniqueKeys: true });
|
||||
if (doc.errors.length || doc.warnings.length) {
|
||||
// Parser messages can contain credentials from the YAML; do not echo them.
|
||||
throw new CustomProxyValidationError(
|
||||
"YAML 格式错误,请检查缩进、引号和重复字段。",
|
||||
);
|
||||
}
|
||||
let value: unknown;
|
||||
try {
|
||||
value = doc.toJS({ maxAliasCount: 100 });
|
||||
} catch {
|
||||
throw new CustomProxyValidationError(
|
||||
"YAML 引用无效或过多,请使用独立的节点配置。",
|
||||
);
|
||||
}
|
||||
if (isRecord(value) && "proxies" in value) value = value.proxies;
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length !== 1)
|
||||
throw new CustomProxyValidationError(
|
||||
"每条节点记录只能包含一个 proxy,请将多个节点分别添加。",
|
||||
);
|
||||
value = value[0];
|
||||
}
|
||||
if (!isRecord(value))
|
||||
throw new CustomProxyValidationError(
|
||||
"请填写一个 Clash proxy 对象或单节点列表。",
|
||||
);
|
||||
if (typeof value.name !== "string" || !value.name.trim()) {
|
||||
throw new CustomProxyValidationError(
|
||||
"节点 YAML 必须包含非空的 name 字段(字符串)。",
|
||||
);
|
||||
}
|
||||
if (typeof value.type !== "string" || !value.type.trim()) {
|
||||
throw new CustomProxyValidationError(
|
||||
"节点 YAML 必须包含非空的 type 字段,例如 anytls。",
|
||||
);
|
||||
}
|
||||
return value as Record<string, unknown> & { name: string; type: string };
|
||||
}
|
||||
|
||||
/** Re-serialize only for output; stored source and comments remain untouched. */
|
||||
export function renderCustomProxyYaml(source: string) {
|
||||
return stringify([parseCustomProxyYaml(source)], { lineWidth: 0 }).trimEnd();
|
||||
}
|
||||
|
||||
export function extractProxyNameFromYaml(source: string) {
|
||||
try {
|
||||
return parseCustomProxyYaml(source).name;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user