@twikoojs/edgeone-makers 2.0.13 → 2.0.15
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/dist/cloud-functions/index.js +65 -17
- package/package.json +5 -5
|
@@ -43,7 +43,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
43
43
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
44
44
|
//#endregion
|
|
45
45
|
//#region ../shared/dist/index.mjs
|
|
46
|
-
/*! @twikoojs/shared v2.0.
|
|
46
|
+
/*! @twikoojs/shared v2.0.15 */
|
|
47
47
|
/**
|
|
48
48
|
* Twikoo 统一版本号常量(构建期占位符)。
|
|
49
49
|
*
|
|
@@ -56,7 +56,7 @@ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
|
56
56
|
* 占位符字面量由 `@twikoojs/tsdown-config` 的 `VERSION_PLACEHOLDER` 常量定义,
|
|
57
57
|
* 客户端与服务端统一读取本常量,避免版本号在多包之间手工同步。
|
|
58
58
|
*/
|
|
59
|
-
const VERSION = "2.0.
|
|
59
|
+
const VERSION = "2.0.15";
|
|
60
60
|
/**
|
|
61
61
|
* 后端事件名常量(Scope E:24 个客户端事件 + 1 个服务端内部事件)。
|
|
62
62
|
*
|
|
@@ -24334,19 +24334,33 @@ async function getXml2js() {
|
|
|
24334
24334
|
return pickDefault(await loadLib("xml2js"));
|
|
24335
24335
|
}
|
|
24336
24336
|
/**
|
|
24337
|
-
* 获取 html-to-text
|
|
24337
|
+
* 获取 html-to-text 编译产物;通知忽略图片,长度检查按替代文本或固定占位计数。
|
|
24338
|
+
* @param options 是否使用评论长度计数规则
|
|
24338
24339
|
* @returns 编译后的转换函数
|
|
24339
24340
|
*/
|
|
24340
|
-
async function getHtmlToText() {
|
|
24341
|
+
async function getHtmlToText(options = {}) {
|
|
24341
24342
|
return pickDefault(await loadLib("html-to-text")).compile({
|
|
24342
24343
|
wordwrap: false,
|
|
24343
|
-
|
|
24344
|
-
|
|
24345
|
-
|
|
24346
|
-
|
|
24347
|
-
|
|
24348
|
-
|
|
24349
|
-
|
|
24344
|
+
formatters: {
|
|
24345
|
+
/** 图片按替代文本计数;无替代文本的图片和分隔线各占 10 个字符。 */
|
|
24346
|
+
wordCount(elem, _walk, builder) {
|
|
24347
|
+
const text = elem.name === "img" ? elem.attribs.alt?.trim() : "";
|
|
24348
|
+
builder.addInline(text || "-".repeat(10), { noWordTransform: true });
|
|
24349
|
+
} },
|
|
24350
|
+
selectors: [
|
|
24351
|
+
{
|
|
24352
|
+
selector: "a",
|
|
24353
|
+
options: { ignoreHref: true }
|
|
24354
|
+
},
|
|
24355
|
+
{
|
|
24356
|
+
selector: "img",
|
|
24357
|
+
format: options.forWordCount ? "wordCount" : "skip"
|
|
24358
|
+
},
|
|
24359
|
+
...options.forWordCount ? [{
|
|
24360
|
+
selector: "hr",
|
|
24361
|
+
format: "wordCount"
|
|
24362
|
+
}] : []
|
|
24363
|
+
]
|
|
24350
24364
|
});
|
|
24351
24365
|
}
|
|
24352
24366
|
/**
|
|
@@ -24755,22 +24769,25 @@ async function parseCommentForAdmin(comments, caps) {
|
|
|
24755
24769
|
* 语义对齐)。
|
|
24756
24770
|
*
|
|
24757
24771
|
* 链路(COMMENT_SUBMIT):
|
|
24758
|
-
* 1. preCheckSpam 预检(长度 / 屏蔽词 / 人工审核标记 /
|
|
24772
|
+
* 1. preCheckSpam 预检(长度 / 屏蔽词 / 人工审核标记 / 违禁词)——按需加载纯文本转换器;
|
|
24759
24773
|
* 2. checkCaptcha 验证码(Turnstile / Geetest / Cap 内嵌或外部);
|
|
24760
24774
|
* 3. limitFilter 频率限制(单 IP 与全站 10 分钟窗口);
|
|
24761
24775
|
* 4. postCheckSpam 后检(腾讯云 TMS / Akismet / Jev / LLM,postSubmit 异步阶段)。
|
|
24762
24776
|
*/
|
|
24763
24777
|
/**
|
|
24764
|
-
*
|
|
24778
|
+
* 预垃圾评论检测;长度限制按 HTML 转换后的文本计算。
|
|
24765
24779
|
* @param comment 待检评论字段
|
|
24766
24780
|
* @param config 全量配置
|
|
24767
24781
|
* @param logger 请求日志
|
|
24768
24782
|
* @returns 是否标记为垃圾(MANUAL_REVIEW / 违禁词)
|
|
24769
24783
|
*/
|
|
24770
|
-
function preCheckSpam(comment, config, logger) {
|
|
24784
|
+
async function preCheckSpam(comment, config, logger) {
|
|
24771
24785
|
let limitLength = parseInt(String(config.LIMIT_LENGTH ?? ""));
|
|
24772
24786
|
if (Number.isNaN(limitLength)) limitLength = 500;
|
|
24773
|
-
if (limitLength
|
|
24787
|
+
if (limitLength) {
|
|
24788
|
+
if (comment.comment.length > limitLength * 10) throw new Error("评论内容过长");
|
|
24789
|
+
if ((await getHtmlToText({ forWordCount: true }))(comment.comment).length > limitLength) throw new Error("评论内容过长");
|
|
24790
|
+
}
|
|
24774
24791
|
if (config.BLOCKED_WORDS) {
|
|
24775
24792
|
const commentLowerCase = comment.comment.toLowerCase();
|
|
24776
24793
|
const nickLowerCase = comment.nick.toLowerCase();
|
|
@@ -26540,7 +26557,7 @@ async function parseCommentDoc(ctx) {
|
|
|
26540
26557
|
}),
|
|
26541
26558
|
pid: comment.pid ? toStr(comment.pid) : toStr(comment.rid),
|
|
26542
26559
|
rid: comment.rid,
|
|
26543
|
-
isSpam: isAdminUser ? false : preCheckSpam({
|
|
26560
|
+
isSpam: isAdminUser ? false : await preCheckSpam({
|
|
26544
26561
|
comment: toStr(comment.comment),
|
|
26545
26562
|
nick: toStr(comment.nick),
|
|
26546
26563
|
link: comment.link,
|
|
@@ -27580,6 +27597,34 @@ function allowCors(request, config) {
|
|
|
27580
27597
|
"Access-Control-Max-Age": "600"
|
|
27581
27598
|
};
|
|
27582
27599
|
}
|
|
27600
|
+
/** 错误路径共用进行中的配置读取,避免并发拒绝请求堆积查询。 */
|
|
27601
|
+
const pendingCorsConfig = /* @__PURE__ */ new WeakMap();
|
|
27602
|
+
/** 错误响应只读取已有数据库的配置,最多等待 100ms;失败或超时不放行跨域,也不改变原错误。 */
|
|
27603
|
+
async function allowCorsForError(request, database) {
|
|
27604
|
+
if (!request.headers.origin) return {};
|
|
27605
|
+
let pending = pendingCorsConfig.get(database);
|
|
27606
|
+
if (!pending) {
|
|
27607
|
+
pending = (async () => {
|
|
27608
|
+
try {
|
|
27609
|
+
return await database.getConfig() ?? {};
|
|
27610
|
+
} catch (e) {
|
|
27611
|
+
console.error("Twikoo CORS configuration failed:", e);
|
|
27612
|
+
return null;
|
|
27613
|
+
}
|
|
27614
|
+
})();
|
|
27615
|
+
pendingCorsConfig.set(database, pending);
|
|
27616
|
+
pending.then(() => pendingCorsConfig.delete(database));
|
|
27617
|
+
}
|
|
27618
|
+
let timer;
|
|
27619
|
+
try {
|
|
27620
|
+
const config = await Promise.race([pending, new Promise((resolve) => {
|
|
27621
|
+
timer = setTimeout(() => resolve(null), 100);
|
|
27622
|
+
})]);
|
|
27623
|
+
return config === null ? {} : allowCors(request, config);
|
|
27624
|
+
} finally {
|
|
27625
|
+
clearTimeout(timer);
|
|
27626
|
+
}
|
|
27627
|
+
}
|
|
27583
27628
|
/**
|
|
27584
27629
|
* 计算放行的 Origin(1.x getAllowedOrigin 逐行对齐)。
|
|
27585
27630
|
* @param origin 请求 Origin
|
|
@@ -27649,13 +27694,15 @@ function createPipeline(adapters) {
|
|
|
27649
27694
|
let status = 200;
|
|
27650
27695
|
/** 步骤 3 产出,步骤 9 回填判断用 */
|
|
27651
27696
|
let accessToken = "";
|
|
27697
|
+
let corsAttempted = false;
|
|
27652
27698
|
try {
|
|
27653
27699
|
protect(request, logger);
|
|
27654
27700
|
validateClientFields(request.body);
|
|
27655
27701
|
accessToken = anonymousSignIn(request);
|
|
27702
|
+
corsAttempted = true;
|
|
27656
27703
|
await adapters.database.init();
|
|
27657
27704
|
const { config, failed: configReadFailed } = await readConfig(adapters, logger);
|
|
27658
|
-
Object.assign(headers, allowCors(request, config));
|
|
27705
|
+
if (!configReadFailed) Object.assign(headers, allowCors(request, config));
|
|
27659
27706
|
if (request.method === "OPTIONS") return {
|
|
27660
27707
|
status: 204,
|
|
27661
27708
|
body: {},
|
|
@@ -27681,6 +27728,7 @@ function createPipeline(adapters) {
|
|
|
27681
27728
|
message: getErrorMessage(e)
|
|
27682
27729
|
};
|
|
27683
27730
|
if (e instanceof RateLimitError) status = 429;
|
|
27731
|
+
if (!corsAttempted) Object.assign(headers, await allowCorsForError(request, adapters.database));
|
|
27684
27732
|
}
|
|
27685
27733
|
if (!body.code && !request.body.accessToken) body.accessToken = accessToken;
|
|
27686
27734
|
logger.verbose("请求返回:", body);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twikoojs/edgeone-makers",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.15",
|
|
4
4
|
"description": "Twikoo comment system backend for Tencent EdgeOne Makers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,19 +21,19 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@cap.js/server": "^4.0.5",
|
|
23
23
|
"@edgeone/pages-blob": "latest",
|
|
24
|
-
"@twikoojs/common": "2.0.
|
|
25
|
-
"@twikoojs/shared": "2.0.
|
|
24
|
+
"@twikoojs/common": "2.0.15",
|
|
25
|
+
"@twikoojs/shared": "2.0.15",
|
|
26
26
|
"bowser": "^2.14.1",
|
|
27
27
|
"form-data": "^4.0.6",
|
|
28
28
|
"html-to-text": "^10.0.1",
|
|
29
29
|
"marked": "^18.0.13",
|
|
30
30
|
"pako": "^3.0.2",
|
|
31
|
-
"pushoo": "^2.0.
|
|
31
|
+
"pushoo": "^2.0.15",
|
|
32
32
|
"xml2js": "^0.6.2"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@imaegoo/node-ip2region": "^2.1.1",
|
|
36
|
-
"@types/node": "^26.6.
|
|
36
|
+
"@types/node": "^26.6.4"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node scripts/build-ip2region-data.mjs && tsdown && node scripts/build-zip.mjs",
|