@jayfong/x-utils 1.0.0-202510061015 → 3.4.0

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 (143) hide show
  1. package/lib/ai.d.mts +103 -0
  2. package/lib/ai.mjs +75 -0
  3. package/lib/cache.d.mts +464 -0
  4. package/lib/cache.mjs +531 -0
  5. package/lib/captcha.d.mts +259 -0
  6. package/lib/captcha.mjs +174 -0
  7. package/lib/case.d.mts +184 -0
  8. package/lib/case.mjs +120 -0
  9. package/lib/chinese.d.mts +57 -0
  10. package/lib/chinese.mjs +79 -0
  11. package/lib/cli.d.mts +52 -0
  12. package/lib/cli.mjs +62 -0
  13. package/lib/clipboard.d.mts +50 -0
  14. package/lib/clipboard.mjs +122 -0
  15. package/lib/cmd.d.mts +38 -0
  16. package/lib/cmd.mjs +52 -0
  17. package/lib/crawler.d.mts +330 -0
  18. package/lib/crawler.mjs +313 -0
  19. package/lib/crypto.d.mts +158 -0
  20. package/lib/crypto.mjs +213 -0
  21. package/lib/csv.d.mts +67 -0
  22. package/lib/csv.mjs +76 -0
  23. package/lib/data_set.d.mts +76 -0
  24. package/lib/data_set.mjs +104 -0
  25. package/lib/db_value.d.mts +213 -0
  26. package/lib/db_value.mjs +208 -0
  27. package/lib/dispose.d.mts +98 -0
  28. package/lib/dispose.mjs +107 -0
  29. package/lib/file_store.d.mts +167 -0
  30. package/lib/file_store.mjs +120 -0
  31. package/lib/fs.d.mts +111 -0
  32. package/lib/fs.mjs +127 -0
  33. package/lib/html.d.mts +103 -0
  34. package/lib/html.mjs +112 -0
  35. package/lib/json.d.mts +50 -0
  36. package/lib/json.mjs +74 -0
  37. package/lib/jwt.d.mts +128 -0
  38. package/lib/jwt.mjs +104 -0
  39. package/lib/mail.d.mts +125 -0
  40. package/lib/mail.mjs +108 -0
  41. package/lib/pay.d.mts +394 -0
  42. package/lib/pay.mjs +343 -0
  43. package/lib/puppeteer.d.mts +88 -0
  44. package/lib/puppeteer.mjs +219 -0
  45. package/lib/random.d.mts +71 -0
  46. package/lib/random.mjs +92 -0
  47. package/lib/redis.d.mts +32 -0
  48. package/lib/redis.mjs +24 -0
  49. package/lib/runner.d.mts +80 -0
  50. package/lib/runner.mjs +66 -0
  51. package/lib/sensitive_words.d.mts +82 -0
  52. package/lib/sensitive_words.mjs +73 -0
  53. package/lib/sms.d.mts +133 -0
  54. package/lib/sms.mjs +100 -0
  55. package/lib/task.d.mts +37 -0
  56. package/lib/task.mjs +43 -0
  57. package/lib/text.d.mts +84 -0
  58. package/lib/text.mjs +141 -0
  59. package/lib/yaml.d.mts +51 -0
  60. package/lib/yaml.mjs +64 -0
  61. package/package.json +82 -164
  62. package/_cjs/ai.js +0 -80
  63. package/_cjs/alipan.js +0 -173
  64. package/_cjs/cli.js +0 -51
  65. package/_cjs/clipboard.js +0 -65
  66. package/_cjs/cmd.js +0 -42
  67. package/_cjs/compressor.js +0 -47
  68. package/_cjs/crawler.js +0 -341
  69. package/_cjs/csv.js +0 -97
  70. package/_cjs/data_set.js +0 -115
  71. package/_cjs/env.js +0 -82
  72. package/_cjs/fs.js +0 -86
  73. package/_cjs/html.js +0 -62
  74. package/_cjs/http_server.js +0 -79
  75. package/_cjs/json.js +0 -49
  76. package/_cjs/ksb.js +0 -218
  77. package/_cjs/number.js +0 -39
  78. package/_cjs/ocr.js +0 -74
  79. package/_cjs/package.json +0 -3
  80. package/_cjs/path.js +0 -35
  81. package/_cjs/pinyin.js +0 -37
  82. package/_cjs/puppeteer.js +0 -271
  83. package/_cjs/runner.js +0 -88
  84. package/_cjs/task.js +0 -41
  85. package/_cjs/text.js +0 -136
  86. package/_cjs/unicode/lib_unicode.js +0 -58643
  87. package/_cjs/unicode/unicode.js +0 -441
  88. package/_cjs/upload.js +0 -101
  89. package/_cjs/yaml.js +0 -52
  90. package/ai.d.ts +0 -26
  91. package/ai.js +0 -60
  92. package/alipan.d.ts +0 -209
  93. package/alipan.js +0 -153
  94. package/cli.d.ts +0 -14
  95. package/cli.js +0 -21
  96. package/clipboard.d.ts +0 -6
  97. package/clipboard.js +0 -35
  98. package/cmd.d.ts +0 -5
  99. package/cmd.js +0 -22
  100. package/compressor.d.ts +0 -5
  101. package/compressor.js +0 -17
  102. package/crawler.d.ts +0 -162
  103. package/crawler.js +0 -332
  104. package/csv.d.ts +0 -14
  105. package/csv.js +0 -67
  106. package/data_set.d.ts +0 -22
  107. package/data_set.js +0 -85
  108. package/env.d.ts +0 -6
  109. package/env.js +0 -52
  110. package/fs.d.ts +0 -23
  111. package/fs.js +0 -56
  112. package/html.d.ts +0 -7
  113. package/html.js +0 -45
  114. package/http_server.d.ts +0 -13
  115. package/http_server.js +0 -49
  116. package/json.d.ts +0 -6
  117. package/json.js +0 -19
  118. package/ksb.d.ts +0 -37
  119. package/ksb.js +0 -198
  120. package/number.d.ts +0 -3
  121. package/number.js +0 -9
  122. package/ocr.d.ts +0 -17
  123. package/ocr.js +0 -54
  124. package/path.d.ts +0 -2
  125. package/path.js +0 -5
  126. package/pinyin.d.ts +0 -8
  127. package/pinyin.js +0 -17
  128. package/puppeteer.d.ts +0 -38
  129. package/puppeteer.js +0 -241
  130. package/runner.d.ts +0 -13
  131. package/runner.js +0 -58
  132. package/task.d.ts +0 -6
  133. package/task.js +0 -21
  134. package/text.d.ts +0 -63
  135. package/text.js +0 -119
  136. package/unicode/lib_unicode.d.ts +0 -4
  137. package/unicode/lib_unicode.js +0 -58623
  138. package/unicode/unicode.d.ts +0 -347
  139. package/unicode/unicode.js +0 -426
  140. package/upload.d.ts +0 -34
  141. package/upload.js +0 -71
  142. package/yaml.d.ts +0 -6
  143. package/yaml.js +0 -22
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 随机 ID 与随机字符串生成工具。
3
+ *
4
+ * 提供碰撞概率极低的全局唯一 ID(CUID2)以及基于指定字符集的随机
5
+ * 字符串生成能力(字母数字、纯字母、纯数字)。所有方法均为静态方法,
6
+ * 可直接通过 `XRandom.xxx()` 调用。
7
+ *
8
+ * @example
9
+ * XRandom.cuid2() // 'dj8fyuqt57ejnnbsnik8q1d6'
10
+ * XRandom.generateAlphanumeric(8) // 'a3Fk9Zx2'
11
+ * XRandom.generateAlphabetic(8) // 'AbCdEfGh'
12
+ * XRandom.generateNumeric(6) // '386251'
13
+ *
14
+ * @remarks
15
+ * - `cuid2()` 生成的 ID 长度固定为 24 位,仅包含小写字母与数字,
16
+ * 适用于数据库主键、文件名等需要高唯一性的场景;
17
+ * - 随机字符串基于 `randomstring` 生成,各方法的字符集见对应说明;
18
+ * - 随机字符串不保证密码学安全,请勿用于令牌、密钥等安全敏感场景。
19
+ */
20
+ export declare class XRandom {
21
+ /**
22
+ * 生成 24 位 CUID2 全局唯一 ID。
23
+ *
24
+ * CUID2 是 CUID 的改进版本,专为水平扩展与防碰撞设计,默认由
25
+ * 24 位小写字母与数字组成,无需配置即可直接使用。
26
+ *
27
+ * @returns 24 位小写字母与数字组成的唯一 ID 字符串。
28
+ *
29
+ * @example
30
+ * XRandom.cuid2() // 'dj8fyuqt57ejnnbsnik8q1d6'
31
+ *
32
+ * @see https://github.com/paralleldrive/cuid2
33
+ */
34
+ static cuid2(): string;
35
+ /**
36
+ * 生成由大小写字母与数字组成的随机字符串。
37
+ *
38
+ * 字符集为 `[0-9 a-z A-Z]`,共 62 种字符。
39
+ *
40
+ * @param length 生成字符串的长度,需为不小于 0 的整数,传 0 时返回空字符串。
41
+ * @returns 指定长度的字母数字随机字符串。
42
+ *
43
+ * @example
44
+ * XRandom.generateAlphanumeric(8) // 'a3Fk9Zx2'
45
+ */
46
+ static generateAlphanumeric(length: number): string;
47
+ /**
48
+ * 生成由大小写英文字母组成的随机字符串。
49
+ *
50
+ * 字符集为 `[a-z A-Z]`,不包含数字。
51
+ *
52
+ * @param length 生成字符串的长度,需为不小于 0 的整数,传 0 时返回空字符串。
53
+ * @returns 指定长度的字母随机字符串。
54
+ *
55
+ * @example
56
+ * XRandom.generateAlphabetic(8) // 'AbCdEfGh'
57
+ */
58
+ static generateAlphabetic(length: number): string;
59
+ /**
60
+ * 生成由数字组成的随机字符串。
61
+ *
62
+ * 字符集为 `[0-9]`,首位允许为 0(例如 `'086251'`)。
63
+ *
64
+ * @param length 生成字符串的长度,需为不小于 0 的整数,传 0 时返回空字符串。
65
+ * @returns 指定长度的数字随机字符串。
66
+ *
67
+ * @example
68
+ * XRandom.generateNumeric(6) // '386251'
69
+ */
70
+ static generateNumeric(length: number): string;
71
+ }
package/lib/random.mjs ADDED
@@ -0,0 +1,92 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import * as randomstring from "randomstring";
3
+ // src/random.ts
4
+ /**
5
+ * 随机 ID 与随机字符串生成工具。
6
+ *
7
+ * 提供碰撞概率极低的全局唯一 ID(CUID2)以及基于指定字符集的随机
8
+ * 字符串生成能力(字母数字、纯字母、纯数字)。所有方法均为静态方法,
9
+ * 可直接通过 `XRandom.xxx()` 调用。
10
+ *
11
+ * @example
12
+ * XRandom.cuid2() // 'dj8fyuqt57ejnnbsnik8q1d6'
13
+ * XRandom.generateAlphanumeric(8) // 'a3Fk9Zx2'
14
+ * XRandom.generateAlphabetic(8) // 'AbCdEfGh'
15
+ * XRandom.generateNumeric(6) // '386251'
16
+ *
17
+ * @remarks
18
+ * - `cuid2()` 生成的 ID 长度固定为 24 位,仅包含小写字母与数字,
19
+ * 适用于数据库主键、文件名等需要高唯一性的场景;
20
+ * - 随机字符串基于 `randomstring` 生成,各方法的字符集见对应说明;
21
+ * - 随机字符串不保证密码学安全,请勿用于令牌、密钥等安全敏感场景。
22
+ */
23
+ var XRandom = class {
24
+ /**
25
+ * 生成 24 位 CUID2 全局唯一 ID。
26
+ *
27
+ * CUID2 是 CUID 的改进版本,专为水平扩展与防碰撞设计,默认由
28
+ * 24 位小写字母与数字组成,无需配置即可直接使用。
29
+ *
30
+ * @returns 24 位小写字母与数字组成的唯一 ID 字符串。
31
+ *
32
+ * @example
33
+ * XRandom.cuid2() // 'dj8fyuqt57ejnnbsnik8q1d6'
34
+ *
35
+ * @see https://github.com/paralleldrive/cuid2
36
+ */
37
+ static cuid2() {
38
+ return createId();
39
+ }
40
+ /**
41
+ * 生成由大小写字母与数字组成的随机字符串。
42
+ *
43
+ * 字符集为 `[0-9 a-z A-Z]`,共 62 种字符。
44
+ *
45
+ * @param length 生成字符串的长度,需为不小于 0 的整数,传 0 时返回空字符串。
46
+ * @returns 指定长度的字母数字随机字符串。
47
+ *
48
+ * @example
49
+ * XRandom.generateAlphanumeric(8) // 'a3Fk9Zx2'
50
+ */
51
+ static generateAlphanumeric(length) {
52
+ return randomstring.generate({
53
+ charset: "alphanumeric",
54
+ length
55
+ });
56
+ }
57
+ /**
58
+ * 生成由大小写英文字母组成的随机字符串。
59
+ *
60
+ * 字符集为 `[a-z A-Z]`,不包含数字。
61
+ *
62
+ * @param length 生成字符串的长度,需为不小于 0 的整数,传 0 时返回空字符串。
63
+ * @returns 指定长度的字母随机字符串。
64
+ *
65
+ * @example
66
+ * XRandom.generateAlphabetic(8) // 'AbCdEfGh'
67
+ */
68
+ static generateAlphabetic(length) {
69
+ return randomstring.generate({
70
+ charset: "alphabetic",
71
+ length
72
+ });
73
+ }
74
+ /**
75
+ * 生成由数字组成的随机字符串。
76
+ *
77
+ * 字符集为 `[0-9]`,首位允许为 0(例如 `'086251'`)。
78
+ *
79
+ * @param length 生成字符串的长度,需为不小于 0 的整数,传 0 时返回空字符串。
80
+ * @returns 指定长度的数字随机字符串。
81
+ *
82
+ * @example
83
+ * XRandom.generateNumeric(6) // '386251'
84
+ */
85
+ static generateNumeric(length) {
86
+ return randomstring.generate({
87
+ charset: "numeric",
88
+ length
89
+ });
90
+ }
91
+ };
92
+ export { XRandom };
@@ -0,0 +1,32 @@
1
+ import { Redis, RedisOptions } from "ioredis";
2
+ /**
3
+ * Redis 连接与行为配置项。
4
+ *
5
+ * 直接继承 ioredis 的 `RedisOptions`,作为 {@link XRedis} 构造时的入参类型。
6
+ * 保留为空继承的原因:
7
+ * - 调用方可继续使用 ioredis 原生的全部配置项(如 `host`、`port`、`db`、
8
+ * `password`、`keyPrefix` 等);
9
+ * - 后续若需追加项目级默认配置或扩展字段,只需在此声明,无需改动调用方。
10
+ */
11
+ export interface XRedisOptions extends RedisOptions {}
12
+ /**
13
+ * Redis 客户端封装。
14
+ *
15
+ * 直接继承 ioredis 的 `Redis`,完整保留其命令、事件与类型定义,当前仅作为
16
+ * 统一入口存在,便于:
17
+ * - 集中管理连接实例的创建与复用;
18
+ * - 在需要时统一注入默认配置、日志或监控逻辑,而不影响调用方;
19
+ * - 后续按需覆写/扩展方法,同时保持对原有 API 的兼容。
20
+ *
21
+ * @example
22
+ * const redis = new XRedis({
23
+ * host: '127.0.0.1',
24
+ * port: 6379,
25
+ * });
26
+ *
27
+ * await redis.set('key', 'value');
28
+ * // 'OK'
29
+ * const value = await redis.get('key');
30
+ * // 'value'
31
+ */
32
+ export declare class XRedis extends Redis {}
package/lib/redis.mjs ADDED
@@ -0,0 +1,24 @@
1
+ import { Redis } from "ioredis";
2
+ // src/redis.ts
3
+ /**
4
+ * Redis 客户端封装。
5
+ *
6
+ * 直接继承 ioredis 的 `Redis`,完整保留其命令、事件与类型定义,当前仅作为
7
+ * 统一入口存在,便于:
8
+ * - 集中管理连接实例的创建与复用;
9
+ * - 在需要时统一注入默认配置、日志或监控逻辑,而不影响调用方;
10
+ * - 后续按需覆写/扩展方法,同时保持对原有 API 的兼容。
11
+ *
12
+ * @example
13
+ * const redis = new XRedis({
14
+ * host: '127.0.0.1',
15
+ * port: 6379,
16
+ * });
17
+ *
18
+ * await redis.set('key', 'value');
19
+ * // 'OK'
20
+ * const value = await redis.get('key');
21
+ * // 'value'
22
+ */
23
+ var XRedis = class extends Redis {};
24
+ export { XRedis };
@@ -0,0 +1,80 @@
1
+ import { MsValue } from "vtils";
2
+ import { AsyncOrSync } from "vtils/types";
3
+ /**
4
+ * `XRunner` 的运行配置。
5
+ */
6
+ export interface XRunnerOptions<TConfig extends Record<string, any>> {
7
+ /**
8
+ * 交互配置持久化文件路径(JSON)。
9
+ *
10
+ * 启动时若文件不存在会自动创建;读取到的历史配置会传给 `provideConfig`,
11
+ * 收集到的新配置最终以 2 空格缩进写回该文件,供下次运行复用。
12
+ */
13
+ configFile: string;
14
+ /**
15
+ * 失败重试间隔。
16
+ *
17
+ * - 固定值:每次失败都等待同一个时长;
18
+ * - 函数:可按重试次数动态计算,`retryCount` 从 `1` 开始计数。
19
+ *
20
+ * 时长取值与 `vtils.ms` 一致:数字表示毫秒,字符串需带单位
21
+ * (如 `'500ms'`、`'1s'`);值非法时 `ms` 会抛错并终止重试循环。
22
+ */
23
+ delay: MsValue | ((retryCount: number) => MsValue);
24
+ /**
25
+ * 收集本次运行配置的交互函数。
26
+ *
27
+ * 入参 `prevConfig` 为启动时读取到的历史配置(文件缺失或内容非法时兜底为 `{}`),
28
+ * 通常用于回填交互默认值;返回最终配置,支持同步或异步返回。
29
+ */
30
+ provideConfig: (prevConfig: Partial<TConfig>) => AsyncOrSync<TConfig>;
31
+ /**
32
+ * 核心执行函数,接收 `provideConfig` 收集到的最终配置。
33
+ *
34
+ * - 成功(resolve):结束运行,`run()` 正常返回;
35
+ * - 失败(reject):进入重试分支,按 `delay` 等待后再次调用。
36
+ */
37
+ run: (config: TConfig) => Promise<void> | void;
38
+ }
39
+ /**
40
+ * 带配置持久化与失败重试能力的交互式运行器。
41
+ *
42
+ * 典型用法:CLI 启动时先询问用户配置并落盘复用,随后循环执行业务函数,
43
+ * 失败时自动等待并重试。
44
+ *
45
+ * 运行流程(详见 {@link XRunner.run}):
46
+ * 1. 确保配置文件存在(缺失时自动创建);
47
+ * 2. 读取历史配置,作为本次提问默认值;
48
+ * 3. 用户确认后回写配置文件,保证下次可复用;
49
+ * 4. 执行业务函数,成功即退出;
50
+ * 5. 失败时按 `delay` 等待后重试,直到成功或出现不可恢复错误。
51
+ */
52
+ export declare class XRunner<TConfig extends Record<string, any>> {
53
+ private options;
54
+ /**
55
+ * 创建运行器实例。
56
+ *
57
+ * @param options 运行配置,各字段语义见 {@link XRunnerOptions}。
58
+ */
59
+ constructor(options: XRunnerOptions<TConfig>);
60
+ /**
61
+ * 启动运行器。
62
+ *
63
+ * 依次执行:确保配置文件存在 → 读取历史配置 → 通过 `provideConfig`
64
+ * 收集本次配置 → 写回配置文件 → 循环执行 `run`。
65
+ *
66
+ * 边界与异常说明:
67
+ * - 配置文件缺失时由 `XFs.ensureFile` 自动补齐目录并创建空文件;
68
+ * - 配置内容非法(非 JSON)时 `readJson(..., { throws: false })` 返回
69
+ * `null`,经 `|| {}` 兜底为空对象,不阻断启动;
70
+ * - `provideConfig` 抛错或写回配置失败时,错误直接向外抛出,不进入重试;
71
+ * - `run` 抛错时进入重试分支:`retryCount` 自增后计算等待时长,打印
72
+ * 错误与重试提示,等待后重新执行;
73
+ * - `delay` 为函数时按 `retryCount`(从 `1` 开始)动态计算;其返回值
74
+ * 若无法被 `ms` 解析,`ms` 会抛错并终止重试循环。
75
+ *
76
+ * @returns 业务函数成功后 resolve 为 `undefined`;仅在成功或抛出
77
+ * 不可恢复错误时返回,否则持续重试。
78
+ */
79
+ run(): Promise<void>;
80
+ }
package/lib/runner.mjs ADDED
@@ -0,0 +1,66 @@
1
+ import { XFs } from "./fs.mjs";
2
+ import { ms, wait } from "vtils";
3
+ // src/runner.ts
4
+ /**
5
+ * 带配置持久化与失败重试能力的交互式运行器。
6
+ *
7
+ * 典型用法:CLI 启动时先询问用户配置并落盘复用,随后循环执行业务函数,
8
+ * 失败时自动等待并重试。
9
+ *
10
+ * 运行流程(详见 {@link XRunner.run}):
11
+ * 1. 确保配置文件存在(缺失时自动创建);
12
+ * 2. 读取历史配置,作为本次提问默认值;
13
+ * 3. 用户确认后回写配置文件,保证下次可复用;
14
+ * 4. 执行业务函数,成功即退出;
15
+ * 5. 失败时按 `delay` 等待后重试,直到成功或出现不可恢复错误。
16
+ */
17
+ var XRunner = class {
18
+ options;
19
+ /**
20
+ * 创建运行器实例。
21
+ *
22
+ * @param options 运行配置,各字段语义见 {@link XRunnerOptions}。
23
+ */
24
+ constructor(options) {
25
+ this.options = options;
26
+ }
27
+ /**
28
+ * 启动运行器。
29
+ *
30
+ * 依次执行:确保配置文件存在 → 读取历史配置 → 通过 `provideConfig`
31
+ * 收集本次配置 → 写回配置文件 → 循环执行 `run`。
32
+ *
33
+ * 边界与异常说明:
34
+ * - 配置文件缺失时由 `XFs.ensureFile` 自动补齐目录并创建空文件;
35
+ * - 配置内容非法(非 JSON)时 `readJson(..., { throws: false })` 返回
36
+ * `null`,经 `|| {}` 兜底为空对象,不阻断启动;
37
+ * - `provideConfig` 抛错或写回配置失败时,错误直接向外抛出,不进入重试;
38
+ * - `run` 抛错时进入重试分支:`retryCount` 自增后计算等待时长,打印
39
+ * 错误与重试提示,等待后重新执行;
40
+ * - `delay` 为函数时按 `retryCount`(从 `1` 开始)动态计算;其返回值
41
+ * 若无法被 `ms` 解析,`ms` 会抛错并终止重试循环。
42
+ *
43
+ * @returns 业务函数成功后 resolve 为 `undefined`;仅在成功或抛出
44
+ * 不可恢复错误时返回,否则持续重试。
45
+ */
46
+ async run() {
47
+ await XFs.ensureFile(this.options.configFile);
48
+ const savedConfig = await XFs.readJson(this.options.configFile, { throws: false }) || {};
49
+ const config = await this.options.provideConfig(savedConfig);
50
+ await XFs.writeJson(this.options.configFile, config, { spaces: 2 });
51
+ let retryCount = 0;
52
+ while (true) try {
53
+ await this.options.run(config);
54
+ return;
55
+ } catch (err) {
56
+ retryCount++;
57
+ const delay = typeof this.options.delay === "function" ? this.options.delay(retryCount) : this.options.delay;
58
+ const delayMs = ms(delay);
59
+ if (delayMs == null) throw new Error(`无效的重试等待时长: ${String(delay)}`);
60
+ console.error(err);
61
+ console.log(`执行报错, 第${retryCount}次重试前等待${delay}`);
62
+ await wait(delayMs);
63
+ }
64
+ }
65
+ };
66
+ export { XRunner };
@@ -0,0 +1,82 @@
1
+ export interface XSensitiveWordsOptions {
2
+ /**
3
+ * 敏感词列表。
4
+ *
5
+ * 支持三种写法:
6
+ * - 普通文本,如 `赌博`:做大小写不敏感的子串匹配;
7
+ * - `<手机号>`:匹配 11 位大陆手机号;
8
+ * - 正则表达式,以 `/` 开头且以 `/` 结尾,如 `/[色情]+/`:
9
+ * 会去除首尾的 `/` 后编译为正则,对归一化后的文本执行 `test`。
10
+ *
11
+ * 构造时会将每个词统一转为小写并去重,因此列表中重复或
12
+ * 大小写不同的词不会影响匹配结果。
13
+ */
14
+ words: string[];
15
+ }
16
+ /**
17
+ * `XSensitiveWords.validate` 的返回结果。
18
+ */
19
+ export interface XSensitiveWordsValidateResult {
20
+ /** 是否通过校验,`true` 表示未命中任何敏感词。 */
21
+ pass: boolean;
22
+ /**
23
+ * 命中的敏感词列表。
24
+ *
25
+ * - 默认(`every` 为假)最多包含一个词,命中即停止;
26
+ * - 传入 `every` 为真时,包含命中的全部词;
27
+ * - 未命中时为空数组。
28
+ */
29
+ words: string[];
30
+ }
31
+ /**
32
+ * 敏感词校验工具。
33
+ *
34
+ * 用于校验文本是否包含敏感词,支持普通词、`<手机号>` 与正则三种
35
+ * 敏感词写法(见 {@link XSensitiveWordsOptions.words})。
36
+ *
37
+ * 校验前会先对文本做归一化处理,以提升命中率:
38
+ * 1. 全角转半角(如 `A` → `A`);
39
+ * 2. 移除非单词字符(保留中文、字母、数字、下划线等);
40
+ * 3. 移除空白字符;
41
+ * 4. 转小写;
42
+ * 5. 叠词归一(除字母数字外,连续重复字符折叠为单个),
43
+ * 以支持 `qq`、手机号等场景。
44
+ *
45
+ * @example
46
+ * const sw = new XSensitiveWords({
47
+ * words: ['赌博', '<手机号>', '/[色情]+/'],
48
+ * });
49
+ *
50
+ * await sw.validate('这是一条赌博信息');
51
+ * // { pass: false, words: ['赌博'] }
52
+ */
53
+ export declare class XSensitiveWords {
54
+ private sensitiveWords;
55
+ constructor(options: XSensitiveWordsOptions);
56
+ /**
57
+ * 文本归一化。
58
+ *
59
+ * 依次执行:全角转半角 → 移除非单词字符 → 移除空白字符 → 转小写
60
+ * → 叠词归一。与构造时仅做“转小写 + 去重”的敏感词列表配合,
61
+ * 实现大小写不敏感、忽略格式干扰的匹配。
62
+ */
63
+ private transform;
64
+ /**
65
+ * 验证文本是否包含敏感词。
66
+ *
67
+ * 对文本做归一化后,按顺序遍历敏感词:
68
+ * - `<手机号>`:用手机号正则测试;
69
+ * - `/.../` 形式的正则词:编译后测试;
70
+ * - 其余普通词:做子串包含判断。
71
+ *
72
+ * @param text 待校验的文本。
73
+ * @param every 是否返回命中的全部敏感词;默认为 `false`,命中一个即停止。
74
+ * @returns 校验结果,`pass` 表示是否通过,`words` 为命中的敏感词。
75
+ *
76
+ * @example
77
+ * const sw = new XSensitiveWords({ words: ['赌博', '色情'] });
78
+ * await sw.validate('赌博与色情', true);
79
+ * // { pass: false, words: ['赌博', '色情'] }
80
+ */
81
+ validate(text: string, every?: boolean): Promise<XSensitiveWordsValidateResult>;
82
+ }
@@ -0,0 +1,73 @@
1
+ import { memoize, removeBlankChars, removeNonWordChars, toHalfWidthString, uniq } from "vtils";
2
+ import { phoneNumberRegExpBuilder } from "vtils/regexp";
3
+ // src/sensitive_words.ts
4
+ const phoneNumberRegExp = phoneNumberRegExpBuilder.build();
5
+ const toRegExp = memoize((re) => new RegExp(re.substring(1, re.length - 1)), (re) => re);
6
+ /**
7
+ * 敏感词校验工具。
8
+ *
9
+ * 用于校验文本是否包含敏感词,支持普通词、`<手机号>` 与正则三种
10
+ * 敏感词写法(见 {@link XSensitiveWordsOptions.words})。
11
+ *
12
+ * 校验前会先对文本做归一化处理,以提升命中率:
13
+ * 1. 全角转半角(如 `A` → `A`);
14
+ * 2. 移除非单词字符(保留中文、字母、数字、下划线等);
15
+ * 3. 移除空白字符;
16
+ * 4. 转小写;
17
+ * 5. 叠词归一(除字母数字外,连续重复字符折叠为单个),
18
+ * 以支持 `qq`、手机号等场景。
19
+ *
20
+ * @example
21
+ * const sw = new XSensitiveWords({
22
+ * words: ['赌博', '<手机号>', '/[色情]+/'],
23
+ * });
24
+ *
25
+ * await sw.validate('这是一条赌博信息');
26
+ * // { pass: false, words: ['赌博'] }
27
+ */
28
+ var XSensitiveWords = class {
29
+ sensitiveWords = [];
30
+ constructor(options) {
31
+ this.sensitiveWords = uniq(options.words.map((word) => word.toLowerCase()));
32
+ }
33
+ /**
34
+ * 文本归一化。
35
+ *
36
+ * 依次执行:全角转半角 → 移除非单词字符 → 移除空白字符 → 转小写
37
+ * → 叠词归一。与构造时仅做“转小写 + 去重”的敏感词列表配合,
38
+ * 实现大小写不敏感、忽略格式干扰的匹配。
39
+ */
40
+ transform(text) {
41
+ return removeBlankChars(removeNonWordChars(toHalfWidthString(text))).toLowerCase().replace(/([^a-zA-Z0-9])\1+/g, "$1");
42
+ }
43
+ /**
44
+ * 验证文本是否包含敏感词。
45
+ *
46
+ * 对文本做归一化后,按顺序遍历敏感词:
47
+ * - `<手机号>`:用手机号正则测试;
48
+ * - `/.../` 形式的正则词:编译后测试;
49
+ * - 其余普通词:做子串包含判断。
50
+ *
51
+ * @param text 待校验的文本。
52
+ * @param every 是否返回命中的全部敏感词;默认为 `false`,命中一个即停止。
53
+ * @returns 校验结果,`pass` 表示是否通过,`words` 为命中的敏感词。
54
+ *
55
+ * @example
56
+ * const sw = new XSensitiveWords({ words: ['赌博', '色情'] });
57
+ * await sw.validate('赌博与色情', true);
58
+ * // { pass: false, words: ['赌博', '色情'] }
59
+ */
60
+ async validate(text, every) {
61
+ text = this.transform(text);
62
+ const words = [];
63
+ for (const word of this.sensitiveWords) if (word === "<手机号>" ? phoneNumberRegExp.test(text) : word[0] === "/" && word[word.length - 1] === "/" ? toRegExp(word).test(text) : text.includes(word)) {
64
+ words.push(word);
65
+ if (!every) break;
66
+ }
67
+ return {
68
+ pass: !words.length,
69
+ words
70
+ };
71
+ }
72
+ };
73
+ export { XSensitiveWords };
package/lib/sms.d.mts ADDED
@@ -0,0 +1,133 @@
1
+ /**
2
+ * 短信服务提供商。
3
+ */
4
+ export declare enum SmsServiceProvider {
5
+ /** 腾讯云短信。 */
6
+ TencentCloud = "TencentCloud"
7
+ }
8
+ /**
9
+ * `XSms` 的构造配置。
10
+ */
11
+ export interface XSmsOptions {
12
+ /**
13
+ * 短信服务提供商。
14
+ *
15
+ * 目前仅支持 {@link SmsServiceProvider.TencentCloud}。实际发送时以
16
+ * {@link XSmsSendPayload.provider} 为准进行路由,本字段作为实例级默认
17
+ * 配置保留,供后续扩展多提供商时使用。
18
+ */
19
+ provider: SmsServiceProvider;
20
+ /** 云厂商 API 密钥 SecretId(腾讯云访问管理 CAM 中获取)。 */
21
+ secretId: string;
22
+ /** 云厂商 API 密钥 SecretKey(腾讯云访问管理 CAM 中获取)。 */
23
+ secretKey: string;
24
+ /** 短信应用 ID(腾讯云短信控制台中的应用 SdkAppId)。 */
25
+ appId: string;
26
+ }
27
+ /**
28
+ * `XSms.send` 的发送参数。
29
+ */
30
+ export interface XSmsSendPayload {
31
+ /**
32
+ * 本次发送实际使用的短信服务提供商。
33
+ *
34
+ * `send` 依据该字段路由到对应厂商的实现;目前仅支持
35
+ * {@link SmsServiceProvider.TencentCloud}。
36
+ */
37
+ provider: SmsServiceProvider;
38
+ /** 短信签名,必须是已在对应厂商控制台审核通过的签名内容(非签名 ID)。 */
39
+ signature: string;
40
+ /** 短信模板 ID,必须是已在对应厂商控制台审核通过的模板。 */
41
+ templateId: string;
42
+ /**
43
+ * 模板变量值列表。
44
+ *
45
+ * 按顺序对应模板中的变量,个数需与模板变量个数一致;模板无变量时
46
+ * 传空数组即可。例如模板为「您的验证码为 {1},{2} 分钟内有效」时,
47
+ * 传 `['123456', '5']`。
48
+ */
49
+ templateParamValues: string[];
50
+ /**
51
+ * 接收短信的手机号。
52
+ *
53
+ * 支持两种写法:
54
+ * - 以 `+` 开头:视为 E.164 国际号码,原样使用,如 `+8613800138000`;
55
+ * - 不以 `+` 开头:视为大陆手机号,发送时自动补 `+86` 前缀,
56
+ * 如 `13800138000` → `+8613800138000`。
57
+ */
58
+ phoneNumber: string;
59
+ }
60
+ /**
61
+ * `XSms.send` 的返回结果。
62
+ */
63
+ export interface XSmsSendResult {
64
+ /** 是否发送成功。对应厂商返回的首条发送状态码是否为 `Ok`。 */
65
+ success: boolean;
66
+ /** 厂商返回的状态描述。成功时通常为 `send success`,失败时为失败原因。 */
67
+ message: string;
68
+ /** 厂商返回的短信发送流水号(SerialNo),可用于对账与查询。 */
69
+ smsId: string;
70
+ }
71
+ /**
72
+ * 短信发送工具。
73
+ *
74
+ * 统一封装各云厂商的短信发送接口,将厂商原始返回转换为统一的
75
+ * {@link XSmsSendResult}。当前支持腾讯云短信(
76
+ * {@link SmsServiceProvider.TencentCloud}),后续可在此基础上扩展更多
77
+ * 厂商。
78
+ *
79
+ * 腾讯云实现的几个关键点:
80
+ * 1. 使用 `tencentcloud-sdk-nodejs-sms` 的 `v20210111` 版本客户端;
81
+ * 2. 客户端构造必须显式传入 `region`(否则报错缺少 `Region` 参数),
82
+ * 短信用途下该值不参与实际路由,统一使用 `ap-beijing`;
83
+ * 3. 手机号规范化:以 `+` 开头视为 E.164 号码原样使用,否则自动补
84
+ * `+86` 前缀;
85
+ * 4. 单次发送一个号码,取返回的 `SendStatusSet[0]` 作为发送结果,
86
+ * `Code === 'Ok'` 视为成功;若服务商未返回任何状态则抛出错误。
87
+ *
88
+ * @example
89
+ * const sms = new XSms({
90
+ * provider: SmsServiceProvider.TencentCloud,
91
+ * secretId: 'your-secret-id',
92
+ * secretKey: 'your-secret-key',
93
+ * appId: '1400000000',
94
+ * });
95
+ *
96
+ * const result = await sms.send({
97
+ * provider: SmsServiceProvider.TencentCloud,
98
+ * signature: '我的签名',
99
+ * templateId: '1234567',
100
+ * templateParamValues: ['123456', '5'],
101
+ * phoneNumber: '13800138000',
102
+ * });
103
+ * // result = { success: true, message: 'send success', smsId: '...' }
104
+ */
105
+ export declare class XSms {
106
+ private options;
107
+ constructor(options: XSmsOptions);
108
+ /**
109
+ * 发送短信。
110
+ *
111
+ * 根据 `payload.provider` 路由到对应厂商:
112
+ * - {@link SmsServiceProvider.TencentCloud}:创建腾讯云短信客户端并调用
113
+ * `SendSms`,将返回的首条发送状态映射为 {@link XSmsSendResult};
114
+ * - 其他未知提供商:抛出错误。
115
+ *
116
+ * 手机号规范化:以 `+` 开头视为 E.164 号码原样使用,否则自动补
117
+ * `+86` 前缀后发送(与腾讯云「境内号码默认 +86」的规则一致)。
118
+ *
119
+ * @param payload 发送参数,见 {@link XSmsSendPayload}。
120
+ * @returns 发送结果,见 {@link XSmsSendResult}。
121
+ * @throws 当提供商不受支持,或服务商未返回任何发送状态时抛出错误。
122
+ *
123
+ * @example
124
+ * await sms.send({
125
+ * provider: SmsServiceProvider.TencentCloud,
126
+ * signature: '我的签名',
127
+ * templateId: '1234567',
128
+ * templateParamValues: ['123456'],
129
+ * phoneNumber: '13800138000',
130
+ * });
131
+ */
132
+ send(payload: XSmsSendPayload): Promise<XSmsSendResult>;
133
+ }