@anyul/koishi-plugin-rss 5.2.1 → 5.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/lib/commands/error-handler.js +2 -5
  2. package/lib/commands/index.d.ts +17 -1
  3. package/lib/commands/index.js +388 -2
  4. package/lib/commands/subscription-edit.d.ts +7 -0
  5. package/lib/commands/subscription-edit.js +177 -0
  6. package/lib/commands/subscription-management.d.ts +12 -0
  7. package/lib/commands/subscription-management.js +176 -0
  8. package/lib/commands/utils.d.ts +13 -1
  9. package/lib/commands/utils.js +43 -2
  10. package/lib/config.js +19 -0
  11. package/lib/core/ai.d.ts +16 -2
  12. package/lib/core/ai.js +73 -6
  13. package/lib/core/feeder.d.ts +1 -1
  14. package/lib/core/feeder.js +238 -125
  15. package/lib/core/item-processor.d.ts +5 -0
  16. package/lib/core/item-processor.js +66 -136
  17. package/lib/core/notification-queue.d.ts +2 -0
  18. package/lib/core/notification-queue.js +80 -33
  19. package/lib/core/parser.js +12 -0
  20. package/lib/core/renderer.d.ts +15 -0
  21. package/lib/core/renderer.js +105 -16
  22. package/lib/index.js +28 -784
  23. package/lib/tsconfig.tsbuildinfo +1 -1
  24. package/lib/types.d.ts +24 -0
  25. package/lib/utils/common.js +52 -3
  26. package/lib/utils/error-handler.d.ts +8 -0
  27. package/lib/utils/error-handler.js +27 -0
  28. package/lib/utils/error-tracker.js +24 -8
  29. package/lib/utils/fetcher.js +68 -9
  30. package/lib/utils/logger.d.ts +4 -2
  31. package/lib/utils/logger.js +144 -6
  32. package/lib/utils/media.js +3 -6
  33. package/lib/utils/sanitizer.d.ts +58 -0
  34. package/lib/utils/sanitizer.js +227 -0
  35. package/lib/utils/security.d.ts +75 -0
  36. package/lib/utils/security.js +312 -0
  37. package/lib/utils/structured-logger.js +3 -20
  38. package/package.json +2 -1
@@ -19,16 +19,28 @@ exports.setUser = setUser;
19
19
  exports.TracePerformance = TracePerformance;
20
20
  exports.wrapAsyncFunction = wrapAsyncFunction;
21
21
  exports.withErrorTracking = withErrorTracking;
22
+ const error_handler_1 = require("./error-handler");
23
+ const logger_1 = require("./logger");
22
24
  // 动态导入 Sentry,避免未安装时编译失败
23
25
  let Sentry = null;
26
+ let sentryDependencyWarningShown = false;
24
27
  try {
25
28
  // eslint-disable-next-line @typescript-eslint/no-var-requires
26
29
  Sentry = require('@sentry/node');
27
30
  }
28
31
  catch {
29
- // Sentry 未安装,功能将被禁用
30
- console.warn('Sentry not installed. Error tracking will be disabled.');
31
- console.warn('To enable error tracking, install: npm install @sentry/node');
32
+ Sentry = null;
33
+ }
34
+ function warnMissingSentryDependency() {
35
+ if (sentryDependencyWarningShown)
36
+ return;
37
+ sentryDependencyWarningShown = true;
38
+ logger_1.logger.warn('[error-tracker] Sentry not installed. Error tracking will be disabled.');
39
+ logger_1.logger.warn('[error-tracker] To enable error tracking, install: npm install @sentry/node');
40
+ }
41
+ function logSentryInitError(error) {
42
+ const errorMessage = error instanceof Error ? error.message : String(error);
43
+ logger_1.logger.error(`[error-tracker] Failed to initialize Sentry: ${errorMessage}`);
32
44
  }
33
45
  /**
34
46
  * 错误追踪器类
@@ -43,7 +55,11 @@ class ErrorTracker {
43
55
  * 初始化 Sentry
44
56
  */
45
57
  init() {
46
- if (!this.config.enabled || !this.config.dsn || !Sentry) {
58
+ if (!this.config.enabled || !this.config.dsn) {
59
+ return;
60
+ }
61
+ if (!Sentry) {
62
+ warnMissingSentryDependency();
47
63
  return;
48
64
  }
49
65
  try {
@@ -72,7 +88,7 @@ class ErrorTracker {
72
88
  this.initialized = true;
73
89
  }
74
90
  catch (error) {
75
- console.error('Failed to initialize Sentry:', error);
91
+ logSentryInitError(error);
76
92
  }
77
93
  }
78
94
  /**
@@ -190,7 +206,7 @@ let globalErrorTracker = null;
190
206
  * 初始化全局错误追踪器
191
207
  */
192
208
  function initErrorTracker(config) {
193
- if (!globalErrorTracker) {
209
+ if (!globalErrorTracker || !globalErrorTracker.isInitialized()) {
194
210
  globalErrorTracker = new ErrorTracker(config);
195
211
  globalErrorTracker.init();
196
212
  }
@@ -322,14 +338,14 @@ function withErrorTracking(fn, context, errorMessage) {
322
338
  // 处理 Promise
323
339
  if (result instanceof Promise) {
324
340
  return result.catch((error) => {
325
- trackError(error, context);
341
+ trackError((0, error_handler_1.normalizeError)(error, errorMessage || 'Unknown error'), context);
326
342
  throw error;
327
343
  });
328
344
  }
329
345
  return result;
330
346
  }
331
347
  catch (error) {
332
- trackError(error, context);
348
+ trackError((0, error_handler_1.normalizeError)(error, errorMessage || 'Unknown error'), context);
333
349
  throw error;
334
350
  }
335
351
  });
@@ -6,8 +6,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createHttpFunction = exports.RequestManager = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const https_proxy_agent_1 = require("https-proxy-agent");
9
+ const error_handler_1 = require("./error-handler");
10
+ const error_tracker_1 = require("./error-tracker");
9
11
  const logger_1 = require("./logger");
10
12
  const common_1 = require("./common");
13
+ const security_1 = require("./security");
11
14
  // 简化版 RequestManager,仅用于普通 API 请求,大文件下载建议绕过
12
15
  class RequestManager {
13
16
  queue = [];
@@ -69,14 +72,42 @@ class RequestManager {
69
72
  exports.RequestManager = RequestManager;
70
73
  const createHttpFunction = (ctx, config, requestManager) => {
71
74
  return async (url, arg, requestConfig = {}) => {
75
+ const isHeavyRequest = requestConfig.responseType === 'arraybuffer' || requestConfig.responseType === 'stream';
76
+ const requestType = isHeavyRequest ? 'heavy' : 'normal';
77
+ const requestTimeout = (arg.timeout || 60) * 1000;
78
+ const requestDebug = (0, logger_1.createDebugWithContext)(config, {
79
+ url,
80
+ requestType,
81
+ isHeavyRequest,
82
+ timeout: requestTimeout,
83
+ });
84
+ // URL 安全验证
85
+ try {
86
+ (0, security_1.validateUrlOrThrow)(url, (0, security_1.getSecurityOptions)(config));
87
+ }
88
+ catch (error) {
89
+ if (error instanceof security_1.SecurityError) {
90
+ const normalizedError = (0, error_handler_1.normalizeError)(error);
91
+ requestDebug(`URL 安全验证失败: ${normalizedError.message}`, 'security', 'error', {
92
+ stage: 'security-validation',
93
+ });
94
+ (0, error_tracker_1.trackError)(normalizedError, {
95
+ url,
96
+ requestType,
97
+ isHeavyRequest,
98
+ stage: 'security-validation',
99
+ });
100
+ throw error;
101
+ }
102
+ throw error;
103
+ }
72
104
  // 关键修改:如果检测到是大文件下载(responseType 为 arraybuffer 或 stream),绕过队列直接请求
73
105
  // 这样避免视频下载阻塞 RSS 轮询,也避免因队列超时导致下载失败
74
- const isHeavyRequest = requestConfig.responseType === 'arraybuffer' || requestConfig.responseType === 'stream';
75
106
  const makeRequest = async () => {
76
107
  // 基础配置
77
- (0, logger_1.debug)(config, `[DEBUG_PROXY] fetcher makeRequest arg.proxyAgent: ${JSON.stringify(arg?.proxyAgent)}`, 'request', 'details');
108
+ requestDebug(`[DEBUG_PROXY] fetcher makeRequest arg.proxyAgent: ${JSON.stringify(arg?.proxyAgent)}`, 'request', 'details');
78
109
  let configObj = {
79
- timeout: (arg.timeout || 60) * 1000,
110
+ timeout: requestTimeout,
80
111
  headers: {
81
112
  'User-Agent': config.net.userAgent || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
82
113
  },
@@ -95,10 +126,15 @@ const createHttpFunction = (ctx, config, requestManager) => {
95
126
  port: config.net.proxyAgent.port,
96
127
  auth: config.net.proxyAgent.auth?.enabled ? config.net.proxyAgent.auth : undefined
97
128
  };
98
- (0, logger_1.debug)(config, `[DEBUG_PROXY] fetcher 使用防御性全局代理`, 'request', 'details');
129
+ requestDebug(`[DEBUG_PROXY] fetcher 使用防御性全局代理`, 'request', 'details', {
130
+ proxyEnabled: true,
131
+ proxyUrl: config.net.proxyAgent.host
132
+ ? `${config.net.proxyAgent.protocol}://${config.net.proxyAgent.host}:${config.net.proxyAgent.port}`
133
+ : '',
134
+ });
99
135
  }
100
136
  }
101
- const proxyEnabled = currentProxyAgent?.enabled;
137
+ const proxyEnabled = Boolean(currentProxyAgent?.enabled);
102
138
  let proxyUrl = '';
103
139
  if (proxyEnabled && currentProxyAgent.host) {
104
140
  proxyUrl = `${currentProxyAgent.protocol}://${currentProxyAgent.host}:${currentProxyAgent.port}`;
@@ -113,13 +149,17 @@ const createHttpFunction = (ctx, config, requestManager) => {
113
149
  delete requestConfig.headers;
114
150
  }
115
151
  const finalConfig = { ...configObj, ...requestConfig };
152
+ const requestContext = {
153
+ proxyEnabled,
154
+ proxyUrl,
155
+ };
116
156
  // 对于重请求,打印更详细的日志(包含代理状态)
117
157
  if (isHeavyRequest) {
118
158
  const proxyInfo = proxyEnabled ? ` [代理: ${proxyUrl}]` : ' [直连]';
119
- (0, logger_1.debug)(config, `Heavy Request${proxyInfo}: ${url}`, 'request', 'details');
159
+ requestDebug(`Heavy Request${proxyInfo}: ${url}`, 'request', 'details', requestContext);
120
160
  }
121
161
  else {
122
- (0, logger_1.debug)(config, `Request: ${url}`, 'request', 'details');
162
+ requestDebug(`Request: ${url}`, 'request', 'details', requestContext);
123
163
  }
124
164
  let retries = 3;
125
165
  let lastError;
@@ -133,12 +173,31 @@ const createHttpFunction = (ctx, config, requestManager) => {
133
173
  const status = error.response?.status || 'Unknown';
134
174
  const errMsg = error.message || error.code || error;
135
175
  if (retries > 0) {
136
- (0, logger_1.debug)(config, `[Request Retry] 剩余 ${retries} 次: ${url} [Status: ${status}] ${errMsg}`, 'request', 'info');
176
+ requestDebug(`[Request Retry] 剩余 ${retries} 次: ${url} [Status: ${status}] ${errMsg}`, 'request', 'info', {
177
+ ...requestContext,
178
+ retryCount: 3 - retries,
179
+ retriesRemaining: retries,
180
+ status,
181
+ });
137
182
  await (0, common_1.sleep)(1500); // 增加重试间隔
138
183
  }
139
184
  }
140
185
  }
141
- throw lastError || new Error('Max retries reached');
186
+ const normalizedError = (0, error_handler_1.normalizeError)(lastError || new Error('Max retries reached'));
187
+ const status = lastError?.response?.status || 'Unknown';
188
+ requestDebug(`Request failed after retries: ${normalizedError.message}`, 'request', 'error', {
189
+ ...requestContext,
190
+ status,
191
+ retriesRemaining: 0,
192
+ });
193
+ (0, error_tracker_1.trackError)(normalizedError, {
194
+ url,
195
+ requestType,
196
+ isHeavyRequest,
197
+ status,
198
+ ...requestContext,
199
+ });
200
+ throw normalizedError;
142
201
  };
143
202
  if (isHeavyRequest) {
144
203
  return makeRequest(); // 直接执行,不走队列
@@ -1,6 +1,8 @@
1
1
  import { Logger } from 'koishi';
2
2
  import { Config } from '../types';
3
3
  declare const logger: Logger;
4
+ export type DebugLogType = "disable" | "error" | "info" | "details";
5
+ export declare function shouldLog(config: Config, type: DebugLogType): boolean;
4
6
  /**
5
7
  * 增强的调试日志函数
6
8
  *
@@ -10,7 +12,7 @@ declare const logger: Logger;
10
12
  * @param type - 日志级别
11
13
  * @param context - 额外的上下文信息
12
14
  */
13
- export declare function debug(config: Config, message: any, name?: string, type?: "disable" | "error" | "info" | "details", context?: Record<string, any>): void;
15
+ export declare function debug(config: Config, message: any, name?: string, type?: DebugLogType, context?: Record<string, any>): void;
14
16
  /**
15
17
  * 便捷函数:记录错误日志
16
18
  */
@@ -31,5 +33,5 @@ export declare function debugInfo(config: Config, message: any, name?: string, c
31
33
  * feedDebug('Processing feed', 'feeder', 'info')
32
34
  * // 输出会自动包含 guildId 和 platform
33
35
  */
34
- export declare function createDebugWithContext(config: Config, fixedContext: Record<string, any>): (message: any, name?: string, type?: "disable" | "error" | "info" | "details", additionalContext?: Record<string, any>) => void;
36
+ export declare function createDebugWithContext(config: Config, fixedContext: Record<string, any>): (message: any, name?: string, type?: DebugLogType, additionalContext?: Record<string, any>) => void;
35
37
  export { logger };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.logger = void 0;
4
+ exports.shouldLog = shouldLog;
4
5
  exports.debug = debug;
5
6
  exports.debugError = debugError;
6
7
  exports.debugInfo = debugInfo;
@@ -9,6 +10,132 @@ const koishi_1 = require("koishi");
9
10
  const types_1 = require("../types");
10
11
  const logger = new koishi_1.Logger('rss-owl');
11
12
  exports.logger = logger;
13
+ function shouldLog(config, type) {
14
+ const typeLevel = types_1.debugLevel.findIndex(i => i === type);
15
+ if (typeLevel < 1)
16
+ return false;
17
+ const configLevel = types_1.debugLevel.findIndex(i => i === config.debug);
18
+ if (configLevel < 0)
19
+ return false;
20
+ return typeLevel <= configLevel;
21
+ }
22
+ /**
23
+ * 敏感信息模式定义
24
+ */
25
+ const SENSITIVE_PATTERNS = [
26
+ // API Key 模式
27
+ { pattern: /api[_-]?key["']?\s*[:=]\s*["']?([^"'&\s,}]+)/gi, replacement: 'api_key=***' },
28
+ // Bearer Token
29
+ { pattern: /Bearer\s+([A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+)/gi, replacement: 'Bearer ***' },
30
+ // Basic Auth
31
+ { pattern: /Basic\s+([A-Za-z0-9+/=]+)/gi, replacement: 'Basic ***' },
32
+ // 代理认证
33
+ { pattern: /([a-zA-Z]+):\/\/([^:]+):([^@]+)@/gi, replacement: '$1://$2:***@' },
34
+ // 密码字段
35
+ { pattern: /["']?password["']?\s*[:=]\s*["']?([^"'&\s,}]+)/gi, replacement: 'password=***' },
36
+ // 密钥字段
37
+ { pattern: /["']?secret["']?\s*[:=]\s*["']?([^"'&\s,}]+)/gi, replacement: 'secret=***' },
38
+ { pattern: /["']?token["']?\s*[:=]\s*["']?([^"'&\s,}]+)/gi, replacement: 'token=***' },
39
+ // AWS Access Key
40
+ { pattern: /(AKIA|ABIA|ACCA|ASIA)[0-9A-Z]{16}/g, replacement: '***' },
41
+ // GitHub Token
42
+ { pattern: /(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}/g, replacement: '***' },
43
+ // JWT Token (更精确的匹配)
44
+ { pattern: /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, replacement: '***' },
45
+ ];
46
+ /**
47
+ * 脱敏日志消息,移除敏感信息
48
+ *
49
+ * @param message - 原始消息(字符串或对象)
50
+ * @returns 脱敏后的消息
51
+ */
52
+ function sanitizeLogMessage(message) {
53
+ if (typeof message === 'string') {
54
+ let sanitized = message;
55
+ for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
56
+ sanitized = sanitized.replace(pattern, replacement);
57
+ }
58
+ return sanitized;
59
+ }
60
+ if (message === null || message === undefined) {
61
+ return message;
62
+ }
63
+ if (message instanceof Error) {
64
+ const sanitizedError = new Error(sanitizeLogMessage(message.message));
65
+ sanitizedError.name = message.name;
66
+ return sanitizedError;
67
+ }
68
+ // 如果是对象,深度脱敏
69
+ if (typeof message === 'object') {
70
+ try {
71
+ // 先序列化再脱敏,然后反序列化
72
+ const jsonStr = JSON.stringify(message);
73
+ let sanitized = jsonStr;
74
+ for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
75
+ sanitized = sanitized.replace(pattern, replacement);
76
+ }
77
+ return JSON.parse(sanitized);
78
+ }
79
+ catch {
80
+ // 如果序列化失败,返回原始对象的脱敏版本
81
+ return sanitizeObject(message);
82
+ }
83
+ }
84
+ return message;
85
+ }
86
+ /**
87
+ * 递归脱敏对象中的敏感字段
88
+ */
89
+ function sanitizeObject(obj, depth = 0) {
90
+ // 防止无限递归
91
+ if (depth > 5 || obj === null || obj === undefined) {
92
+ return obj;
93
+ }
94
+ // 敏感字段列表
95
+ const sensitiveFields = new Set([
96
+ 'password', 'passwd', 'secret', 'token', 'apiKey', 'api_key',
97
+ 'apikey', 'accessToken', 'access_token', 'refreshToken', 'refresh_token',
98
+ 'auth', 'authorization', 'credential', 'privateKey', 'private_key',
99
+ 'sessionId', 'session_id', 'sessionid', 'cookie', 'x-api-key',
100
+ 'x-api-key', 'bearer', 'basic'
101
+ ]);
102
+ if (Array.isArray(obj)) {
103
+ return obj.map(item => sanitizeObject(item, depth + 1));
104
+ }
105
+ if (typeof obj === 'object') {
106
+ const result = {};
107
+ for (const [key, value] of Object.entries(obj)) {
108
+ const lowerKey = key.toLowerCase();
109
+ if (sensitiveFields.has(lowerKey) || lowerKey.includes('secret') || lowerKey.includes('token')) {
110
+ result[key] = '***';
111
+ }
112
+ else if (typeof value === 'object') {
113
+ result[key] = sanitizeObject(value, depth + 1);
114
+ }
115
+ else if (typeof value === 'string') {
116
+ // 检查字符串值是否包含可能的 token
117
+ if (value.length > 30 && /[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/.test(value)) {
118
+ result[key] = '***';
119
+ }
120
+ else {
121
+ result[key] = value;
122
+ }
123
+ }
124
+ else {
125
+ result[key] = value;
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+ return obj;
131
+ }
132
+ function emitLog(type, content) {
133
+ if (type === 'error') {
134
+ logger.error(content);
135
+ return;
136
+ }
137
+ logger.info(content);
138
+ }
12
139
  /**
13
140
  * 增强的调试日志函数
14
141
  *
@@ -19,11 +146,16 @@ exports.logger = logger;
19
146
  * @param context - 额外的上下文信息
20
147
  */
21
148
  function debug(config, message, name = '', type = 'details', context) {
22
- const typeLevel = types_1.debugLevel.findIndex(i => i == type);
23
- if (typeLevel < 1)
24
- return;
25
- if (typeLevel > types_1.debugLevel.findIndex(i => i == config.debug))
149
+ if (!shouldLog(config, type))
26
150
  return;
151
+ // 检查是否启用日志脱敏(默认启用)
152
+ const sanitizeEnabled = config.logging?.sanitizeLogs !== false;
153
+ if (sanitizeEnabled) {
154
+ message = sanitizeLogMessage(message);
155
+ if (context) {
156
+ context = sanitizeObject(context);
157
+ }
158
+ }
27
159
  // 获取日志配置
28
160
  const loggingConfig = config.logging || {};
29
161
  // 格式化消息内容
@@ -31,6 +163,12 @@ function debug(config, message, name = '', type = 'details', context) {
31
163
  if (typeof message === 'string') {
32
164
  formattedMessage = message;
33
165
  }
166
+ else if (message instanceof Error) {
167
+ formattedMessage = message.message || String(message);
168
+ }
169
+ else if (typeof message === 'function') {
170
+ formattedMessage = String(message);
171
+ }
34
172
  else if (message === null || message === undefined) {
35
173
  formattedMessage = String(message);
36
174
  }
@@ -86,7 +224,7 @@ function debug(config, message, name = '', type = 'details', context) {
86
224
  }
87
225
  }
88
226
  // 输出结构化日志
89
- logger.info(JSON.stringify(logEntry));
227
+ emitLog(type, JSON.stringify(logEntry));
90
228
  }
91
229
  else {
92
230
  // 传统文本格式
@@ -102,7 +240,7 @@ function debug(config, message, name = '', type = 'details', context) {
102
240
  .join(' ');
103
241
  textOutput += ` | ${contextStr}`;
104
242
  }
105
- logger.info(textOutput);
243
+ emitLog(type, textOutput);
106
244
  }
107
245
  }
108
246
  /**
@@ -57,12 +57,9 @@ exports.getCacheDir = getCacheDir;
57
57
  const writeCacheFile = async (fileUrl, config) => {
58
58
  const cacheDir = (0, exports.getCacheDir)(config);
59
59
  (0, logger_1.debug)(config, cacheDir, 'cacheDir', 'details');
60
- let fileList = fs.readdirSync(cacheDir);
61
- let suffix = /(?<=^data:.+?\/).+?(?=;base64)/.exec(fileUrl)[0];
62
- let fileName = `${parseInt((Math.random() * 10000000).toString()).toString()}.${suffix}`;
63
- while (fileList.find(i => i == fileName)) {
64
- fileName = `${parseInt((Math.random() * 10000000).toString()).toString()}.${suffix}`;
65
- }
60
+ let suffix = /(?<=^data:.+?\/).+?(?=;base64)/.exec(fileUrl)?.[0] || 'bin';
61
+ // 使用时间戳 + 随机数生成唯一文件名,避免竞态条件
62
+ let fileName = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}.${suffix}`;
66
63
  let base64Data = fileUrl.replace(/^data:.+?;base64,/, "");
67
64
  let filePath = `${cacheDir}/${fileName}`;
68
65
  fs.writeFileSync(filePath, base64Data, 'base64');
@@ -0,0 +1,58 @@
1
+ import { Config } from '../types';
2
+ /**
3
+ * 清理 HTML 内容,移除潜在恶意代码
4
+ *
5
+ * @param html - 要清理的 HTML 字符串
6
+ * @param config - 可选的额外配置
7
+ * @returns 清理后的 HTML 字符串
8
+ */
9
+ export declare function sanitizeHtml(html: string, config?: Record<string, unknown>): string;
10
+ /**
11
+ * 清理并返回纯文本(用于需要提取文本的场景)
12
+ *
13
+ * @param html - 要清理的 HTML 字符串
14
+ * @returns 清理后的纯文本
15
+ */
16
+ export declare function sanitizeToText(html: string): string;
17
+ /**
18
+ * 清理 HTML 中的图片 URL
19
+ *
20
+ * @param html - 包含图片的 HTML 字符串
21
+ * @returns 清理后的 HTML(图片 URL 已验证)
22
+ */
23
+ export declare function sanitizeImageUrls(html: string): string;
24
+ /**
25
+ * 清理链接(a 标签)
26
+ *
27
+ * @param html - 包含链接的 HTML 字符串
28
+ * @returns 清理后的 HTML(链接已安全化)
29
+ */
30
+ export declare function sanitizeLinks(html: string): string;
31
+ /**
32
+ * 创建带有 HTML 清理功能的模板内容处理函数
33
+ *
34
+ * @param config - 配置对象
35
+ * @returns 清理函数
36
+ */
37
+ export declare function createSanitizer(config: Config): {
38
+ /**
39
+ * 清理 HTML 内容
40
+ */
41
+ sanitize: (html: string) => string;
42
+ /**
43
+ * 清理并返回纯文本
44
+ */
45
+ sanitizeToText: (html: string) => string;
46
+ /**
47
+ * 清理图片
48
+ */
49
+ sanitizeImages: (html: string) => string;
50
+ /**
51
+ * 清理链接
52
+ */
53
+ sanitizeLinks: (html: string) => string;
54
+ /**
55
+ * 检查是否启用清理
56
+ */
57
+ isEnabled: () => boolean;
58
+ };