koishi-plugin-video-parser-all 0.8.6 → 0.8.8
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.
- package/lib/index.d.ts +0 -14
- package/lib/index.js +9 -80
- package/package.json +1 -1
- package/readme.md +21 -40
package/lib/index.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export declare const Config: Schema<{
|
|
|
4
4
|
enable?: boolean | null | undefined;
|
|
5
5
|
botName?: string | null | undefined;
|
|
6
6
|
showWaitingTip?: boolean | null | undefined;
|
|
7
|
-
sameLinkInterval?: number | null | undefined;
|
|
8
7
|
debug?: boolean | null | undefined;
|
|
9
8
|
} & import("cosmokit").Dict & {
|
|
10
9
|
unifiedMessageFormat?: string | null | undefined;
|
|
@@ -23,23 +22,16 @@ export declare const Config: Schema<{
|
|
|
23
22
|
retryInterval?: number | null | undefined;
|
|
24
23
|
} & {
|
|
25
24
|
enableForward?: boolean | null | undefined;
|
|
26
|
-
} & {
|
|
27
|
-
messageBufferDelay?: number | null | undefined;
|
|
28
|
-
} & {
|
|
29
|
-
autoClearCacheInterval?: number | null | undefined;
|
|
30
25
|
} & {
|
|
31
26
|
waitingTipText?: string | null | undefined;
|
|
32
|
-
duplicateLinkText?: string | null | undefined;
|
|
33
27
|
unsupportedPlatformText?: string | null | undefined;
|
|
34
28
|
invalidLinkText?: string | null | undefined;
|
|
35
|
-
cacheClearedText?: string | null | undefined;
|
|
36
29
|
parseErrorPrefix?: string | null | undefined;
|
|
37
30
|
parseErrorItemFormat?: string | null | undefined;
|
|
38
31
|
}, {
|
|
39
32
|
enable: boolean;
|
|
40
33
|
botName: string;
|
|
41
34
|
showWaitingTip: boolean;
|
|
42
|
-
sameLinkInterval: number;
|
|
43
35
|
debug: boolean;
|
|
44
36
|
} & import("cosmokit").Dict & {
|
|
45
37
|
unifiedMessageFormat: string;
|
|
@@ -58,16 +50,10 @@ export declare const Config: Schema<{
|
|
|
58
50
|
retryInterval: number;
|
|
59
51
|
} & {
|
|
60
52
|
enableForward: boolean;
|
|
61
|
-
} & {
|
|
62
|
-
messageBufferDelay: number;
|
|
63
|
-
} & {
|
|
64
|
-
autoClearCacheInterval: number;
|
|
65
53
|
} & {
|
|
66
54
|
waitingTipText: string;
|
|
67
|
-
duplicateLinkText: string;
|
|
68
55
|
unsupportedPlatformText: string;
|
|
69
56
|
invalidLinkText: string;
|
|
70
|
-
cacheClearedText: string;
|
|
71
57
|
parseErrorPrefix: string;
|
|
72
58
|
parseErrorItemFormat: string;
|
|
73
59
|
}>;
|
package/lib/index.js
CHANGED
|
@@ -7,16 +7,12 @@ exports.Config = exports.name = void 0;
|
|
|
7
7
|
exports.apply = apply;
|
|
8
8
|
const koishi_1 = require("koishi");
|
|
9
9
|
const axios_1 = __importDefault(require("axios"));
|
|
10
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
11
|
-
const fs_1 = __importDefault(require("fs"));
|
|
12
|
-
const path_1 = __importDefault(require("path"));
|
|
13
10
|
exports.name = 'video-parser-all';
|
|
14
11
|
exports.Config = koishi_1.Schema.intersect([
|
|
15
12
|
koishi_1.Schema.object({
|
|
16
13
|
enable: koishi_1.Schema.boolean().default(true).description('是否启用视频解析插件'),
|
|
17
14
|
botName: koishi_1.Schema.string().default('视频解析机器人').description('合并转发消息中显示的机器人名称'),
|
|
18
15
|
showWaitingTip: koishi_1.Schema.boolean().default(true).description('解析时显示等待提示'),
|
|
19
|
-
sameLinkInterval: koishi_1.Schema.number().min(0).default(180).description('相同链接重复解析间隔(秒)'),
|
|
20
16
|
debug: koishi_1.Schema.boolean().default(false).description('开启调试模式,在控制台输出详细日志'),
|
|
21
17
|
}).description('基础设置'),
|
|
22
18
|
koishi_1.Schema.object({
|
|
@@ -41,24 +37,14 @@ exports.Config = koishi_1.Schema.intersect([
|
|
|
41
37
|
koishi_1.Schema.object({
|
|
42
38
|
enableForward: koishi_1.Schema.boolean().default(false).description('启用合并转发(仅 OneBot 平台)'),
|
|
43
39
|
}).description('发送方式设置'),
|
|
44
|
-
koishi_1.Schema.object({
|
|
45
|
-
messageBufferDelay: koishi_1.Schema.number().min(0).default(0).description('消息缓冲延迟(毫秒)'),
|
|
46
|
-
}).description('消息缓冲'),
|
|
47
|
-
koishi_1.Schema.object({
|
|
48
|
-
autoClearCacheInterval: koishi_1.Schema.number().min(0).default(0).description('自动清理缓存间隔(分钟,0 为关闭)'),
|
|
49
|
-
}).description('缓存清理'),
|
|
50
40
|
koishi_1.Schema.object({
|
|
51
41
|
waitingTipText: koishi_1.Schema.string().default('正在解析视频,请稍候...').description('解析等待提示'),
|
|
52
|
-
duplicateLinkText: koishi_1.Schema.string().default('请勿重复解析相同链接').description('重复链接提示'),
|
|
53
42
|
unsupportedPlatformText: koishi_1.Schema.string().default('不支持该平台链接').description('不支持的平台提示'),
|
|
54
43
|
invalidLinkText: koishi_1.Schema.string().default('无效的视频链接').description('无效链接提示(parse 指令)'),
|
|
55
|
-
cacheClearedText: koishi_1.Schema.string().default('✅ 缓存已清空').description('缓存清理提示'),
|
|
56
44
|
parseErrorPrefix: koishi_1.Schema.string().default('❌ 解析失败:').description('解析失败消息前缀'),
|
|
57
45
|
parseErrorItemFormat: koishi_1.Schema.string().default('【${url}】: ${msg}').description('每条解析失败格式,可用 ${url} ${msg}'),
|
|
58
46
|
}).description('界面文字设置'),
|
|
59
47
|
]);
|
|
60
|
-
const processed = new Map();
|
|
61
|
-
const linkBuffer = new Map();
|
|
62
48
|
const logger = new koishi_1.Logger(exports.name);
|
|
63
49
|
let debugEnabled = false;
|
|
64
50
|
function debugLog(level, ...args) {
|
|
@@ -246,14 +232,12 @@ function parseApiResponse(raw, maxDescLen) {
|
|
|
246
232
|
else if (extra.create_time) {
|
|
247
233
|
publishTime = extra.create_time * 1000;
|
|
248
234
|
}
|
|
249
|
-
|
|
235
|
+
return {
|
|
250
236
|
type, title, desc, author, uid, avatar, cover,
|
|
251
237
|
video, videos, images, live_photo, music,
|
|
252
238
|
like, comment, collect, share, play,
|
|
253
239
|
duration, publishTime
|
|
254
240
|
};
|
|
255
|
-
debugLog('DEBUG', '解析后的数据:', result);
|
|
256
|
-
return result;
|
|
257
241
|
}
|
|
258
242
|
function generateFormattedText(p, format) {
|
|
259
243
|
const vars = {
|
|
@@ -278,24 +262,7 @@ function generateFormattedText(p, format) {
|
|
|
278
262
|
for (const [key, value] of Object.entries(vars)) {
|
|
279
263
|
result = result.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value);
|
|
280
264
|
}
|
|
281
|
-
|
|
282
|
-
debugLog('DEBUG', '生成格式化文本:', final);
|
|
283
|
-
return final;
|
|
284
|
-
}
|
|
285
|
-
function clearAllCache() {
|
|
286
|
-
processed.clear();
|
|
287
|
-
linkBuffer.forEach(buf => clearTimeout(buf.timer));
|
|
288
|
-
linkBuffer.clear();
|
|
289
|
-
const tempDir = path_1.default.join(process.cwd(), 'temp_videos');
|
|
290
|
-
if (fs_1.default.existsSync(tempDir)) {
|
|
291
|
-
fs_1.default.readdirSync(tempDir).forEach(file => {
|
|
292
|
-
try {
|
|
293
|
-
fs_1.default.unlinkSync(path_1.default.join(tempDir, file));
|
|
294
|
-
}
|
|
295
|
-
catch (error) { }
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
return true;
|
|
265
|
+
return result.replace(/^\s*\n/gm, '').trim();
|
|
299
266
|
}
|
|
300
267
|
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
301
268
|
function buildForwardNode(session, content, botName) {
|
|
@@ -311,17 +278,13 @@ function buildForwardNode(session, content, botName) {
|
|
|
311
278
|
function apply(ctx, config) {
|
|
312
279
|
debugEnabled = config.debug || false;
|
|
313
280
|
debugLog('INFO', '插件初始化开始');
|
|
314
|
-
debugLog('INFO', '当前配置:', config);
|
|
315
281
|
const texts = {
|
|
316
282
|
waitingTipText: config.waitingTipText || '正在解析视频,请稍候...',
|
|
317
|
-
duplicateLinkText: config.duplicateLinkText || '请勿重复解析相同链接',
|
|
318
283
|
unsupportedPlatformText: config.unsupportedPlatformText || '不支持该平台链接',
|
|
319
284
|
invalidLinkText: config.invalidLinkText || '无效的视频链接',
|
|
320
|
-
cacheClearedText: config.cacheClearedText || '✅ 缓存已清空',
|
|
321
285
|
parseErrorPrefix: config.parseErrorPrefix || '❌ 解析失败:',
|
|
322
286
|
parseErrorItemFormat: config.parseErrorItemFormat || '【${url}】: ${msg}',
|
|
323
287
|
};
|
|
324
|
-
clearAllCache();
|
|
325
288
|
const http = axios_1.default.create({
|
|
326
289
|
timeout: config.timeout,
|
|
327
290
|
headers: {
|
|
@@ -338,8 +301,7 @@ function apply(ctx, config) {
|
|
|
338
301
|
params: { url },
|
|
339
302
|
timeout: config.timeout
|
|
340
303
|
});
|
|
341
|
-
debugLog('
|
|
342
|
-
debugLog('DEBUG', 'API完整响应:', res.data);
|
|
304
|
+
debugLog('DEBUG', `API响应: ${JSON.stringify(res.data)}`);
|
|
343
305
|
if (res.data && (res.data.code === 200 || res.data.code === 0)) {
|
|
344
306
|
return parseApiResponse(res.data, config.maxDescLength);
|
|
345
307
|
}
|
|
@@ -354,34 +316,23 @@ function apply(ctx, config) {
|
|
|
354
316
|
throw new Error('API请求全部失败');
|
|
355
317
|
}
|
|
356
318
|
async function parseUrl(url) {
|
|
357
|
-
debugLog('INFO', `开始解析链接: ${url}`);
|
|
358
319
|
const realUrl = await resolveShortUrl(url);
|
|
359
|
-
debugLog('DEBUG', `重定向后的URL: ${realUrl}`);
|
|
360
320
|
const platform = getPlatformType(realUrl);
|
|
361
321
|
if (!platform) {
|
|
362
322
|
return { success: false, msg: texts.unsupportedPlatformText };
|
|
363
323
|
}
|
|
364
|
-
const
|
|
365
|
-
let lastError = null;
|
|
366
|
-
for (const candidate of candidates) {
|
|
324
|
+
for (const candidate of [url, realUrl]) {
|
|
367
325
|
try {
|
|
368
326
|
const info = await fetchApi(candidate);
|
|
369
327
|
return { success: true, data: info };
|
|
370
328
|
}
|
|
371
329
|
catch (error) {
|
|
372
|
-
|
|
330
|
+
debugLog('ERROR', `候选链接解析失败: ${candidate}`);
|
|
373
331
|
}
|
|
374
332
|
}
|
|
375
|
-
return { success: false, msg:
|
|
333
|
+
return { success: false, msg: '解析失败' };
|
|
376
334
|
}
|
|
377
|
-
async function processSingleUrl(
|
|
378
|
-
const hash = crypto_1.default.createHash('md5').update(url).digest('hex');
|
|
379
|
-
const now = Date.now();
|
|
380
|
-
const last = processed.get(hash);
|
|
381
|
-
if (last && (now - last) < config.sameLinkInterval * 1000) {
|
|
382
|
-
return { success: false, msg: texts.duplicateLinkText };
|
|
383
|
-
}
|
|
384
|
-
processed.set(hash, now);
|
|
335
|
+
async function processSingleUrl(url) {
|
|
385
336
|
const result = await parseUrl(url);
|
|
386
337
|
if (!result.success)
|
|
387
338
|
return result;
|
|
@@ -411,18 +362,11 @@ function apply(ctx, config) {
|
|
|
411
362
|
return null;
|
|
412
363
|
}
|
|
413
364
|
}
|
|
414
|
-
async function flush(session,
|
|
415
|
-
const key = `${session.platform}:${session.userId}:${session.channelId}`;
|
|
416
|
-
const buffer = linkBuffer.get(key);
|
|
417
|
-
const urls = manualUrls || buffer?.urls || [];
|
|
418
|
-
if (buffer) {
|
|
419
|
-
clearTimeout(buffer.timer);
|
|
420
|
-
linkBuffer.delete(key);
|
|
421
|
-
}
|
|
365
|
+
async function flush(session, urls) {
|
|
422
366
|
const items = [];
|
|
423
367
|
const errors = [];
|
|
424
368
|
for (const url of urls) {
|
|
425
|
-
const res = await processSingleUrl(
|
|
369
|
+
const res = await processSingleUrl(url);
|
|
426
370
|
if (res.success) {
|
|
427
371
|
items.push(res.data);
|
|
428
372
|
}
|
|
@@ -542,20 +486,5 @@ function apply(ctx, config) {
|
|
|
542
486
|
}
|
|
543
487
|
await flush(session, us);
|
|
544
488
|
});
|
|
545
|
-
ctx.command('clear-cache', '清空缓存').action(async ({ session }) => {
|
|
546
|
-
clearAllCache();
|
|
547
|
-
await sendWithTimeout(session, texts.cacheClearedText);
|
|
548
|
-
});
|
|
549
|
-
setInterval(() => {
|
|
550
|
-
const now = Date.now();
|
|
551
|
-
processed.forEach((t, h) => { if (now - t > 86400000)
|
|
552
|
-
processed.delete(h); });
|
|
553
|
-
}, 3600000);
|
|
554
|
-
if (config.autoClearCacheInterval > 0) {
|
|
555
|
-
setInterval(() => {
|
|
556
|
-
clearAllCache();
|
|
557
|
-
debugLog('INFO', '自动清理缓存');
|
|
558
|
-
}, config.autoClearCacheInterval * 60 * 1000);
|
|
559
|
-
}
|
|
560
489
|
debugLog('INFO', '插件初始化完成');
|
|
561
490
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
- 🤖 自动识别链接来源,即丢即用
|
|
9
9
|
- 🎨 完全自定义的解析结果格式,支持多项变量替换
|
|
10
10
|
- 🐛 内置Debug调试模式,可详细记录所有操作与API交互日志
|
|
11
|
-
- ⚡ 防重复解析、API重试、本地视频下载、多线程加速等实用功能
|
|
12
11
|
- 📤 支持OneBot平台消息合并转发,优化多图文展示体验
|
|
13
|
-
-
|
|
12
|
+
- 💬 所有提示文案均可自定义,适配多语言场景
|
|
14
13
|
|
|
15
14
|
### English
|
|
16
15
|
This is a **multi-platform video/image parsing plugin** developed for the Koishi bot framework, using a unified API interface to automatically recognize and parse short video/image/live photo links from 20+ mainstream platforms such as Douyin, Kuaishou, Bilibili, Xiaohongshu, Weibo, YouTube, TikTok, Jianying, AcFun, Zhihu, Huya and more. Core features:
|
|
@@ -18,9 +17,8 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
18
17
|
- 🤖 Auto-detection of link sources, just drop & go
|
|
19
18
|
- 🎨 Fully customizable parsing result format with variable substitutions
|
|
20
19
|
- 🐛 Built-in Debug mode, recording detailed operations and API interaction logs
|
|
21
|
-
- ⚡ Duplicate parsing prevention, API retry, local video download, multithread acceleration
|
|
22
20
|
- 📤 Support OneBot message forwarding for better image/video display
|
|
23
|
-
-
|
|
21
|
+
- 💬 All prompt texts are customizable for multilingual scenarios
|
|
24
22
|
|
|
25
23
|
## 项目仓库 (Repository)
|
|
26
24
|
- GitHub: `https://github.com/Minecraft-1314/koishi-plugin-video-parser-all`
|
|
@@ -31,7 +29,6 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
31
29
|
| 指令 (Command) | 说明 (Description) | 示例 (Example) |
|
|
32
30
|
|----------------|--------------------|----------------|
|
|
33
31
|
| `parse <url>` | 手动解析指定的视频/图集链接 | `parse https://v.douyin.com/xxxx/` |
|
|
34
|
-
| `clear-cache` | 清理解析缓存和临时下载的视频文件 | `clear-cache` |
|
|
35
32
|
|
|
36
33
|
## 配置项说明 (Configuration)
|
|
37
34
|
|
|
@@ -40,65 +37,49 @@ This is a **multi-platform video/image parsing plugin** developed for the Koishi
|
|
|
40
37
|
|--------|------|--------|------|
|
|
41
38
|
| `enable` | boolean | true | 是否启用视频解析插件 |
|
|
42
39
|
| `botName` | string | 视频解析机器人 | 合并转发消息中显示的机器人名称 |
|
|
43
|
-
| `showWaitingTip` | boolean | true |
|
|
44
|
-
| `
|
|
45
|
-
| `sameLinkInterval` | number | 180 | 相同链接重复解析间隔(秒),防止频繁解析 |
|
|
46
|
-
| `debug` | boolean | false | 是否开启Debug调试模式,控制台输出详细日志 |
|
|
47
|
-
| `debugFile` | boolean | false | 开启Debug时将日志同时写入本地`debug.log`文件 |
|
|
40
|
+
| `showWaitingTip` | boolean | true | 解析时是否显示等待提示 |
|
|
41
|
+
| `debug` | boolean | false | 是否开启 Debug 模式,在控制台输出详细日志 |
|
|
48
42
|
|
|
49
43
|
### 统一消息格式
|
|
50
44
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
51
45
|
|--------|------|--------|------|
|
|
52
|
-
| `unifiedMessageFormat` | string |
|
|
46
|
+
| `unifiedMessageFormat` | string | `\${标题}\n\${作者}\n\${简介}\n点赞:\${点赞数}\n收藏:\${收藏数}\n转发:\${转发数}\n播放:\${播放数}\n评论:\${评论数}` | 自定义解析结果的输出格式,支持变量替换 |
|
|
53
47
|
|
|
54
48
|
### 内容显示设置
|
|
55
49
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
56
50
|
|--------|------|--------|------|
|
|
57
|
-
| `showImageText` | boolean | true |
|
|
51
|
+
| `showImageText` | boolean | true | 是否发送解析后的文字内容 |
|
|
58
52
|
| `showVideoFile` | boolean | true | 是否发送视频文件(关闭则只发送视频链接) |
|
|
59
53
|
| `sendLivePhotoVideos` | boolean | true | 是否发送实况图片附带的短视频(仅 live_photo 类型) |
|
|
54
|
+
| `maxDescLength` | number | 200 | 简介内容最大长度(字符),超出自动截断 |
|
|
60
55
|
|
|
61
|
-
###
|
|
56
|
+
### 网络与 API 设置
|
|
62
57
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
63
58
|
|--------|------|--------|------|
|
|
64
|
-
| `
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
| 配置项 | 类型 | 默认值 | 说明 |
|
|
68
|
-
|--------|------|--------|------|
|
|
69
|
-
| `timeout` | number | 180000 | API请求超时时间(毫秒) |
|
|
70
|
-
| `videoSendTimeout` | number | 60000 | 视频消息发送超时时间(毫秒,0为不限制) |
|
|
71
|
-
| `userAgent` | string | Chrome 124 UA | API请求使用的User-Agent标识 |
|
|
59
|
+
| `timeout` | number | 180000 | API 请求超时时间(毫秒) |
|
|
60
|
+
| `videoSendTimeout` | number | 60000 | 视频消息发送超时时间(毫秒,0 为不限制) |
|
|
61
|
+
| `userAgent` | string | Chrome 124 UA | API 请求使用的 User-Agent |
|
|
72
62
|
|
|
73
63
|
### 错误与重试设置
|
|
74
64
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
75
65
|
|--------|------|--------|------|
|
|
76
|
-
| `ignoreSendError` | boolean | true |
|
|
77
|
-
| `retryTimes` | number | 3 | API请求失败时的重试次数 |
|
|
78
|
-
| `retryInterval` | number | 1000 |
|
|
66
|
+
| `ignoreSendError` | boolean | true | 是否忽略消息发送失败,避免插件崩溃 |
|
|
67
|
+
| `retryTimes` | number | 3 | API 请求失败时的重试次数 |
|
|
68
|
+
| `retryInterval` | number | 1000 | 重试间隔时间(毫秒) |
|
|
79
69
|
|
|
80
70
|
### 发送方式设置
|
|
81
71
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
82
72
|
|--------|------|--------|------|
|
|
83
|
-
| `enableForward` | boolean | false |
|
|
84
|
-
| `downloadVideoBeforeSend` | boolean | false | 发送前先下载视频到本地并发送文件 |
|
|
85
|
-
| `maxVideoSize` | number | 0 | 最大视频下载大小限制(MB,0为不限制) |
|
|
86
|
-
| `downloadThreads` | number | 0 | 多线程下载线程数(0为单线程,最大10) |
|
|
73
|
+
| `enableForward` | boolean | false | 是否启用合并转发(仅 OneBot 平台) |
|
|
87
74
|
|
|
88
|
-
###
|
|
75
|
+
### 界面文字设置
|
|
89
76
|
| 配置项 | 类型 | 默认值 | 说明 |
|
|
90
77
|
|--------|------|--------|------|
|
|
91
|
-
| `
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
|
95
|
-
|
|
96
|
-
| `autoClearCacheInterval` | number | 0 | 自动清理缓存间隔(分钟,0为关闭自动清理) |
|
|
97
|
-
|
|
98
|
-
## 缓存机制说明 (Cache Mechanism)
|
|
99
|
-
- 临时视频默认存放目录:项目根目录 `temp_videos`
|
|
100
|
-
- 支持定时自动清空缓存、手动执行 `clear-cache` 一键清理
|
|
101
|
-
- 自动清理过期解析记录、残留分段下载文件,避免磁盘占用
|
|
78
|
+
| `waitingTipText` | string | 正在解析视频,请稍候... | 解析等待提示文字 |
|
|
79
|
+
| `unsupportedPlatformText` | string | 不支持该平台链接 | 不支持的平台提示 |
|
|
80
|
+
| `invalidLinkText` | string | 无效的视频链接 | invalid link prompt (for parse command) |
|
|
81
|
+
| `parseErrorPrefix` | string | ❌ 解析失败: | 解析失败消息前缀 |
|
|
82
|
+
| `parseErrorItemFormat` | string | 【${url}】: ${msg} | 每条解析失败格式,可用 `${url}` `${msg}` |
|
|
102
83
|
|
|
103
84
|
## 支持的变量 (Supported Variables)
|
|
104
85
|
在 `unifiedMessageFormat` 中可使用以下变量进行自定义格式化:
|