@qcplay/cli 1.0.15 → 1.0.16
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/content-rules.js +118 -15
- package/lib/wechat-article.js +8 -0
- package/package.json +1 -1
package/lib/content-rules.js
CHANGED
|
@@ -14,6 +14,10 @@ const RULE_ACTIONS = new Set([
|
|
|
14
14
|
"remove_text_block",
|
|
15
15
|
"replace_text",
|
|
16
16
|
"remove_image",
|
|
17
|
+
"remove_first_image",
|
|
18
|
+
"remove_last_image",
|
|
19
|
+
"remove_images_between_text",
|
|
20
|
+
"remove_first_video",
|
|
17
21
|
"replace_image",
|
|
18
22
|
"remove_image_caption"
|
|
19
23
|
]);
|
|
@@ -168,7 +172,15 @@ function validateAction(action, location) {
|
|
|
168
172
|
const imageFields = new Set(["src", "alt", "title", "caption", "nearby_text"]);
|
|
169
173
|
const removableImageFields = new Set([...imageFields, "sha256"]);
|
|
170
174
|
const textFields = new Set(["text"]);
|
|
171
|
-
if (
|
|
175
|
+
if (["remove_first_image", "remove_last_image", "remove_first_video"].includes(action.type)) {
|
|
176
|
+
if (action.match !== undefined) ruleError(location, `${action.type} 不支持 match`);
|
|
177
|
+
} else if (action.type === "remove_images_between_text") {
|
|
178
|
+
validateMatch(action.match, `${location}.match`, textFields);
|
|
179
|
+
validateMatch(action.end_match, `${location}.end_match`, textFields);
|
|
180
|
+
if (action.match.text === undefined || action.end_match.text === undefined) {
|
|
181
|
+
ruleError(location, "remove_images_between_text 必须提供起止 text");
|
|
182
|
+
}
|
|
183
|
+
} else if (action.type === "remove_image_caption") {
|
|
172
184
|
validateMatch(action.match || {}, `${location}.match`, imageFields);
|
|
173
185
|
} else if (action.type === "remove_image" || action.type === "replace_image") {
|
|
174
186
|
validateMatch(action.match, `${location}.match`, action.type === "remove_image" ? removableImageFields : imageFields);
|
|
@@ -343,7 +355,7 @@ function docxImageHashes(buffer) {
|
|
|
343
355
|
const commentLength = buffer.readUInt16LE(offset + 32);
|
|
344
356
|
const localHeaderOffset = buffer.readUInt32LE(offset + 42);
|
|
345
357
|
const fileName = buffer.subarray(offset + 46, offset + 46 + fileNameLength).toString("utf8");
|
|
346
|
-
if (/^word\/media
|
|
358
|
+
if (/^word\/media\/[^/]+$/i.test(fileName)) {
|
|
347
359
|
if (buffer.readUInt32LE(localHeaderOffset) !== 0x04034b50) throw new Error("DOCX 条目无效");
|
|
348
360
|
const localNameLength = buffer.readUInt16LE(localHeaderOffset + 26);
|
|
349
361
|
const localExtraLength = buffer.readUInt16LE(localHeaderOffset + 28);
|
|
@@ -368,10 +380,9 @@ function parseRuleText(text, file, imageHashes = []) {
|
|
|
368
380
|
const fields = { "规则名称": "name", "项目": "project", "地区": "region", "平台": "platform", "来源": "source", "阶段": "phase" };
|
|
369
381
|
const ignored = /^(?:#|\/\/|说明[::]?|其他要求[::]?)\s*/;
|
|
370
382
|
const unsupported = [];
|
|
371
|
-
let hasImageHashRule = false;
|
|
372
383
|
for (const rawLine of trimmed.split(/\r?\n/)) {
|
|
373
384
|
const line = rawLine.trim().replace(/^(?:[-*•]\s*|\d+[.、]\s*)/, "");
|
|
374
|
-
if (!line || ignored.test(line) || /(?:样式全部保留|样式.*保留不变|区分.*b站.*非b
|
|
385
|
+
if (!line || ignored.test(line) || /(?:样式全部保留|样式.*保留不变|区分.*b站.*非b站|可以有两篇文章|^文章.*规(?:则)?(?:修改)?$)/i.test(line)) continue;
|
|
375
386
|
if (line === "最强蜗牛") {
|
|
376
387
|
rule.project = line;
|
|
377
388
|
continue;
|
|
@@ -412,21 +423,51 @@ function parseRuleText(text, file, imageHashes = []) {
|
|
|
412
423
|
}
|
|
413
424
|
const imageSection = /^(?:b站的内容要删除如下图片|删除图片)\s*[::]?\s*$/i.test(line);
|
|
414
425
|
if (imageSection) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
426
|
+
rule.actions.push(...imageHashes.map(sha256 => ({ type: "remove_image", match: { sha256 } })));
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (/^删除第一张图片\s*[::]?\s*$/i.test(line)) {
|
|
430
|
+
rule.actions.push({ type: "remove_first_image" });
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (/^删除最后一张图片\s*[::]?\s*$/i.test(line)) {
|
|
434
|
+
rule.actions.push({ type: "remove_last_image" });
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const imagesBetween = line.match(/^删除(?:从)?\s*(?!图片[::])(.+?)\s*(?:到|至)\s*(.+?)\s*中间的图片$/);
|
|
438
|
+
if (imagesBetween) {
|
|
439
|
+
rule.actions.push({
|
|
440
|
+
type: "remove_images_between_text",
|
|
441
|
+
match: { text: imagesBetween[1].trim(), operator: "contains" },
|
|
442
|
+
end_match: { text: imagesBetween[2].trim(), operator: "contains" }
|
|
443
|
+
});
|
|
425
444
|
continue;
|
|
426
445
|
}
|
|
427
446
|
const image = line.match(/^删除图片[::]\s*(.+)$/);
|
|
428
447
|
if (image) {
|
|
429
|
-
|
|
448
|
+
const instruction = image[1].trim();
|
|
449
|
+
if (/^最后一张图片$/i.test(instruction)) {
|
|
450
|
+
rule.actions.push({ type: "remove_last_image" });
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (/^第一张图片$/i.test(instruction)) {
|
|
454
|
+
rule.actions.push({ type: "remove_first_image" });
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const nestedImagesBetween = instruction.match(/^删除(?:从)?\s*(.+?)\s*(?:到|至)\s*(.+?)\s*中间的图片$/);
|
|
458
|
+
if (nestedImagesBetween) {
|
|
459
|
+
rule.actions.push({
|
|
460
|
+
type: "remove_images_between_text",
|
|
461
|
+
match: { text: nestedImagesBetween[1].trim(), operator: "contains" },
|
|
462
|
+
end_match: { text: nestedImagesBetween[2].trim(), operator: "contains" }
|
|
463
|
+
});
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
rule.actions.push({ type: "remove_image", match: { nearby_text: instruction, operator: "contains" } });
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (/^删除视频[::]\s*第一个视频$/i.test(line)) {
|
|
470
|
+
rule.actions.push({ type: "remove_first_video" });
|
|
430
471
|
continue;
|
|
431
472
|
}
|
|
432
473
|
unsupported.push(rawLine.trim());
|
|
@@ -693,6 +734,22 @@ function replaceTextNodes($, match, replacement) {
|
|
|
693
734
|
return changed;
|
|
694
735
|
}
|
|
695
736
|
|
|
737
|
+
function documentOrder($) {
|
|
738
|
+
const nodes = [];
|
|
739
|
+
const visit = node => {
|
|
740
|
+
nodes.push(node);
|
|
741
|
+
for (const child of node.children || []) visit(child);
|
|
742
|
+
};
|
|
743
|
+
for (const node of $.root().contents().toArray()) visit(node);
|
|
744
|
+
return nodes;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function firstMatchingContentBlock($, match) {
|
|
748
|
+
return $(BLOCK_SELECTOR)
|
|
749
|
+
.toArray()
|
|
750
|
+
.find(node => matchesValue($(node).text().trim(), match.text, match));
|
|
751
|
+
}
|
|
752
|
+
|
|
696
753
|
function applyAction($, action) {
|
|
697
754
|
if (action.type === "remove_section_until_next_heading") {
|
|
698
755
|
let changed = false;
|
|
@@ -713,6 +770,44 @@ function applyAction($, action) {
|
|
|
713
770
|
if (action.type === "replace_text") {
|
|
714
771
|
return replaceTextNodes($, action.match, action.replacement);
|
|
715
772
|
}
|
|
773
|
+
if (action.type === "remove_first_image") {
|
|
774
|
+
const firstImage = $("img").first();
|
|
775
|
+
if (!firstImage.length) return false;
|
|
776
|
+
removeImage($, firstImage);
|
|
777
|
+
return true;
|
|
778
|
+
}
|
|
779
|
+
if (action.type === "remove_last_image") {
|
|
780
|
+
const lastImage = $("img").last();
|
|
781
|
+
if (!lastImage.length) return false;
|
|
782
|
+
removeImage($, lastImage);
|
|
783
|
+
return true;
|
|
784
|
+
}
|
|
785
|
+
if (action.type === "remove_first_video") {
|
|
786
|
+
const firstVideo = $("video,mp-common-videosnap").first();
|
|
787
|
+
if (!firstVideo.length) return false;
|
|
788
|
+
const container = firstVideo.closest("p,figure,div,section").first();
|
|
789
|
+
if (container.length && !container.text().trim() && container.find("video,mp-common-videosnap").length === 1) {
|
|
790
|
+
container.remove();
|
|
791
|
+
} else {
|
|
792
|
+
firstVideo.remove();
|
|
793
|
+
}
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
if (action.type === "remove_images_between_text") {
|
|
797
|
+
const startNode = firstMatchingContentBlock($, action.match);
|
|
798
|
+
const endNode = firstMatchingContentBlock($, action.end_match);
|
|
799
|
+
if (!startNode || !endNode) return false;
|
|
800
|
+
const nodes = documentOrder($);
|
|
801
|
+
const start = nodes.indexOf(startNode);
|
|
802
|
+
const end = nodes.indexOf(endNode);
|
|
803
|
+
if (start < 0 || end < 0 || start >= end) return false;
|
|
804
|
+
const images = $("img").toArray().filter(node => {
|
|
805
|
+
const position = nodes.indexOf(node);
|
|
806
|
+
return position > start && position < end;
|
|
807
|
+
});
|
|
808
|
+
images.forEach(node => removeImage($, $(node)));
|
|
809
|
+
return images.length > 0;
|
|
810
|
+
}
|
|
716
811
|
const images = $("img").toArray().filter(node => imageMatches($, $(node), action.match || {}));
|
|
717
812
|
if (action.type === "remove_image") {
|
|
718
813
|
images.forEach(node => removeImage($, $(node)));
|
|
@@ -756,6 +851,10 @@ export function applyContentRules(article, entry = {}, config = entry.contentRul
|
|
|
756
851
|
"remove_text_block",
|
|
757
852
|
"replace_text",
|
|
758
853
|
"remove_image",
|
|
854
|
+
"remove_first_image",
|
|
855
|
+
"remove_last_image",
|
|
856
|
+
"remove_images_between_text",
|
|
857
|
+
"remove_first_video",
|
|
759
858
|
"replace_image",
|
|
760
859
|
"remove_image_caption"
|
|
761
860
|
]);
|
|
@@ -767,6 +866,10 @@ export function applyContentRules(article, entry = {}, config = entry.contentRul
|
|
|
767
866
|
if (options.phase === "import") {
|
|
768
867
|
const priority = {
|
|
769
868
|
remove_image: 0,
|
|
869
|
+
remove_first_image: 0,
|
|
870
|
+
remove_last_image: 0,
|
|
871
|
+
remove_images_between_text: 0,
|
|
872
|
+
remove_first_video: 0,
|
|
770
873
|
replace_image: 0,
|
|
771
874
|
remove_image_caption: 0,
|
|
772
875
|
remove_section_until_next_heading: 1,
|
package/lib/wechat-article.js
CHANGED
|
@@ -1386,6 +1386,14 @@ export async function convertWechatHtml(html, pageUrl, options = {}) {
|
|
|
1386
1386
|
const key = assertWechatImageUrl(rawSource, sourceUrl).toString();
|
|
1387
1387
|
return uploaded.get(key);
|
|
1388
1388
|
});
|
|
1389
|
+
const retainedSources = entry.sources.filter(rawSource => {
|
|
1390
|
+
const key = assertWechatImageUrl(rawSource, sourceUrl).toString();
|
|
1391
|
+
return !ignored.has(key);
|
|
1392
|
+
});
|
|
1393
|
+
if (retainedSources.length === 0) {
|
|
1394
|
+
$(entry.element).css("background-image", "none");
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1389
1397
|
$(entry.element).attr("data-qcplay-background-processed", "1");
|
|
1390
1398
|
$(entry.element).attr("data-qcplay-background-srcs", urls.filter(Boolean).join("|"));
|
|
1391
1399
|
});
|