@cloudcreate/adsense-check 1.3.0 → 1.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.
- package/README.md +14 -4
- package/dist/{chunk-PGQWYP7I.js → chunk-XKNR4LB4.js} +555 -56
- package/dist/cli.js +62 -40
- package/dist/index.d.ts +6 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @cloudcreate/adsense-check
|
|
2
2
|
|
|
3
|
-
Automated website checker for Google AdSense review requirements. Detects "low value content" — the #1 rejection reason. Supports content sites, tool sites, and
|
|
3
|
+
Automated website checker for Google AdSense review requirements. Detects "low value content" — the #1 rejection reason. Supports content sites, tool sites, game sites, video sites, and reference sites with AI-powered topic analysis and content relevance checking.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -43,9 +43,11 @@ Automatically classifies websites into three supported types:
|
|
|
43
43
|
|
|
44
44
|
| Type | Description | Examples |
|
|
45
45
|
|------|-------------|----------|
|
|
46
|
-
| **Content** | News, blogs,
|
|
46
|
+
| **Content** | News, blogs, educational articles, guides | theexceltranslator.com |
|
|
47
47
|
| **Tool** | Online calculators, converters, generators | ishowspeedsaid.com |
|
|
48
48
|
| **Game** | Online games, game portals | popstone2.com |
|
|
49
|
+
| **Video** | Video sharing, video blogs, YouTube-style sites | — |
|
|
50
|
+
| **Reference** | Wiki, encyclopedia, glossary, knowledge base | — |
|
|
49
51
|
| **Unsupported** | Other types (e-commerce, social, etc.) | — |
|
|
50
52
|
|
|
51
53
|
AI analysis classifies the site type and topic. Falls back to DOM signal detection when AI is unavailable.
|
|
@@ -55,7 +57,7 @@ AI analysis classifies the site type and topic. Falls back to DOM signal detecti
|
|
|
55
57
|
With `--ai`, the tool analyzes the homepage to determine:
|
|
56
58
|
- **Topic**: What the site is about (e.g., "online match-3 puzzle games")
|
|
57
59
|
- **Description**: One-line summary of the site's purpose
|
|
58
|
-
- **Type**: content / tool / game / unsupported
|
|
60
|
+
- **Type**: content / tool / game / video / reference / unsupported
|
|
59
61
|
|
|
60
62
|
### Content Relevance Checking
|
|
61
63
|
|
|
@@ -101,6 +103,14 @@ Page score = geometric mean of all four dimensions. This means any weak dimensio
|
|
|
101
103
|
|
|
102
104
|
Site score = page-type weighted average across all analyzed pages (homepage and content pages have highest weight).
|
|
103
105
|
|
|
106
|
+
### AI Page Classification
|
|
107
|
+
|
|
108
|
+
With `--ai`, each page is classified by content analysis into one of: homepage, listing, content, game_detail, video_detail, reference_detail, required, utility. This overrides URL-based classification for sites with non-standard URL patterns.
|
|
109
|
+
|
|
110
|
+
### Compliance Re-check
|
|
111
|
+
|
|
112
|
+
Pages flagged with borderline compliance scores (3-5) receive a second-pass AI review to reduce false positives. Context-aware: informational/educational mentions of sensitive topics are not treated as violations.
|
|
113
|
+
|
|
104
114
|
### Single-Page Analysis
|
|
105
115
|
|
|
106
116
|
```bash
|
|
@@ -124,7 +134,7 @@ adsense-check <site> --page <url> --ai
|
|
|
124
134
|
-o, --output <dir> Report output dir (default: tmp)
|
|
125
135
|
--no-save Skip auto-saving report
|
|
126
136
|
-l, --lang <lang> Output language: en|zh (default: en)
|
|
127
|
-
--type <type> Force site type: content|tool|game
|
|
137
|
+
--type <type> Force site type: content|tool|game|video|reference
|
|
128
138
|
--detect-only Only detect site type/topic, skip full check
|
|
129
139
|
```
|
|
130
140
|
|
|
@@ -36,15 +36,13 @@ var BrowserManager = class {
|
|
|
36
36
|
async function fetchPage(page, url, timeout = 3e4) {
|
|
37
37
|
const response = await page.goto(url, { waitUntil: "domcontentloaded", timeout });
|
|
38
38
|
const status = response?.status() ?? 0;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await page.waitForLoadState("networkidle", { timeout: 1e4 });
|
|
43
|
-
} catch {
|
|
44
|
-
}
|
|
45
|
-
await page.waitForTimeout(2e3);
|
|
46
|
-
text = await page.evaluate(() => document.body?.innerText ?? "");
|
|
39
|
+
try {
|
|
40
|
+
await page.waitForLoadState("networkidle", { timeout: 1e4 });
|
|
41
|
+
} catch {
|
|
47
42
|
}
|
|
43
|
+
await page.waitForTimeout(1500);
|
|
44
|
+
const urlAfterRender = page.url();
|
|
45
|
+
let text = await page.evaluate(() => document.body?.innerText ?? "");
|
|
48
46
|
const content = await page.content();
|
|
49
47
|
const links = await page.evaluate(
|
|
50
48
|
() => Array.from(document.querySelectorAll("a[href]")).map((a) => a.href).filter((href) => href.startsWith("http"))
|
|
@@ -52,7 +50,7 @@ async function fetchPage(page, url, timeout = 3e4) {
|
|
|
52
50
|
const linkDetails = await page.evaluate(
|
|
53
51
|
() => Array.from(document.querySelectorAll("a[href]")).filter((a) => a.href.startsWith("http")).map((a) => ({
|
|
54
52
|
href: a.href,
|
|
55
|
-
text: a.innerText
|
|
53
|
+
text: a.innerText?.trim() ?? ""
|
|
56
54
|
}))
|
|
57
55
|
);
|
|
58
56
|
const navText = await page.evaluate(() => {
|
|
@@ -89,10 +87,11 @@ async function fetchPage(page, url, timeout = 3e4) {
|
|
|
89
87
|
canvasCount: document.querySelectorAll("canvas").length,
|
|
90
88
|
articleCount: document.querySelectorAll("article").length,
|
|
91
89
|
textLength: (document.body?.innerText ?? "").replace(/\s+/g, "").length,
|
|
92
|
-
gameLinks
|
|
90
|
+
gameLinks,
|
|
91
|
+
videoElementCount: document.querySelectorAll("video").length
|
|
93
92
|
};
|
|
94
93
|
});
|
|
95
|
-
return { status, content, text, links, linkDetails, navText, footerText, title, url, signals };
|
|
94
|
+
return { status, content, text, links, linkDetails, navText, footerText, title, url: urlAfterRender, signals };
|
|
96
95
|
}
|
|
97
96
|
async function checkRobotsTxt(origin) {
|
|
98
97
|
try {
|
|
@@ -180,6 +179,10 @@ var en = {
|
|
|
180
179
|
"item.content.game_desc": "Game Descriptions",
|
|
181
180
|
"item.content.iframe_quality": "Iframe Quality",
|
|
182
181
|
"item.content.game_variety": "Game Variety",
|
|
182
|
+
"item.content.video_desc": "Video Descriptions",
|
|
183
|
+
"item.content.video_variety": "Video Variety",
|
|
184
|
+
"item.content.reference_entry": "Reference Entries",
|
|
185
|
+
"item.content.reference_variety": "Reference Variety",
|
|
183
186
|
// Structure items
|
|
184
187
|
"item.structure.internal": "Internal Links",
|
|
185
188
|
"item.structure.deadlinks": "Dead Links",
|
|
@@ -230,9 +233,21 @@ var en = {
|
|
|
230
233
|
"content.iframe_quality.warn": "{count} game iframes detected \u2014 ensure each has proper title and size attributes",
|
|
231
234
|
"content.game_variety.pass": "Game pages show good variety",
|
|
232
235
|
"content.game_variety.warn": "Game pages are {pct}% similar \u2014 may look like mass-produced content",
|
|
236
|
+
// Video-specific messages
|
|
237
|
+
"content.video_desc.pass": "{total} video page(s) have sufficient description text",
|
|
238
|
+
"content.video_desc.warn": "{thin}/{total} video pages lack description text (recommend 50+ chars)",
|
|
239
|
+
"content.video_variety.pass": "Video pages show good variety (similarity {pct}%)",
|
|
240
|
+
"content.video_variety.warn": "Video pages are {pct}% similar \u2014 may look like mass-produced content",
|
|
241
|
+
// Reference-specific messages
|
|
242
|
+
"content.reference_entry.pass": "Reference entries have sufficient structure and metadata",
|
|
243
|
+
"content.reference_entry.warn": "{thin}/{total} reference entries lack structure (recommend 100+ chars)",
|
|
244
|
+
"content.reference_variety.pass": "Reference pages show good variety (similarity {pct}%)",
|
|
245
|
+
"content.reference_variety.warn": "Reference pages are {pct}% similar \u2014 expected for structured entries",
|
|
233
246
|
// Site type detection
|
|
234
247
|
"detector.type.content": "Content Site",
|
|
235
248
|
"detector.type.game": "Game Site",
|
|
249
|
+
"detector.type.video": "Video Site",
|
|
250
|
+
"detector.type.reference": "Reference Site",
|
|
236
251
|
"detector.signals": "Signals: {details}",
|
|
237
252
|
// Required pages messages
|
|
238
253
|
"pages.found": "Found {name} page ({path})",
|
|
@@ -266,6 +281,7 @@ var en = {
|
|
|
266
281
|
// Policy messages
|
|
267
282
|
"policy.keywords.pass": "No policy-violating keywords found",
|
|
268
283
|
"policy.keywords.fail": "{count} potentially violating keyword(s) found",
|
|
284
|
+
"policy.keywords.warn": "{count} potentially violating keyword(s) found (on pages with substantial content \u2014 verify with AI analysis)",
|
|
269
285
|
// AI messages
|
|
270
286
|
"ai.skip": "AI_API_KEY not configured, skipping AI analysis",
|
|
271
287
|
"ai.fail": "AI analysis failed: {error}",
|
|
@@ -312,7 +328,68 @@ var en = {
|
|
|
312
328
|
"detector.type.unsupported": "Unsupported Type",
|
|
313
329
|
"topic.info": "Site topic: {topic}",
|
|
314
330
|
"topic.description": "{description}",
|
|
315
|
-
"topic.unsupported_warning": "This site type ({type}) is not supported by AdSense checklist"
|
|
331
|
+
"topic.unsupported_warning": "This site type ({type}) is not supported by AdSense checklist",
|
|
332
|
+
// Reporter UI
|
|
333
|
+
"reporter.site_type": "Site type",
|
|
334
|
+
"reporter.topic": "Topic",
|
|
335
|
+
"reporter.pages_label": "Pages",
|
|
336
|
+
"reporter.confidence": "{confidence} confidence",
|
|
337
|
+
"reporter.ai_value_label": "AI Value Score",
|
|
338
|
+
"reporter.ai_value_note": "geometric mean \xD7 page-type weights",
|
|
339
|
+
"reporter.ai_dimensions": "AI Dimensions",
|
|
340
|
+
"reporter.avg_per_10": "avg /10",
|
|
341
|
+
"reporter.dim_value": "Value",
|
|
342
|
+
"reporter.dim_originality": "Originality",
|
|
343
|
+
"reporter.dim_relevance": "Relevance",
|
|
344
|
+
"reporter.dim_compliance": "Compliance",
|
|
345
|
+
"reporter.formula_label": "Hard {hardPct}% \xD7 0.4 + Soft {softPct}% \xD7 0.6 - Penalty {penalty} = {total}",
|
|
346
|
+
"reporter.mechanical_label": "mechanical",
|
|
347
|
+
// Markdown report
|
|
348
|
+
"md.report_title": "AdSense Review Report",
|
|
349
|
+
"md.table.project": "Item",
|
|
350
|
+
"md.table.value": "Value",
|
|
351
|
+
"md.table.url": "URL",
|
|
352
|
+
"md.table.time": "Time",
|
|
353
|
+
"md.table.site_type": "Site Type",
|
|
354
|
+
"md.table.topic": "Topic",
|
|
355
|
+
"md.table.description": "Description",
|
|
356
|
+
"md.table.sampling": "Sampling",
|
|
357
|
+
"md.table.total": "total",
|
|
358
|
+
"md.table.recent": "recent (6mo)",
|
|
359
|
+
"md.table.sampled": "sampled",
|
|
360
|
+
"md.table.confidence": "confidence",
|
|
361
|
+
"md.composite_score_title": "Composite Score",
|
|
362
|
+
"md.hard_requirements": "Hard Requirements",
|
|
363
|
+
"md.soft_scoring": "Soft Scoring",
|
|
364
|
+
"md.ai_value_title": "AI Value Analysis",
|
|
365
|
+
"md.table.dimension": "Dimension",
|
|
366
|
+
"md.table.avg_score": "Avg Score",
|
|
367
|
+
"md.dim_value": "Value",
|
|
368
|
+
"md.dim_originality": "Originality",
|
|
369
|
+
"md.dim_relevance": "Relevance",
|
|
370
|
+
"md.dim_compliance": "Compliance",
|
|
371
|
+
"md.site_ai_score": "Site AI Score",
|
|
372
|
+
"md.geometric_weighted": "geometric mean \xD7 page-type weights",
|
|
373
|
+
"md.page_details": "Page Details",
|
|
374
|
+
"md.pages_count": "{count} pages",
|
|
375
|
+
"md.table.status": "Status",
|
|
376
|
+
"md.table.type": "Type",
|
|
377
|
+
"md.table.path": "Path",
|
|
378
|
+
"md.table.score": "Score",
|
|
379
|
+
"md.table.content_ratio": "Content Ratio",
|
|
380
|
+
"md.table.ai_composite": "AI Composite",
|
|
381
|
+
"md.table.title": "Title",
|
|
382
|
+
"md.problem_pages": "Problem Page Details",
|
|
383
|
+
"md.ai_status": "AI Status",
|
|
384
|
+
"md.four_dimensions": "Four-dimension scores",
|
|
385
|
+
"md.ai_composite_score": "AI composite score",
|
|
386
|
+
"md.geometric_mean": "geometric mean",
|
|
387
|
+
"md.assessment": "Assessment",
|
|
388
|
+
"md.suggestions": "Suggestions",
|
|
389
|
+
"md.summary.not_ready": "**\u274C NOT READY** \u2014 {count} item(s) must be fixed",
|
|
390
|
+
"md.summary.needs_fixes": "**\u26A0\uFE0F NEEDS FIXES** \u2014 {count} warning(s) to address",
|
|
391
|
+
"md.summary.mostly_ready": "**\u26A0\uFE0F MOSTLY READY** \u2014 fix {count} warning(s) before submitting",
|
|
392
|
+
"md.summary.ready": "**\u2705 READY** \u2014 can submit for AdSense review"
|
|
316
393
|
};
|
|
317
394
|
var zh = {
|
|
318
395
|
// 分类
|
|
@@ -334,6 +411,10 @@ var zh = {
|
|
|
334
411
|
"item.content.game_desc": "\u6E38\u620F\u63CF\u8FF0",
|
|
335
412
|
"item.content.iframe_quality": "Iframe \u8D28\u91CF",
|
|
336
413
|
"item.content.game_variety": "\u6E38\u620F\u591A\u6837\u6027",
|
|
414
|
+
"item.content.video_desc": "\u89C6\u9891\u63CF\u8FF0",
|
|
415
|
+
"item.content.video_variety": "\u89C6\u9891\u591A\u6837\u6027",
|
|
416
|
+
"item.content.reference_entry": "\u53C2\u8003\u6761\u76EE\u5B8C\u6574\u6027",
|
|
417
|
+
"item.content.reference_variety": "\u53C2\u8003\u591A\u6837\u6027",
|
|
337
418
|
// 结构检查项
|
|
338
419
|
"item.structure.internal": "\u5185\u90E8\u94FE\u63A5",
|
|
339
420
|
"item.structure.deadlinks": "\u6B7B\u94FE\u68C0\u6D4B",
|
|
@@ -384,9 +465,21 @@ var zh = {
|
|
|
384
465
|
"content.iframe_quality.warn": "\u68C0\u6D4B\u5230 {count} \u4E2A\u6E38\u620F iframe \u2014 \u786E\u4FDD\u6BCF\u4E2A\u90FD\u6709 title \u548C\u5408\u7406\u5C3A\u5BF8",
|
|
385
466
|
"content.game_variety.pass": "\u6E38\u620F\u9875\u9762\u591A\u6837\u6027\u6B63\u5E38",
|
|
386
467
|
"content.game_variety.warn": "\u6E38\u620F\u9875\u9762\u76F8\u4F3C\u5EA6 {pct}% \u2014 \u53EF\u80FD\u662F\u6A21\u677F\u6279\u91CF\u751F\u6210",
|
|
468
|
+
// 视频站专用消息
|
|
469
|
+
"content.video_desc.pass": "{total} \u4E2A\u89C6\u9891\u9875\u9762\u6709\u8DB3\u591F\u7684\u63CF\u8FF0\u6587\u5B57",
|
|
470
|
+
"content.video_desc.warn": "{thin}/{total} \u4E2A\u89C6\u9891\u9875\u9762\u7F3A\u5C11\u63CF\u8FF0\u6587\u5B57\uFF08\u5EFA\u8BAE 50+ \u5B57\uFF09",
|
|
471
|
+
"content.video_variety.pass": "\u89C6\u9891\u9875\u9762\u591A\u6837\u6027\u6B63\u5E38 (\u76F8\u4F3C\u5EA6 {pct}%)",
|
|
472
|
+
"content.video_variety.warn": "\u89C6\u9891\u9875\u9762\u76F8\u4F3C\u5EA6 {pct}% \u2014 \u53EF\u80FD\u662F\u6A21\u677F\u6279\u91CF\u751F\u6210",
|
|
473
|
+
// 参考站专用消息
|
|
474
|
+
"content.reference_entry.pass": "\u53C2\u8003\u6761\u76EE\u5177\u6709\u8DB3\u591F\u7684\u7ED3\u6784\u548C\u5143\u6570\u636E",
|
|
475
|
+
"content.reference_entry.warn": "{thin}/{total} \u4E2A\u53C2\u8003\u6761\u76EE\u7ED3\u6784\u4E0D\u8DB3\uFF08\u5EFA\u8BAE 100+ \u5B57\uFF09",
|
|
476
|
+
"content.reference_variety.pass": "\u53C2\u8003\u9875\u9762\u591A\u6837\u6027\u6B63\u5E38 (\u76F8\u4F3C\u5EA6 {pct}%)",
|
|
477
|
+
"content.reference_variety.warn": "\u53C2\u8003\u9875\u9762\u76F8\u4F3C\u5EA6 {pct}% \u2014 \u7ED3\u6784\u5316\u6761\u76EE\u5C5E\u4E8E\u6B63\u5E38",
|
|
387
478
|
// 站点类型检测
|
|
388
479
|
"detector.type.content": "\u5185\u5BB9\u7AD9",
|
|
389
480
|
"detector.type.game": "\u6E38\u620F\u7AD9",
|
|
481
|
+
"detector.type.video": "\u89C6\u9891\u7AD9",
|
|
482
|
+
"detector.type.reference": "\u53C2\u8003\u7AD9",
|
|
390
483
|
"detector.signals": "\u68C0\u6D4B\u4FE1\u53F7: {details}",
|
|
391
484
|
// 必要页面消息
|
|
392
485
|
"pages.found": "\u627E\u5230 {name} \u9875\u9762 ({path})",
|
|
@@ -420,6 +513,7 @@ var zh = {
|
|
|
420
513
|
// 合规消息
|
|
421
514
|
"policy.keywords.pass": "\u672A\u68C0\u6D4B\u5230\u660E\u663E\u7684\u8FDD\u89C4\u5173\u952E\u8BCD",
|
|
422
515
|
"policy.keywords.fail": "\u68C0\u6D4B\u5230 {count} \u4E2A\u53EF\u7591\u5173\u952E\u8BCD",
|
|
516
|
+
"policy.keywords.warn": "\u68C0\u6D4B\u5230 {count} \u4E2A\u53EF\u7591\u5173\u952E\u8BCD\uFF08\u4F4D\u4E8E\u5185\u5BB9\u5145\u5B9E\u9875\u9762\uFF0C\u8BF7\u7ED3\u5408 AI \u5206\u6790\u786E\u8BA4\uFF09",
|
|
423
517
|
// AI 消息
|
|
424
518
|
"ai.skip": "\u672A\u914D\u7F6E AI_API_KEY\uFF0C\u8DF3\u8FC7 AI \u5206\u6790",
|
|
425
519
|
"ai.fail": "AI \u5206\u6790\u5931\u8D25: {error}",
|
|
@@ -466,7 +560,69 @@ var zh = {
|
|
|
466
560
|
"detector.type.unsupported": "\u4E0D\u652F\u6301\u7684\u7C7B\u578B",
|
|
467
561
|
"topic.info": "\u7AD9\u70B9\u4E3B\u9898: {topic}",
|
|
468
562
|
"topic.description": "{description}",
|
|
469
|
-
"topic.unsupported_warning": "\u8BE5\u7AD9\u70B9\u7C7B\u578B\uFF08{type}\uFF09\u4E0D\u5728 AdSense \u68C0\u67E5\u652F\u6301\u8303\u56F4\u5185"
|
|
563
|
+
"topic.unsupported_warning": "\u8BE5\u7AD9\u70B9\u7C7B\u578B\uFF08{type}\uFF09\u4E0D\u5728 AdSense \u68C0\u67E5\u652F\u6301\u8303\u56F4\u5185",
|
|
564
|
+
// 报告 UI(终端)
|
|
565
|
+
"reporter.site_type": "\u7AD9\u70B9\u7C7B\u578B",
|
|
566
|
+
"reporter.topic": "\u4E3B\u9898",
|
|
567
|
+
"reporter.pages_label": "\u9875\u9762",
|
|
568
|
+
"reporter.confidence": "\u7F6E\u4FE1\u5EA6: {confidence}",
|
|
569
|
+
"reporter.ai_value_label": "AI \u4EF7\u503C\u8BC4\u5206",
|
|
570
|
+
"reporter.ai_value_note": "\u51E0\u4F55\u5747\u503C \xD7 \u9875\u9762\u7C7B\u578B\u52A0\u6743",
|
|
571
|
+
"reporter.ai_dimensions": "AI \u7EF4\u5EA6",
|
|
572
|
+
"reporter.avg_per_10": "\u5747\u5206 /10",
|
|
573
|
+
"reporter.formula_label": "\u786C\u6027 {hardPct}% \xD7 0.4 + \u67D4\u6027 {softPct}% \xD7 0.6 - \u6263\u5206 {penalty} = {total}",
|
|
574
|
+
"reporter.mechanical_label": "\u673A\u68B0\u8BC4\u5206",
|
|
575
|
+
// 维度名称(终端和 Markdown)
|
|
576
|
+
"reporter.dim_value": "\u4EF7\u503C",
|
|
577
|
+
"reporter.dim_originality": "\u539F\u521B",
|
|
578
|
+
"reporter.dim_relevance": "\u76F8\u5173",
|
|
579
|
+
"reporter.dim_compliance": "\u5408\u89C4",
|
|
580
|
+
// Markdown 报告
|
|
581
|
+
"md.report_title": "AdSense \u5BA1\u6838\u62A5\u544A",
|
|
582
|
+
"md.table.project": "\u9879\u76EE",
|
|
583
|
+
"md.table.value": "\u503C",
|
|
584
|
+
"md.table.url": "URL",
|
|
585
|
+
"md.table.time": "\u65F6\u95F4",
|
|
586
|
+
"md.table.site_type": "\u7AD9\u70B9\u7C7B\u578B",
|
|
587
|
+
"md.table.topic": "\u4E3B\u9898",
|
|
588
|
+
"md.table.description": "\u63CF\u8FF0",
|
|
589
|
+
"md.table.sampling": "\u62BD\u6837",
|
|
590
|
+
"md.table.total": "\u603B\u8BA1",
|
|
591
|
+
"md.table.recent": "\u8FD1 6 \u4E2A\u6708",
|
|
592
|
+
"md.table.sampled": "\u5DF2\u62BD\u6837",
|
|
593
|
+
"md.table.confidence": "\u7F6E\u4FE1\u5EA6",
|
|
594
|
+
"md.composite_score_title": "\u7EFC\u5408\u8BC4\u5206",
|
|
595
|
+
"md.hard_requirements": "\u786C\u6027\u8981\u6C42",
|
|
596
|
+
"md.soft_scoring": "\u67D4\u6027\u8BC4\u5206",
|
|
597
|
+
"md.ai_value_title": "AI \u4EF7\u503C\u5206\u6790",
|
|
598
|
+
"md.table.dimension": "\u7EF4\u5EA6",
|
|
599
|
+
"md.table.avg_score": "\u5747\u5206",
|
|
600
|
+
"md.dim_value": "\u4EF7\u503C",
|
|
601
|
+
"md.dim_originality": "\u539F\u521B",
|
|
602
|
+
"md.dim_relevance": "\u76F8\u5173",
|
|
603
|
+
"md.dim_compliance": "\u5408\u89C4",
|
|
604
|
+
"md.site_ai_score": "\u7AD9\u70B9 AI \u8BC4\u5206",
|
|
605
|
+
"md.geometric_weighted": "\u51E0\u4F55\u5747\u503C \xD7 \u9875\u9762\u7C7B\u578B\u52A0\u6743",
|
|
606
|
+
"md.page_details": "\u9010\u9875\u8BE6\u60C5",
|
|
607
|
+
"md.pages_count": "{count} \u4E2A\u9875\u9762",
|
|
608
|
+
"md.table.status": "\u72B6\u6001",
|
|
609
|
+
"md.table.type": "\u7C7B\u578B",
|
|
610
|
+
"md.table.path": "\u8DEF\u5F84",
|
|
611
|
+
"md.table.score": "\u8BC4\u5206",
|
|
612
|
+
"md.table.content_ratio": "\u6B63\u6587\u6BD4",
|
|
613
|
+
"md.table.ai_composite": "AI \u7EFC\u5408",
|
|
614
|
+
"md.table.title": "\u6807\u9898",
|
|
615
|
+
"md.problem_pages": "\u95EE\u9898\u9875\u9762\u8BE6\u60C5",
|
|
616
|
+
"md.ai_status": "AI \u72B6\u6001",
|
|
617
|
+
"md.four_dimensions": "\u56DB\u7EF4\u8BC4\u5206",
|
|
618
|
+
"md.ai_composite_score": "AI \u7EFC\u5408\u5206",
|
|
619
|
+
"md.geometric_mean": "\u51E0\u4F55\u5747\u503C",
|
|
620
|
+
"md.assessment": "\u8BC4\u4F30",
|
|
621
|
+
"md.suggestions": "\u6539\u8FDB\u5EFA\u8BAE",
|
|
622
|
+
"md.summary.not_ready": "**\u274C NOT READY** \u2014 {count} \u9879\u5931\u8D25\u9700\u8981\u4FEE\u590D",
|
|
623
|
+
"md.summary.needs_fixes": "**\u26A0\uFE0F NEEDS FIXES** \u2014 {count} \u9879\u8B66\u544A\u5F85\u4FEE\u590D",
|
|
624
|
+
"md.summary.mostly_ready": "**\u26A0\uFE0F MOSTLY READY** \u2014 \u4FEE\u590D {count} \u9879\u8B66\u544A\u540E\u53EF\u63D0\u4EA4\u5BA1\u6838",
|
|
625
|
+
"md.summary.ready": "**\u2705 READY** \u2014 \u53EF\u4EE5\u63D0\u4EA4 AdSense \u5BA1\u6838"
|
|
470
626
|
};
|
|
471
627
|
var langMap = { en, zh };
|
|
472
628
|
function getSupportedLangs() {
|
|
@@ -552,6 +708,21 @@ Score each dimension from 0 to 10:
|
|
|
552
708
|
Also set "relevanceLabel": "relevant" | "tangential" | "off-topic".
|
|
553
709
|
4. compliance (0-10): Does the content comply with Google AdSense policies? 10 = fully compliant, 0 = serious violations.
|
|
554
710
|
Flag: adult content, gambling, drugs, violence, copyright infringement, deceptive content.
|
|
711
|
+
Important context rules:
|
|
712
|
+
- Words like "crack", "bet", "drug", "gamble" used in educational, news, or informational contexts are NOT violations.
|
|
713
|
+
- If the page discusses or reports on sensitive topics (e.g., "puzzle crack" as a news headline, "betting odds" in sports analysis), this is NOT a violation.
|
|
714
|
+
- Only flag actual promotion or facilitation of policy-violating content.
|
|
715
|
+
- If the page appears to be a 404 error page or has minimal content, do not flag it as a compliance violation. Note it as "insufficient content for compliance review".
|
|
716
|
+
|
|
717
|
+
Also classify the page type based on its content and purpose. Choose ONE:
|
|
718
|
+
- "homepage": The site's main landing page
|
|
719
|
+
- "listing": An index/category page listing multiple items (articles, mods, products)
|
|
720
|
+
- "content": A standalone article, blog post, guide, or tutorial
|
|
721
|
+
- "game_detail": A game page with playable game or game download
|
|
722
|
+
- "video_detail": A page centered around a video or video embed
|
|
723
|
+
- "reference_detail": A wiki entry, glossary term, encyclopedia article, or database record
|
|
724
|
+
- "required": About, Privacy, Terms, Contact, Editorial Policy, Legal
|
|
725
|
+
- "utility": Search, Login, Signup, Download, 404, or functional tool pages
|
|
555
726
|
|
|
556
727
|
Page: ${page.url}
|
|
557
728
|
|
|
@@ -565,6 +736,7 @@ Reply in ${langName} with JSON:
|
|
|
565
736
|
"relevance": <0-10>,
|
|
566
737
|
"relevanceLabel": "relevant|tangential|off-topic",
|
|
567
738
|
"compliance": <0-10>,
|
|
739
|
+
"pageType": "homepage|listing|content|game_detail|video_detail|reference_detail|required|utility",
|
|
568
740
|
"assessment": "Brief assessment covering the key findings across all dimensions",
|
|
569
741
|
"suggestions": ["Specific actionable suggestion to improve this page"]
|
|
570
742
|
}`;
|
|
@@ -577,6 +749,8 @@ Reply in ${langName} with JSON:
|
|
|
577
749
|
const complianceScore = clampScore(result.compliance);
|
|
578
750
|
const geoMean = Math.pow(valueScore * originalityScore * relevanceScore * complianceScore, 0.25);
|
|
579
751
|
const status = geoMean >= 7 ? "pass" : geoMean >= 4 ? "warn" : "fail";
|
|
752
|
+
const validPageTypes = ["homepage", "listing", "content", "game_detail", "video_detail", "reference_detail", "required", "utility"];
|
|
753
|
+
const inferredPageType = validPageTypes.includes(result.pageType) ? result.pageType : void 0;
|
|
580
754
|
return {
|
|
581
755
|
url: page.url,
|
|
582
756
|
status,
|
|
@@ -586,7 +760,8 @@ Reply in ${langName} with JSON:
|
|
|
586
760
|
relevanceScore,
|
|
587
761
|
complianceScore,
|
|
588
762
|
assessment: result.assessment ?? "",
|
|
589
|
-
suggestions: result.suggestions ?? []
|
|
763
|
+
suggestions: result.suggestions ?? [],
|
|
764
|
+
inferredPageType
|
|
590
765
|
};
|
|
591
766
|
} catch (err) {
|
|
592
767
|
return {
|
|
@@ -602,6 +777,62 @@ function clampScore(v) {
|
|
|
602
777
|
if (isNaN(n)) return 5;
|
|
603
778
|
return Math.max(0, Math.min(10, Math.round(n)));
|
|
604
779
|
}
|
|
780
|
+
async function recheckCompliance(pages, langName, onProgress) {
|
|
781
|
+
const result = /* @__PURE__ */ new Map();
|
|
782
|
+
if (pages.length === 0) return result;
|
|
783
|
+
const progress = onProgress ?? (() => {
|
|
784
|
+
});
|
|
785
|
+
progress(`AI: re-checking ${pages.length} suspicious page(s) for compliance...`);
|
|
786
|
+
for (const page of pages) {
|
|
787
|
+
const content = page.text.slice(0, PAGE_CHARS);
|
|
788
|
+
const prompt = `You are a Google AdSense policy compliance expert. A previous analysis flagged this page as potentially non-compliant (score: ${page.firstComplianceScore}/10). Perform a careful second review.
|
|
789
|
+
|
|
790
|
+
Focus ONLY on compliance. Check for:
|
|
791
|
+
- Adult or sexually explicit content
|
|
792
|
+
- Gambling or casino promotion
|
|
793
|
+
- Illegal drugs or controlled substances
|
|
794
|
+
- Violence, gore, or hate speech
|
|
795
|
+
- Copyright infringement or pirated content
|
|
796
|
+
- Deceptive content, phishing, or scams
|
|
797
|
+
- Excessive profanity
|
|
798
|
+
- Misleading medical/financial claims
|
|
799
|
+
- Content that targets children inappropriately
|
|
800
|
+
|
|
801
|
+
Be fair \u2014 informational/educational content ABOUT sensitive topics (e.g., health articles, news reporting) is NOT a violation. Only flag actual policy violations.
|
|
802
|
+
|
|
803
|
+
Additional instructions:
|
|
804
|
+
- If the page text is very short (< 200 characters) and appears to be an error page, 404, or placeholder, do not flag any compliance violations. Score compliance as 10 and note "insufficient content".
|
|
805
|
+
- Context matters: words that match policy keywords but appear in news reporting, educational content, or informational discussion are NOT violations.
|
|
806
|
+
|
|
807
|
+
Page: ${page.url}
|
|
808
|
+
|
|
809
|
+
Content:
|
|
810
|
+
${content}
|
|
811
|
+
|
|
812
|
+
Reply in ${langName} with JSON:
|
|
813
|
+
{
|
|
814
|
+
"compliance": <0-10>,
|
|
815
|
+
"verdict": "compliant|borderline|violation",
|
|
816
|
+
"assessment": "Brief explanation of your compliance determination"
|
|
817
|
+
}`;
|
|
818
|
+
try {
|
|
819
|
+
const text = await callAI(prompt, 1024);
|
|
820
|
+
const r = extractJson(text);
|
|
821
|
+
const newScore = clampScore(r.compliance);
|
|
822
|
+
const finalScore = Math.max(page.firstComplianceScore, newScore);
|
|
823
|
+
result.set(page.url, {
|
|
824
|
+
complianceScore: finalScore,
|
|
825
|
+
assessment: r.assessment ?? ""
|
|
826
|
+
});
|
|
827
|
+
} catch {
|
|
828
|
+
result.set(page.url, {
|
|
829
|
+
complianceScore: page.firstComplianceScore,
|
|
830
|
+
assessment: "Re-check failed, keeping original score"
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return result;
|
|
835
|
+
}
|
|
605
836
|
async function analyzeOverall(pageAnalyses, langName, date) {
|
|
606
837
|
const summaries = pageAnalyses.map(
|
|
607
838
|
(p, i) => `Page ${i + 1} (${p.url}): [${p.status}] value=${p.valueScore} originality=${p.originalityScore} relevance=${p.relevanceScore} compliance=${p.complianceScore} \u2014 ${p.assessment.slice(0, 150)}`
|
|
@@ -705,7 +936,7 @@ function extractJson2(text) {
|
|
|
705
936
|
}
|
|
706
937
|
throw new Error("No JSON found in response");
|
|
707
938
|
}
|
|
708
|
-
var VALID_TYPES = ["content", "tool", "game"];
|
|
939
|
+
var VALID_TYPES = ["content", "tool", "game", "video", "reference"];
|
|
709
940
|
async function analyzeSiteTopic(homepage, lang = "en", apiKey) {
|
|
710
941
|
const langName = lang === "zh" ? "\u4E2D\u6587" : "English";
|
|
711
942
|
const content = homepage.text.slice(0, 2e3);
|
|
@@ -717,16 +948,18 @@ Homepage content (first 2000 chars):
|
|
|
717
948
|
${content}
|
|
718
949
|
|
|
719
950
|
Classify this website into ONE of these types:
|
|
720
|
-
- "content": informational site (news, blog,
|
|
951
|
+
- "content": informational site (news, blog, educational articles, guides)
|
|
721
952
|
- "tool": utility/tool site (calculator, converter, generator, online tool)
|
|
722
953
|
- "game": online game site (playable games, game portal)
|
|
954
|
+
- "video": video site (video sharing, video blog, YouTube-style site with embedded videos)
|
|
955
|
+
- "reference": wiki/encyclopedia/reference site (structured knowledge base, searchable database, glossary, dictionary, encyclopedia-style content with interlinked articles, transcript archive)
|
|
723
956
|
- "unsupported": e-commerce, SaaS product, social media, forum, portfolio, or anything not fitting above categories
|
|
724
957
|
|
|
725
958
|
Reply language: ${langName}
|
|
726
959
|
|
|
727
960
|
Reply in ${langName} with JSON:
|
|
728
961
|
{
|
|
729
|
-
"type": "content|tool|game|unsupported",
|
|
962
|
+
"type": "content|tool|game|video|reference|unsupported",
|
|
730
963
|
"topic": "Main topic in 3-5 words (e.g. 'Excel translation reference')",
|
|
731
964
|
"description": "One sentence describing what this site does",
|
|
732
965
|
"confidence": "high|medium|low",
|
|
@@ -759,6 +992,10 @@ var GAME_NAV_KEYWORDS = /\b(games?|play\b|arcade|puzzle|action)\b/i;
|
|
|
759
992
|
var GAME_NAV_KEYWORDS_ZH = /游戏|玩游戏/;
|
|
760
993
|
var TOOL_NAV_KEYWORDS = /\b(calculator|converter|generator|tool|translat|calculat|checker|analyzer|formatter|validator|encoder|decoder)\b/i;
|
|
761
994
|
var TOOL_NAV_KEYWORDS_ZH = /计算器|转换器|工具|翻译/;
|
|
995
|
+
var REFERENCE_NAV_KEYWORDS = /\b(wiki|encyclopedia|reference|glossary|docs|documentation|knowledge\s*base|archive|database|transcript)\b/i;
|
|
996
|
+
var REFERENCE_NAV_KEYWORDS_ZH = /百科|知识库|参考|词典|文档|数据库|档案/;
|
|
997
|
+
var VIDEO_NAV_KEYWORDS = /\b(video|videos|watch|channel|channels|stream|vlog|clip|playlist|shorts|tv)\b/i;
|
|
998
|
+
var VIDEO_NAV_KEYWORDS_ZH = /视频|频道|直播|短视频/;
|
|
762
999
|
var GAME_IFRAME_PATTERNS = [
|
|
763
1000
|
/game/i,
|
|
764
1001
|
/play/i,
|
|
@@ -773,9 +1010,27 @@ var GAME_IFRAME_PATTERNS = [
|
|
|
773
1010
|
/friv/i,
|
|
774
1011
|
/itch\.io/i,
|
|
775
1012
|
/htmlgames/i,
|
|
776
|
-
/gameflare/i
|
|
777
|
-
/embed/i
|
|
1013
|
+
/gameflare/i
|
|
778
1014
|
];
|
|
1015
|
+
var VIDEO_IFRAME_PATTERNS = [
|
|
1016
|
+
/youtube\.com\/embed/i,
|
|
1017
|
+
/youtube-nocookie\.com/i,
|
|
1018
|
+
/youtu\.be/i,
|
|
1019
|
+
/player\.vimeo\.com/i,
|
|
1020
|
+
/player\.bilibili\.com/i,
|
|
1021
|
+
/dailymotion\.com\/embed/i,
|
|
1022
|
+
/embed\.twitch\.tv/i,
|
|
1023
|
+
/streamable\.com\/o/i,
|
|
1024
|
+
/wistia.*\.net\/medias/i,
|
|
1025
|
+
/vidyard\.com\/embed/i,
|
|
1026
|
+
/brightcove/i
|
|
1027
|
+
];
|
|
1028
|
+
function isVideoIframe(src) {
|
|
1029
|
+
return VIDEO_IFRAME_PATTERNS.some((p) => p.test(src));
|
|
1030
|
+
}
|
|
1031
|
+
function isGameIframe(src) {
|
|
1032
|
+
return GAME_IFRAME_PATTERNS.some((p) => p.test(src));
|
|
1033
|
+
}
|
|
779
1034
|
function detectSiteType(pagesSignals, navText, manualType) {
|
|
780
1035
|
if (manualType) {
|
|
781
1036
|
return { type: manualType, confidence: "high", signals: { iframeRatio: 0, canvasRatio: 0, articleRatio: 0, navGameKeywords: false } };
|
|
@@ -788,28 +1043,55 @@ function detectSiteType(pagesSignals, navText, manualType) {
|
|
|
788
1043
|
let pagesWithCanvas = 0;
|
|
789
1044
|
let pagesWithArticle = 0;
|
|
790
1045
|
let pagesWithGameIframe = 0;
|
|
1046
|
+
let pagesWithVideoIframe = 0;
|
|
1047
|
+
let pagesWithVideoElement = 0;
|
|
791
1048
|
let firstPageIframes = 0;
|
|
792
1049
|
let firstPageCanvas = 0;
|
|
1050
|
+
let firstPageVideoIframes = 0;
|
|
793
1051
|
let totalGameLinks = 0;
|
|
794
1052
|
for (let i = 0; i < pagesSignals.length; i++) {
|
|
795
1053
|
const sig = pagesSignals[i];
|
|
796
1054
|
if (sig.iframeCount > 0) pagesWithIframe++;
|
|
797
1055
|
if (sig.canvasCount > 0) pagesWithCanvas++;
|
|
798
1056
|
if (sig.articleCount > 0) pagesWithArticle++;
|
|
1057
|
+
if (sig.videoElementCount > 0) pagesWithVideoElement++;
|
|
799
1058
|
totalGameLinks += sig.gameLinks || 0;
|
|
800
1059
|
if (i === 0) {
|
|
801
1060
|
firstPageIframes = sig.iframeCount;
|
|
802
1061
|
firstPageCanvas = sig.canvasCount;
|
|
803
1062
|
}
|
|
804
|
-
const hasGameIframe = sig.iframeSrcs.some((
|
|
1063
|
+
const hasGameIframe = sig.iframeSrcs.some((s) => isGameIframe(s));
|
|
1064
|
+
const hasVideoIframe = sig.iframeSrcs.some((s) => isVideoIframe(s));
|
|
805
1065
|
if (hasGameIframe) pagesWithGameIframe++;
|
|
1066
|
+
if (hasVideoIframe) {
|
|
1067
|
+
pagesWithVideoIframe++;
|
|
1068
|
+
if (i === 0) firstPageVideoIframes = sig.iframeSrcs.filter((s) => isVideoIframe(s)).length;
|
|
1069
|
+
}
|
|
806
1070
|
}
|
|
807
1071
|
const avgGameLinks = totalGameLinks / total;
|
|
808
1072
|
const iframeRatio = pagesWithIframe / total;
|
|
809
1073
|
const canvasRatio = pagesWithCanvas / total;
|
|
810
1074
|
const articleRatio = pagesWithArticle / total;
|
|
811
1075
|
const gameIframeRatio = pagesWithGameIframe / total;
|
|
1076
|
+
const videoIframeRatio = pagesWithVideoIframe / total;
|
|
1077
|
+
const videoElementRatio = pagesWithVideoElement / total;
|
|
812
1078
|
const navGameKeywords = GAME_NAV_KEYWORDS.test(navText) || GAME_NAV_KEYWORDS_ZH.test(navText);
|
|
1079
|
+
const navVideoKeywords = VIDEO_NAV_KEYWORDS.test(navText) || VIDEO_NAV_KEYWORDS_ZH.test(navText);
|
|
1080
|
+
let videoScore = 0;
|
|
1081
|
+
if (videoIframeRatio >= 0.3) videoScore += 5;
|
|
1082
|
+
else if (videoIframeRatio >= 0.1) videoScore += 3;
|
|
1083
|
+
if (videoElementRatio >= 0.3) videoScore += 5;
|
|
1084
|
+
else if (videoElementRatio >= 0.1) videoScore += 3;
|
|
1085
|
+
if (navVideoKeywords) videoScore += 3;
|
|
1086
|
+
if (firstPageVideoIframes >= 3) videoScore += 3;
|
|
1087
|
+
else if (firstPageVideoIframes >= 1) videoScore += 1;
|
|
1088
|
+
if (videoScore >= 3) {
|
|
1089
|
+
return {
|
|
1090
|
+
type: "video",
|
|
1091
|
+
confidence: videoScore >= 6 ? "high" : "medium",
|
|
1092
|
+
signals: { iframeRatio, canvasRatio, articleRatio, navGameKeywords }
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
813
1095
|
let gameScore = 0;
|
|
814
1096
|
if (gameIframeRatio >= 0.3) gameScore += 5;
|
|
815
1097
|
else if (gameIframeRatio >= 0.1) gameScore += 3;
|
|
@@ -823,23 +1105,31 @@ function detectSiteType(pagesSignals, navText, manualType) {
|
|
|
823
1105
|
else if (avgGameLinks >= 2) gameScore += 2;
|
|
824
1106
|
else if (totalGameLinks >= 3) gameScore += 1;
|
|
825
1107
|
if (articleRatio >= 0.7 && gameScore < 3) gameScore -= 2;
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1108
|
+
if (gameScore >= 3) {
|
|
1109
|
+
return {
|
|
1110
|
+
type: "game",
|
|
1111
|
+
confidence: gameScore >= 6 ? "high" : "medium",
|
|
1112
|
+
signals: { iframeRatio, canvasRatio, articleRatio, navGameKeywords }
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
const navToolKeywords = TOOL_NAV_KEYWORDS.test(navText) || TOOL_NAV_KEYWORDS_ZH.test(navText);
|
|
1116
|
+
if (navToolKeywords) {
|
|
1117
|
+
return { type: "tool", confidence: "medium", signals: { iframeRatio, canvasRatio, articleRatio, navGameKeywords } };
|
|
1118
|
+
}
|
|
1119
|
+
const navReferenceKeywords = REFERENCE_NAV_KEYWORDS.test(navText) || REFERENCE_NAV_KEYWORDS_ZH.test(navText);
|
|
1120
|
+
let referenceScore = 0;
|
|
1121
|
+
if (articleRatio >= 0.7) referenceScore += 3;
|
|
1122
|
+
else if (articleRatio >= 0.5) referenceScore += 1;
|
|
1123
|
+
if (navReferenceKeywords) referenceScore += 3;
|
|
1124
|
+
if (iframeRatio < 0.1) referenceScore += 1;
|
|
1125
|
+
if (referenceScore >= 3) {
|
|
1126
|
+
return {
|
|
1127
|
+
type: "reference",
|
|
1128
|
+
confidence: referenceScore >= 6 ? "high" : "medium",
|
|
1129
|
+
signals: { iframeRatio, canvasRatio, articleRatio, navGameKeywords }
|
|
1130
|
+
};
|
|
841
1131
|
}
|
|
842
|
-
return { type, confidence, signals: { iframeRatio, canvasRatio, articleRatio, navGameKeywords } };
|
|
1132
|
+
return { type: "content", confidence: "high", signals: { iframeRatio, canvasRatio, articleRatio, navGameKeywords } };
|
|
843
1133
|
}
|
|
844
1134
|
|
|
845
1135
|
// src/scorer.ts
|
|
@@ -870,11 +1160,29 @@ function scorePage(pageType, contentChars, contentRatio, issues, siteType, aiSta
|
|
|
870
1160
|
checks.push({ label: "Content depth", status: contentChars >= 300 ? "pass" : contentChars >= 100 ? "warn" : "fail", weight: 3 });
|
|
871
1161
|
}
|
|
872
1162
|
checks.push({ label: "Content ratio", status: contentRatio >= 30 ? "pass" : contentRatio >= 15 ? "warn" : "fail", weight: 2 });
|
|
1163
|
+
} else if (pageType === "video_detail") {
|
|
1164
|
+
if (siteType === "video") {
|
|
1165
|
+
checks.push({ label: "Video description", status: contentChars >= 50 ? "pass" : "warn", weight: 3 });
|
|
1166
|
+
checks.push({ label: "Content ratio", status: contentRatio >= 15 ? "pass" : contentRatio >= 5 ? "warn" : "fail", weight: 2 });
|
|
1167
|
+
} else {
|
|
1168
|
+
checks.push({ label: "Content depth", status: contentChars >= 300 ? "pass" : contentChars >= 100 ? "warn" : "fail", weight: 3 });
|
|
1169
|
+
checks.push({ label: "Content ratio", status: contentRatio >= 30 ? "pass" : contentRatio >= 15 ? "warn" : "fail", weight: 2 });
|
|
1170
|
+
}
|
|
1171
|
+
} else if (pageType === "reference_detail") {
|
|
1172
|
+
if (siteType === "reference") {
|
|
1173
|
+
checks.push({ label: "Entry completeness", status: contentChars >= 100 ? "pass" : contentChars >= 50 ? "warn" : "fail", weight: 3 });
|
|
1174
|
+
checks.push({ label: "Content ratio", status: contentRatio >= 20 ? "pass" : contentRatio >= 5 ? "warn" : "fail", weight: 2 });
|
|
1175
|
+
} else {
|
|
1176
|
+
checks.push({ label: "Content depth", status: contentChars >= 300 ? "pass" : contentChars >= 100 ? "warn" : "fail", weight: 3 });
|
|
1177
|
+
checks.push({ label: "Content ratio", status: contentRatio >= 30 ? "pass" : contentRatio >= 15 ? "warn" : "fail", weight: 2 });
|
|
1178
|
+
}
|
|
1179
|
+
} else if (pageType === "reference_listing") {
|
|
1180
|
+
checks.push({ label: "Listing content", status: contentChars >= 200 ? "pass" : contentChars >= 50 ? "warn" : "fail", weight: 2 });
|
|
1181
|
+
} else if (pageType === "listing") {
|
|
1182
|
+
checks.push({ label: "Content", status: contentChars >= 200 ? "pass" : contentChars >= 50 ? "warn" : "fail", weight: 2 });
|
|
873
1183
|
} else if (pageType === "required") {
|
|
874
1184
|
checks.push({ label: "Exists", status: contentChars > 0 ? "pass" : "fail", weight: 3 });
|
|
875
1185
|
checks.push({ label: "Content depth", status: contentChars >= 300 ? "pass" : contentChars >= 100 ? "warn" : "fail", weight: 2 });
|
|
876
|
-
} else if (pageType === "listing") {
|
|
877
|
-
checks.push({ label: "Content", status: contentChars >= 200 ? "pass" : contentChars >= 50 ? "warn" : "fail", weight: 2 });
|
|
878
1186
|
} else if (pageType === "utility") {
|
|
879
1187
|
checks.push({ label: "Functional", status: contentChars > 0 ? "pass" : "warn", weight: 1 });
|
|
880
1188
|
} else {
|
|
@@ -889,8 +1197,11 @@ var AI_PAGE_TYPE_WEIGHTS = {
|
|
|
889
1197
|
homepage: 1.5,
|
|
890
1198
|
content: 1,
|
|
891
1199
|
game_detail: 1,
|
|
1200
|
+
video_detail: 1,
|
|
1201
|
+
reference_detail: 1,
|
|
892
1202
|
unknown: 0.5,
|
|
893
1203
|
listing: 0.1,
|
|
1204
|
+
reference_listing: 0.1,
|
|
894
1205
|
required: 0.2,
|
|
895
1206
|
utility: 0.1
|
|
896
1207
|
};
|
|
@@ -1174,12 +1485,90 @@ function checkGameSite(pages, pagesSignals, lang) {
|
|
|
1174
1485
|
}
|
|
1175
1486
|
return items;
|
|
1176
1487
|
}
|
|
1488
|
+
function checkVideoSite(pages, pagesSignals, lang) {
|
|
1489
|
+
const items = [];
|
|
1490
|
+
const subpages = pages.slice(1);
|
|
1491
|
+
const subpageSignals = pagesSignals.slice(1);
|
|
1492
|
+
if (subpages.length > 0) {
|
|
1493
|
+
let thinDesc = 0;
|
|
1494
|
+
const thinPages = [];
|
|
1495
|
+
for (let i = 0; i < subpages.length; i++) {
|
|
1496
|
+
const sig = subpageSignals[i];
|
|
1497
|
+
if (sig && sig.textLength < 50) {
|
|
1498
|
+
thinDesc++;
|
|
1499
|
+
try {
|
|
1500
|
+
thinPages.push(new URL(subpages[i].url).pathname);
|
|
1501
|
+
} catch {
|
|
1502
|
+
thinPages.push(subpages[i].url);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
if (subpages.length > 0) {
|
|
1507
|
+
const ratio = thinDesc / subpages.length;
|
|
1508
|
+
items.push(
|
|
1509
|
+
ratio > 0.5 ? { name: t("item.content.video_desc", lang), status: "warn", message: t("content.video_desc.warn", lang, { thin: thinDesc, total: subpages.length }), detail: thinPages.slice(0, 5).join(", ") } : { name: t("item.content.video_desc", lang), status: "pass", message: t("content.video_desc.pass", lang, { total: subpages.length }) }
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (subpages.length >= 3) {
|
|
1514
|
+
const tpl = detectTemplatePages(subpages);
|
|
1515
|
+
items.push({
|
|
1516
|
+
name: t("item.content.video_variety", lang),
|
|
1517
|
+
status: tpl.isTemplate ? "warn" : "pass",
|
|
1518
|
+
message: t(tpl.isTemplate ? "content.video_variety.warn" : "content.video_variety.pass", lang, { pct: tpl.similarity })
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
return items;
|
|
1522
|
+
}
|
|
1523
|
+
function checkReferenceSite(pages, pagesSignals, lang) {
|
|
1524
|
+
const items = [];
|
|
1525
|
+
const subpages = pages.slice(1);
|
|
1526
|
+
const subpageSignals = pagesSignals.slice(1);
|
|
1527
|
+
if (subpages.length > 0) {
|
|
1528
|
+
let thinEntries = 0;
|
|
1529
|
+
const thinPages = [];
|
|
1530
|
+
for (let i = 0; i < subpages.length; i++) {
|
|
1531
|
+
const sig = subpageSignals[i];
|
|
1532
|
+
if (sig && sig.textLength < 100) {
|
|
1533
|
+
thinEntries++;
|
|
1534
|
+
try {
|
|
1535
|
+
thinPages.push(new URL(subpages[i].url).pathname);
|
|
1536
|
+
} catch {
|
|
1537
|
+
thinPages.push(subpages[i].url);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
if (subpages.length > 0) {
|
|
1542
|
+
const ratio = thinEntries / subpages.length;
|
|
1543
|
+
items.push(
|
|
1544
|
+
ratio > 0.5 ? { name: t("item.content.reference_entry", lang), status: "warn", message: t("content.reference_entry.warn", lang, { thin: thinEntries, total: subpages.length }), detail: thinPages.slice(0, 5).join(", ") } : { name: t("item.content.reference_entry", lang), status: "pass", message: t("content.reference_entry.pass", lang) }
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
if (subpages.length >= 3) {
|
|
1549
|
+
const tpl = detectTemplatePages(subpages);
|
|
1550
|
+
items.push({
|
|
1551
|
+
name: t("item.content.reference_variety", lang),
|
|
1552
|
+
status: tpl.similarity > 70 ? "warn" : "pass",
|
|
1553
|
+
message: t(tpl.similarity > 70 ? "content.reference_variety.warn" : "content.reference_variety.pass", lang, { pct: tpl.similarity })
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
return items;
|
|
1557
|
+
}
|
|
1177
1558
|
function checkContentQuality(pages, sitePageCount, lang, siteType = "content", pagesSignals) {
|
|
1178
1559
|
const items = [];
|
|
1179
1560
|
if (siteType === "game") {
|
|
1180
1561
|
if (pagesSignals) {
|
|
1181
1562
|
items.push(...checkGameSite(pages, pagesSignals, lang));
|
|
1182
1563
|
}
|
|
1564
|
+
} else if (siteType === "video") {
|
|
1565
|
+
if (pagesSignals) {
|
|
1566
|
+
items.push(...checkVideoSite(pages, pagesSignals, lang));
|
|
1567
|
+
}
|
|
1568
|
+
} else if (siteType === "reference") {
|
|
1569
|
+
if (pagesSignals) {
|
|
1570
|
+
items.push(...checkReferenceSite(pages, pagesSignals, lang));
|
|
1571
|
+
}
|
|
1183
1572
|
} else {
|
|
1184
1573
|
items.push(...checkContentSite(pages, lang));
|
|
1185
1574
|
}
|
|
@@ -1322,20 +1711,31 @@ function checkPolicyCompliance(pages, lang) {
|
|
|
1322
1711
|
for (const page of pages) {
|
|
1323
1712
|
for (const p of BLACKLIST) {
|
|
1324
1713
|
const m = page.text.match(p);
|
|
1325
|
-
if (m)
|
|
1714
|
+
if (m) {
|
|
1715
|
+
const hasSubstance = page.text.replace(/\s+/g, "").length > 200;
|
|
1716
|
+
violations.push({ url: page.url, match: m[0], hasSubstance });
|
|
1717
|
+
}
|
|
1326
1718
|
}
|
|
1327
1719
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1720
|
+
const allHaveSubstance = violations.length > 0 && violations.every((v) => v.hasSubstance);
|
|
1721
|
+
const status = violations.length === 0 ? "pass" : allHaveSubstance ? "warn" : "fail";
|
|
1722
|
+
items.push({
|
|
1723
|
+
name: t("item.policy.keywords", lang),
|
|
1724
|
+
status,
|
|
1725
|
+
message: violations.length > 0 ? t("policy.keywords.fail", lang, { count: violations.length }) : t("policy.keywords.pass", lang),
|
|
1726
|
+
detail: violations.length > 0 ? violations.map((v) => `${new URL(v.url).pathname}: "${v.match}"`).join("; ") : void 0
|
|
1727
|
+
});
|
|
1331
1728
|
return { name: t("cat.policy", lang), items };
|
|
1332
1729
|
}
|
|
1333
1730
|
|
|
1334
1731
|
// src/classifier.ts
|
|
1335
|
-
var REQUIRED_PATTERNS = [/\/about/i, /\/privacy/i, /\/contact/i, /\/terms/i, /\/legal/i];
|
|
1336
|
-
var CONTENT_PREFIXES = ["/blog/", "/news/", "/guides/", "/articles/", "/posts/", "/tutorials/"
|
|
1732
|
+
var REQUIRED_PATTERNS = [/\/about/i, /\/privacy/i, /\/contact/i, /\/terms/i, /\/legal/i, /\/editorial-policy/i, /\/imprint/i];
|
|
1733
|
+
var CONTENT_PREFIXES = ["/blog/", "/news/", "/guides/", "/articles/", "/posts/", "/tutorials/"];
|
|
1337
1734
|
var GAME_PREFIXES = ["/games/", "/game/", "/play/", "/online-games/"];
|
|
1338
|
-
var
|
|
1735
|
+
var VIDEO_PREFIXES = ["/videos/", "/video/", "/watch/", "/v/", "/shorts/", "/clip/", "/stream/"];
|
|
1736
|
+
var REFERENCE_PREFIXES = ["/wiki/", "/reference/", "/docs/", "/encyclopedia/", "/glossary/", "/knowledge/", "/archive/", "/database/", "/transcript/"];
|
|
1737
|
+
var REFERENCE_LISTING_PATHS = ["/wiki", "/reference", "/docs", "/encyclopedia", "/glossary", "/knowledge", "/archive", "/database", "/transcript"];
|
|
1738
|
+
var LISTING_PATHS = ["/blog", "/news", "/guides", "/articles", "/games", "/play", "/videos", "/watch", "/channels", "/categories", "/tags", "/archive"];
|
|
1339
1739
|
var UTILITY_PATTERNS = [/\/download/i, /\/search/i, /\/login/i, /\/signup/i, /\/register/i, /\/sitemap/i, /\/404/i];
|
|
1340
1740
|
function classifyPage(url) {
|
|
1341
1741
|
let pathname;
|
|
@@ -1361,6 +1761,19 @@ function classifyPage(url) {
|
|
|
1361
1761
|
if (suffix.length > 1) return "game_detail";
|
|
1362
1762
|
}
|
|
1363
1763
|
}
|
|
1764
|
+
for (const prefix of VIDEO_PREFIXES) {
|
|
1765
|
+
if (normalizedPath.startsWith(prefix.replace(/\/$/, "/"))) {
|
|
1766
|
+
const suffix = normalizedPath.slice(prefix.replace(/\/$/, "").length);
|
|
1767
|
+
if (suffix.length > 1) return "video_detail";
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
for (const prefix of REFERENCE_PREFIXES) {
|
|
1771
|
+
if (normalizedPath.startsWith(prefix.replace(/\/$/, "/"))) {
|
|
1772
|
+
const suffix = normalizedPath.slice(prefix.replace(/\/$/, "").length);
|
|
1773
|
+
if (suffix.length > 1) return "reference_detail";
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
if (REFERENCE_LISTING_PATHS.some((p) => normalizedPath === p || normalizedPath === p.replace(/\/$/, ""))) return "reference_listing";
|
|
1364
1777
|
if (LISTING_PATHS.some((p) => normalizedPath === p || normalizedPath === p.replace(/\/$/, ""))) return "listing";
|
|
1365
1778
|
const langPrefix = normalizedPath.match(/^\/[a-z]{2}(\/|$)/);
|
|
1366
1779
|
if (langPrefix) {
|
|
@@ -1380,6 +1793,20 @@ function classifyPage(url) {
|
|
|
1380
1793
|
return "listing";
|
|
1381
1794
|
}
|
|
1382
1795
|
}
|
|
1796
|
+
for (const prefix of VIDEO_PREFIXES) {
|
|
1797
|
+
if (rest.startsWith(prefix.replace(/\/$/, "/"))) {
|
|
1798
|
+
const suffix = rest.slice(prefix.replace(/\/$/, "").length);
|
|
1799
|
+
if (suffix.length > 1) return "video_detail";
|
|
1800
|
+
return "listing";
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
for (const prefix of REFERENCE_PREFIXES) {
|
|
1804
|
+
if (rest.startsWith(prefix.replace(/\/$/, "/"))) {
|
|
1805
|
+
const suffix = rest.slice(prefix.replace(/\/$/, "").length);
|
|
1806
|
+
if (suffix.length > 1) return "reference_detail";
|
|
1807
|
+
return "reference_listing";
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1383
1810
|
if (REQUIRED_PATTERNS.some((p) => p.test(rest))) return "required";
|
|
1384
1811
|
}
|
|
1385
1812
|
return "unknown";
|
|
@@ -1418,14 +1845,22 @@ function buildPageDetails(pages, aiAnalyses, siteType) {
|
|
|
1418
1845
|
contentStatus = contentStatus === "fail" ? "fail" : "warn";
|
|
1419
1846
|
}
|
|
1420
1847
|
}
|
|
1421
|
-
const pageType = classifyPage(page.url);
|
|
1422
1848
|
const ai = aiMap.get(page.url);
|
|
1423
1849
|
const aiStatus = ai?.status;
|
|
1424
1850
|
const relevance = ai?.relevance;
|
|
1851
|
+
const pageType = ai?.inferredPageType ?? classifyPage(page.url);
|
|
1425
1852
|
const { score } = scorePage(pageType, contentChars, contentRatio, issues, siteType, aiStatus);
|
|
1426
1853
|
const detail = { url: page.url, title: page.title, pageType, totalChars, contentChars, contentRatio, contentStatus, issues, score };
|
|
1427
1854
|
if (relevance) detail.relevance = relevance;
|
|
1428
|
-
if (ai) detail.ai = {
|
|
1855
|
+
if (ai) detail.ai = {
|
|
1856
|
+
status: ai.status,
|
|
1857
|
+
valueScore: ai.valueScore,
|
|
1858
|
+
originalityScore: ai.originalityScore,
|
|
1859
|
+
relevanceScore: ai.relevanceScore,
|
|
1860
|
+
complianceScore: ai.complianceScore,
|
|
1861
|
+
assessment: ai.assessment,
|
|
1862
|
+
suggestions: ai.suggestions
|
|
1863
|
+
};
|
|
1429
1864
|
return detail;
|
|
1430
1865
|
});
|
|
1431
1866
|
}
|
|
@@ -1510,7 +1945,11 @@ async function check(options) {
|
|
|
1510
1945
|
const allSignals = [homeData.signals];
|
|
1511
1946
|
const internalLinks = homeData.links.filter((l) => {
|
|
1512
1947
|
try {
|
|
1513
|
-
|
|
1948
|
+
const u = new URL(l);
|
|
1949
|
+
if (u.origin !== origin) return false;
|
|
1950
|
+
if (!isContentUrl(l)) return false;
|
|
1951
|
+
if (u.pathname === "/" && u.search.length > 0) return false;
|
|
1952
|
+
return true;
|
|
1514
1953
|
} catch {
|
|
1515
1954
|
return false;
|
|
1516
1955
|
}
|
|
@@ -1525,18 +1964,24 @@ async function check(options) {
|
|
|
1525
1964
|
const allInternal = [.../* @__PURE__ */ new Set([...internalLinks, ...sitemapInternal])];
|
|
1526
1965
|
const uniqueLinks = allInternal.slice(0, phase1Limit);
|
|
1527
1966
|
const deadLinks = [];
|
|
1528
|
-
const crawledUrls = /* @__PURE__ */ new Set([url.replace(/\/+$/, "")]);
|
|
1967
|
+
const crawledUrls = /* @__PURE__ */ new Set([homeData.url.replace(/\/+$/, "")]);
|
|
1529
1968
|
async function crawlPage(link) {
|
|
1530
|
-
const norm = link.replace(/\/+$/, "");
|
|
1969
|
+
const norm = link.replace(/\/+$/, "").split("#")[0];
|
|
1531
1970
|
if (crawledUrls.has(norm)) return;
|
|
1532
1971
|
crawledUrls.add(norm);
|
|
1533
1972
|
try {
|
|
1534
1973
|
const pg = await browser.newPage();
|
|
1535
1974
|
const data = await fetchPage(pg, link, timeout);
|
|
1975
|
+
const postNorm = data.url.replace(/\/+$/, "").split("#")[0];
|
|
1976
|
+
if (crawledUrls.has(postNorm) && postNorm !== norm) {
|
|
1977
|
+
await pg.close();
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
crawledUrls.add(postNorm);
|
|
1536
1981
|
if (data.status >= 400) {
|
|
1537
1982
|
deadLinks.push(`${link} (${data.status})`);
|
|
1538
1983
|
} else {
|
|
1539
|
-
pages.push({ url:
|
|
1984
|
+
pages.push({ url: data.url, text: data.text, title: data.title, links: data.links });
|
|
1540
1985
|
allSignals.push(data.signals);
|
|
1541
1986
|
}
|
|
1542
1987
|
await pg.close();
|
|
@@ -1547,14 +1992,14 @@ async function check(options) {
|
|
|
1547
1992
|
progress(`Phase 1: Crawling ${uniqueLinks.length} pages...`);
|
|
1548
1993
|
for (let i = 0; i < uniqueLinks.length; i++) {
|
|
1549
1994
|
const link = uniqueLinks[i];
|
|
1550
|
-
progress(`Phase 1: [${i + 1}/${uniqueLinks.length}] ${new URL(link).pathname}`);
|
|
1995
|
+
progress(`Phase 1: [${i + 1}/${uniqueLinks.length}] ${new URL(link).pathname}${new URL(link).search}`);
|
|
1551
1996
|
await crawlPage(link);
|
|
1552
1997
|
}
|
|
1553
1998
|
const CHILDREN_PER_LISTING = 10;
|
|
1554
1999
|
const MAX_DISCOVERY_DEPTH = 3;
|
|
1555
2000
|
const discoveredContent = /* @__PURE__ */ new Set();
|
|
1556
2001
|
const discoveryQueue = pages.map((p) => ({ url: p.url, links: p.links, depth: 0 }));
|
|
1557
|
-
const seenInDiscovery = new Set([...crawledUrls].map((u) => u.replace(/\/+$/, "")));
|
|
2002
|
+
const seenInDiscovery = new Set([...crawledUrls].map((u) => u.replace(/\/+$/, "").split("#")[0]));
|
|
1558
2003
|
while (discoveryQueue.length > 0) {
|
|
1559
2004
|
const current = discoveryQueue.shift();
|
|
1560
2005
|
if (current.depth > MAX_DISCOVERY_DEPTH) continue;
|
|
@@ -1588,7 +2033,7 @@ async function check(options) {
|
|
|
1588
2033
|
if (toCrawl.length > 0) progress(`Phase 2: Crawling ${toCrawl.length} content pages (from ${discoveredContent.size} discovered)...`);
|
|
1589
2034
|
for (let i = 0; i < toCrawl.length; i++) {
|
|
1590
2035
|
const link = toCrawl[i];
|
|
1591
|
-
progress(`Phase 2: [${i + 1}/${toCrawl.length}] ${new URL(link).pathname}`);
|
|
2036
|
+
progress(`Phase 2: [${i + 1}/${toCrawl.length}] ${new URL(link).pathname}${new URL(link).search}`);
|
|
1592
2037
|
await crawlPage(link);
|
|
1593
2038
|
const crawledPage = pages[pages.length - 1];
|
|
1594
2039
|
if (crawledPage && crawledPage.url === link) {
|
|
@@ -1666,11 +2111,55 @@ async function check(options) {
|
|
|
1666
2111
|
aiItems.push({ name: t("item.ai.suggestions", lang), status: "warn", message: t("ai.suggestion_count", lang, { count: aiResult.suggestions.length }), detail: aiResult.suggestions.join("; ") });
|
|
1667
2112
|
}
|
|
1668
2113
|
allCategories.push({ name: t("group.ai_value", lang), items: aiItems, group: "soft" });
|
|
1669
|
-
|
|
1670
|
-
const suspiciousPages = pageAnalyses.filter((a) => {
|
|
2114
|
+
let suspiciousPages = pageAnalyses.filter((a) => {
|
|
1671
2115
|
const c = a.complianceScore ?? 5;
|
|
1672
2116
|
return c > 2 && c <= 5;
|
|
1673
2117
|
});
|
|
2118
|
+
const shortTextPages = pageAnalyses.filter((a) => {
|
|
2119
|
+
const c = a.complianceScore ?? 5;
|
|
2120
|
+
const text = uniquePages.find((up) => up.url === a.url)?.text ?? "";
|
|
2121
|
+
return c <= 2 && text.replace(/\s+/g, "").length < 200;
|
|
2122
|
+
});
|
|
2123
|
+
const recheckUrls = new Set(suspiciousPages.map((p) => p.url));
|
|
2124
|
+
for (const p of shortTextPages) {
|
|
2125
|
+
if (!recheckUrls.has(p.url)) {
|
|
2126
|
+
suspiciousPages.push(p);
|
|
2127
|
+
recheckUrls.add(p.url);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (suspiciousPages.length > 0) {
|
|
2131
|
+
const apiKeyResolved2 = apiKey || process.env.AI_API_KEY;
|
|
2132
|
+
if (apiKeyResolved2) {
|
|
2133
|
+
const recheckResults = await recheckCompliance(
|
|
2134
|
+
suspiciousPages.map((p) => ({
|
|
2135
|
+
url: p.url,
|
|
2136
|
+
text: uniquePages.find((up) => up.url === p.url)?.text ?? "",
|
|
2137
|
+
firstComplianceScore: p.complianceScore ?? 5
|
|
2138
|
+
})),
|
|
2139
|
+
lang,
|
|
2140
|
+
progress
|
|
2141
|
+
);
|
|
2142
|
+
for (const analysis of pageAnalyses) {
|
|
2143
|
+
const recheck = recheckResults.get(analysis.url);
|
|
2144
|
+
if (recheck) {
|
|
2145
|
+
analysis.complianceScore = recheck.complianceScore;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
for (const analysis of pageAnalyses) {
|
|
2149
|
+
const v = analysis.valueScore ?? 5;
|
|
2150
|
+
const o = analysis.originalityScore ?? 5;
|
|
2151
|
+
const r = analysis.relevanceScore ?? 5;
|
|
2152
|
+
const c = analysis.complianceScore ?? 5;
|
|
2153
|
+
const geoMean = Math.pow(v * o * r * c, 0.25);
|
|
2154
|
+
analysis.status = geoMean >= 7 ? "pass" : geoMean >= 4 ? "warn" : "fail";
|
|
2155
|
+
}
|
|
2156
|
+
suspiciousPages = pageAnalyses.filter((a) => {
|
|
2157
|
+
const c = a.complianceScore ?? 5;
|
|
2158
|
+
return c > 2 && c <= 5;
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
const seriousViolations = pageAnalyses.filter((a) => (a.complianceScore ?? 5) <= 2);
|
|
1674
2163
|
const complianceItems = [];
|
|
1675
2164
|
if (seriousViolations.length > 0) {
|
|
1676
2165
|
complianceItems.push({
|
|
@@ -1693,6 +2182,16 @@ async function check(options) {
|
|
|
1693
2182
|
});
|
|
1694
2183
|
}
|
|
1695
2184
|
allCategories.push({ name: t("group.policy_compliance", lang), items: complianceItems, group: "hard" });
|
|
2185
|
+
const avgCompliance = pageAnalyses.length > 0 ? pageAnalyses.reduce((s, a) => s + (a.complianceScore ?? 5), 0) / pageAnalyses.length : 5;
|
|
2186
|
+
if (avgCompliance >= 7) {
|
|
2187
|
+
const policyCat2 = allCategories.find((c) => c.name === t("cat.policy", lang));
|
|
2188
|
+
if (policyCat2) {
|
|
2189
|
+
const keywordItem = policyCat2.items.find((i) => i.name === t("item.policy.keywords", lang));
|
|
2190
|
+
if (keywordItem && keywordItem.status === "fail") {
|
|
2191
|
+
keywordItem.status = "warn";
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
1696
2195
|
} catch (err) {
|
|
1697
2196
|
allCategories.push({ name: t("group.ai_value", lang), items: [{ name: "AI", status: "skip", message: t("ai.fail", lang, { error: err instanceof Error ? err.message : String(err) }) }], group: "soft" });
|
|
1698
2197
|
}
|
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
getSupportedLangs,
|
|
11
11
|
isValidLang,
|
|
12
12
|
t
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XKNR4LB4.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
16
16
|
import "dotenv/config";
|
|
@@ -63,12 +63,12 @@ function renderTerminalReport(report) {
|
|
|
63
63
|
chalk.gray(` Site type: ${typeLabel}${confidenceLabel}`)
|
|
64
64
|
];
|
|
65
65
|
if (report.siteTopic) {
|
|
66
|
-
lines.push(chalk.gray(`
|
|
66
|
+
lines.push(chalk.gray(` ${t("reporter.topic", lang)}: ${report.siteTopic.topic} \u2014 ${report.siteTopic.description}`));
|
|
67
67
|
}
|
|
68
68
|
if (report.samplingInfo) {
|
|
69
69
|
const s = report.samplingInfo;
|
|
70
70
|
const confColor = s.confidence === "high" ? chalk.green : s.confidence === "medium" ? chalk.yellow : chalk.red;
|
|
71
|
-
lines.push(chalk.gray(`
|
|
71
|
+
lines.push(chalk.gray(` ${t("reporter.pages_label", lang)}: ${s.totalDiscovered} total, ${s.recentCount} recent (6mo), ${s.sampledCount} sampled (${s.samplePct}%) ${confColor(t("reporter.confidence", lang, { confidence: s.confidence }))}`));
|
|
72
72
|
}
|
|
73
73
|
if (report.siteType === "unsupported") {
|
|
74
74
|
lines.push("");
|
|
@@ -110,15 +110,16 @@ function renderTerminalReport(report) {
|
|
|
110
110
|
lines.push(chalk.gray(` \u2502`));
|
|
111
111
|
const hardContrib = Math.round(report.hardStatus === "ready" ? 100 * 0.4 : report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "pass").length / Math.max(1, report.hardCategories.flatMap((c) => c.items).length) * 100 * 0.4);
|
|
112
112
|
const softContrib = Math.round(report.softScore * 0.6);
|
|
113
|
-
lines.push(chalk.gray(` \u2502
|
|
113
|
+
lines.push(chalk.gray(` \u2502 ${t("reporter.formula_label", lang, { hardPct: Math.round(hardContrib), softPct: report.softScore, penalty: report.warningPenalty, total: report.compositeScore })}`));
|
|
114
114
|
if (report.siteAiScore > 0) {
|
|
115
|
-
lines.push(chalk.gray(` \u2502
|
|
115
|
+
lines.push(chalk.gray(` \u2502 ${t("reporter.ai_value_label", lang)}: ${report.siteAiScore}/100 (${t("reporter.ai_value_note", lang)})`));
|
|
116
116
|
}
|
|
117
117
|
if (report.aiDimensionAverages) {
|
|
118
118
|
const d = report.aiDimensionAverages;
|
|
119
119
|
const dimColor = (v) => v >= 8 ? chalk.green : v >= 5 ? chalk.yellow : chalk.red;
|
|
120
|
+
const dimLabel = (key, v) => `${t(`reporter.dim_${key}`, lang)} ${v}`;
|
|
120
121
|
lines.push(
|
|
121
|
-
chalk.gray(` \u2502
|
|
122
|
+
chalk.gray(` \u2502 ${t("reporter.ai_dimensions", lang)}: `) + `${dimColor(d.value)(dimLabel("value", d.value))} ${dimColor(d.originality)(dimLabel("originality", d.originality))} ${dimColor(d.relevance)(dimLabel("relevance", d.relevance))} ${dimColor(d.compliance)(dimLabel("compliance", d.compliance))} ` + chalk.gray(`(${t("reporter.avg_per_10", lang)})`)
|
|
122
123
|
);
|
|
123
124
|
}
|
|
124
125
|
lines.push(chalk.gray(` \u2514\u2500`));
|
|
@@ -172,6 +173,9 @@ var PAGE_TYPE_ICONS = {
|
|
|
172
173
|
homepage: chalk.cyan("*"),
|
|
173
174
|
content: chalk.green("A"),
|
|
174
175
|
game_detail: chalk.blue("G"),
|
|
176
|
+
video_detail: chalk.cyan("V"),
|
|
177
|
+
reference_detail: chalk.magenta("R"),
|
|
178
|
+
reference_listing: chalk.magenta("r"),
|
|
175
179
|
required: chalk.yellow("!"),
|
|
176
180
|
listing: chalk.gray("L"),
|
|
177
181
|
utility: chalk.gray("#"),
|
|
@@ -215,28 +219,28 @@ function renderMarkdownReport(report) {
|
|
|
215
219
|
const lang = report.lang;
|
|
216
220
|
const typeKey = `detector.type.${report.siteType}`;
|
|
217
221
|
const typeLabel = t(typeKey, lang);
|
|
218
|
-
lines.push(`#
|
|
222
|
+
lines.push(`# ${t("md.report_title", lang)}`);
|
|
219
223
|
lines.push("");
|
|
220
|
-
lines.push(`|
|
|
224
|
+
lines.push(`| ${t("md.table.project", lang)} | ${t("md.table.value", lang)} |`);
|
|
221
225
|
lines.push(`|------|-----|`);
|
|
222
|
-
lines.push(`|
|
|
223
|
-
lines.push(`|
|
|
224
|
-
lines.push(`|
|
|
226
|
+
lines.push(`| ${t("md.table.url", lang)} | ${report.url} |`);
|
|
227
|
+
lines.push(`| ${t("md.table.time", lang)} | ${report.timestamp} |`);
|
|
228
|
+
lines.push(`| ${t("md.table.site_type", lang)} | ${typeLabel} (${report.siteTypeConfidence}) |`);
|
|
225
229
|
if (report.siteTopic) {
|
|
226
|
-
lines.push(`|
|
|
227
|
-
lines.push(`|
|
|
230
|
+
lines.push(`| ${t("md.table.topic", lang)} | ${report.siteTopic.topic} |`);
|
|
231
|
+
lines.push(`| ${t("md.table.description", lang)} | ${report.siteTopic.description} |`);
|
|
228
232
|
}
|
|
229
233
|
if (report.samplingInfo) {
|
|
230
234
|
const s = report.samplingInfo;
|
|
231
|
-
lines.push(`|
|
|
235
|
+
lines.push(`| ${t("md.table.sampling", lang)} | ${s.totalDiscovered} ${t("md.table.total", lang)}, ${s.recentCount} ${t("md.table.recent", lang)}, ${s.sampledCount} ${t("md.table.sampled", lang)} (${s.samplePct}%, ${s.confidence} ${t("md.table.confidence", lang)}) |`);
|
|
232
236
|
}
|
|
233
237
|
lines.push("");
|
|
234
|
-
lines.push(`##
|
|
238
|
+
lines.push(`## ${t("md.composite_score_title", lang)}: ${report.compositeScore}/100`);
|
|
235
239
|
lines.push("");
|
|
236
240
|
const hardFailCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "fail").length;
|
|
237
241
|
const hardWarnCount = report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "warn").length;
|
|
238
242
|
const hardLabel = report.hardStatus === "ready" ? "\u2705 PASS" : report.hardStatus === "warn" ? "\u26A0\uFE0F WARN" : "\u274C FAIL";
|
|
239
|
-
lines.push(`###
|
|
243
|
+
lines.push(`### ${t("md.hard_requirements", lang)} ${hardLabel}`);
|
|
240
244
|
lines.push("");
|
|
241
245
|
for (const cat of report.hardCategories) {
|
|
242
246
|
for (const item of cat.items) {
|
|
@@ -245,7 +249,7 @@ function renderMarkdownReport(report) {
|
|
|
245
249
|
}
|
|
246
250
|
}
|
|
247
251
|
lines.push("");
|
|
248
|
-
lines.push(`###
|
|
252
|
+
lines.push(`### ${t("md.soft_scoring", lang)} ${report.softScore}/100`);
|
|
249
253
|
lines.push("");
|
|
250
254
|
for (const cat of report.softCategories) {
|
|
251
255
|
const isAiCat = cat.name.includes("AI") || cat.name.includes("ai");
|
|
@@ -259,71 +263,89 @@ function renderMarkdownReport(report) {
|
|
|
259
263
|
lines.push("");
|
|
260
264
|
if (report.aiDimensionAverages) {
|
|
261
265
|
const d = report.aiDimensionAverages;
|
|
262
|
-
lines.push(`###
|
|
266
|
+
lines.push(`### ${t("md.ai_value_title", lang)}`);
|
|
263
267
|
lines.push("");
|
|
264
|
-
lines.push(`|
|
|
268
|
+
lines.push(`| ${t("md.table.dimension", lang)} | ${t("md.table.avg_score", lang)} |`);
|
|
265
269
|
lines.push(`|------|------|`);
|
|
266
|
-
lines.push(`|
|
|
267
|
-
lines.push(`|
|
|
268
|
-
lines.push(`|
|
|
269
|
-
lines.push(`|
|
|
270
|
+
lines.push(`| ${t("md.dim_value", lang)} | ${d.value}/10 |`);
|
|
271
|
+
lines.push(`| ${t("md.dim_originality", lang)} | ${d.originality}/10 |`);
|
|
272
|
+
lines.push(`| ${t("md.dim_relevance", lang)} | ${d.relevance}/10 |`);
|
|
273
|
+
lines.push(`| ${t("md.dim_compliance", lang)} | ${d.compliance}/10 |`);
|
|
270
274
|
lines.push("");
|
|
271
|
-
lines.push(
|
|
275
|
+
lines.push(`**${t("md.site_ai_score", lang)}**: ${report.siteAiScore}/100\uFF08${t("md.geometric_weighted", lang)}\uFF09`);
|
|
272
276
|
lines.push("");
|
|
273
277
|
}
|
|
274
278
|
const hardContrib = Math.round(report.hardStatus === "ready" ? 100 * 0.4 : report.hardCategories.flatMap((c) => c.items).filter((i) => i.status === "pass").length / Math.max(1, report.hardCategories.flatMap((c) => c.items).length) * 100 * 0.4);
|
|
275
279
|
lines.push(`> Hard ${Math.round(hardContrib)}% \xD7 0.4 + Soft ${report.softScore}% \xD7 0.6 - Penalty ${report.warningPenalty} = ${report.compositeScore}`);
|
|
276
280
|
lines.push("");
|
|
277
281
|
if (report.pages.length > 0) {
|
|
278
|
-
lines.push(`###
|
|
282
|
+
lines.push(`### ${t("md.page_details", lang)} (${t("md.pages_count", lang, { count: report.pages.length })})`);
|
|
279
283
|
lines.push("");
|
|
280
284
|
const problems = report.pages.filter((p) => p.contentStatus !== "pass" || p.issues.length > 0 || p.ai && p.ai.status !== "pass");
|
|
281
285
|
const ok = report.pages.filter((p) => p.contentStatus === "pass" && p.issues.length === 0 && (!p.ai || p.ai.status === "pass"));
|
|
282
|
-
lines.push(`|
|
|
283
|
-
lines.push(
|
|
286
|
+
lines.push(`| ${t("md.table.status", lang)} | ${t("md.table.type", lang)} | ${t("md.table.path", lang)} | ${t("md.table.score", lang)} | ${t("md.table.content_ratio", lang)} | V | O | R | C | ${t("md.table.ai_composite", lang)} | ${t("md.table.title", lang)} |`);
|
|
287
|
+
lines.push(`|------|------|------|------|--------|---|---|---|---|--------|------|`);
|
|
284
288
|
for (const p of [...problems, ...ok]) {
|
|
285
289
|
const path = (() => {
|
|
286
290
|
try {
|
|
287
|
-
|
|
291
|
+
const u = new URL(p.url);
|
|
292
|
+
return u.pathname + u.search;
|
|
288
293
|
} catch {
|
|
289
294
|
return p.url;
|
|
290
295
|
}
|
|
291
296
|
})();
|
|
292
297
|
const status = MD_ICONS[p.contentStatus];
|
|
293
|
-
const
|
|
294
|
-
|
|
298
|
+
const ai = p.ai;
|
|
299
|
+
const v = ai?.valueScore != null ? ai.valueScore : "-";
|
|
300
|
+
const o = ai?.originalityScore != null ? ai.originalityScore : "-";
|
|
301
|
+
const r = ai?.relevanceScore != null ? ai.relevanceScore : "-";
|
|
302
|
+
const c = ai?.complianceScore != null ? ai.complianceScore : "-";
|
|
303
|
+
const aiComposite = ai?.valueScore != null && ai?.originalityScore != null && ai?.relevanceScore != null && ai?.complianceScore != null ? Math.round(Math.pow(ai.valueScore * ai.originalityScore * ai.relevanceScore * ai.complianceScore, 0.25) * 10) : "-";
|
|
304
|
+
lines.push(`| ${status} | ${p.pageType} | [\`${path}\`](${p.url}) | ${p.score}/100 | ${p.contentRatio}% | ${v} | ${o} | ${r} | ${c} | ${aiComposite} | ${p.title} |`);
|
|
295
305
|
}
|
|
296
306
|
lines.push("");
|
|
297
307
|
const detailPages = problems.filter((p) => p.issues.length > 0 || p.ai && p.ai.status !== "pass");
|
|
298
308
|
if (detailPages.length > 0) {
|
|
299
|
-
lines.push(`####
|
|
309
|
+
lines.push(`#### ${t("md.problem_pages", lang)}`);
|
|
300
310
|
lines.push("");
|
|
301
311
|
for (const p of detailPages) {
|
|
302
312
|
const path = (() => {
|
|
303
313
|
try {
|
|
304
|
-
|
|
314
|
+
const u = new URL(p.url);
|
|
315
|
+
return u.pathname + u.search;
|
|
305
316
|
} catch {
|
|
306
317
|
return p.url;
|
|
307
318
|
}
|
|
308
319
|
})();
|
|
309
|
-
lines.push(
|
|
320
|
+
lines.push(`**[${path}](${p.url})** (${t("reporter.mechanical_label", lang)}: ${p.score}/100)`);
|
|
321
|
+
lines.push("");
|
|
310
322
|
for (const issue of p.issues) lines.push(`- \u26A0\uFE0F ${issue}`);
|
|
311
323
|
if (p.ai) {
|
|
312
|
-
|
|
313
|
-
|
|
324
|
+
const ai = p.ai;
|
|
325
|
+
lines.push(`- ${t("md.ai_status", lang)}: ${MD_ICONS[ai.status]} ${ai.status}`);
|
|
326
|
+
if (ai.valueScore != null) {
|
|
327
|
+
lines.push(`- ${t("md.four_dimensions", lang)}: **${t("md.dim_value", lang)} ${ai.valueScore}** | **${t("md.dim_originality", lang)} ${ai.originalityScore}** | **${t("md.dim_relevance", lang)} ${ai.relevanceScore}** | **${t("md.dim_compliance", lang)} ${ai.complianceScore}**`);
|
|
328
|
+
const geoMean = Math.round(Math.pow((ai.valueScore ?? 5) * (ai.originalityScore ?? 5) * (ai.relevanceScore ?? 5) * (ai.complianceScore ?? 5), 0.25) * 10);
|
|
329
|
+
lines.push(`- ${t("md.ai_composite_score", lang)}: ${geoMean}/100\uFF08${t("md.geometric_mean", lang)}\uFF09`);
|
|
330
|
+
}
|
|
331
|
+
lines.push(`- ${t("md.assessment", lang)}: ${ai.assessment}`);
|
|
332
|
+
if (ai.suggestions.length > 0) {
|
|
333
|
+
lines.push(`- ${t("md.suggestions", lang)}:`);
|
|
334
|
+
for (const s of ai.suggestions.slice(0, 3)) lines.push(` - ${s}`);
|
|
335
|
+
}
|
|
314
336
|
}
|
|
315
337
|
lines.push("");
|
|
316
338
|
}
|
|
317
339
|
}
|
|
318
340
|
}
|
|
319
341
|
if (report.hardStatus === "fail") {
|
|
320
|
-
lines.push(
|
|
342
|
+
lines.push(t("md.summary.not_ready", lang, { count: hardFailCount }));
|
|
321
343
|
} else if (report.hardStatus === "warn") {
|
|
322
|
-
lines.push(
|
|
344
|
+
lines.push(t("md.summary.needs_fixes", lang, { count: hardWarnCount }));
|
|
323
345
|
} else if (report.warned > 0) {
|
|
324
|
-
lines.push(
|
|
346
|
+
lines.push(t("md.summary.mostly_ready", lang, { count: report.warned }));
|
|
325
347
|
} else {
|
|
326
|
-
lines.push(
|
|
348
|
+
lines.push(t("md.summary.ready", lang));
|
|
327
349
|
}
|
|
328
350
|
lines.push("");
|
|
329
351
|
return lines.join("\n");
|
|
@@ -343,7 +365,7 @@ function getDomain(url) {
|
|
|
343
365
|
}
|
|
344
366
|
}
|
|
345
367
|
var program = new Command();
|
|
346
|
-
program.name("adsense-check").description("Check if a website meets Google AdSense review requirements").version("1.0.0").argument("<url>", "Website URL to check").option("-j, --json", "Output JSON to stdout").option("-n, --max-crawl <number>", "Total page crawl limit (Phase 1 + 2)", "50").option("-m, --page-limit <number>", "Max structural pages to crawl (Phase 1)", "50").option("-c, --content-limit <number>", "Max content pages to crawl (Phase 2)", "20").option("--sample-min <number>", "Min content pages to sample", "20").option("--sample-ratio <ratio>", "Content page sampling ratio (0-1)", "0.2").option("--ai", "Enable AI content quality analysis", false).option("-t, --timeout <ms>", "Page load timeout", "30000").option("--api-key <key>", "AI API key").option("-o, --output <dir>", "Report output directory", "tmp").option("--no-save", "Skip auto-saving report").option("-l, --lang <lang>", `Output language (${getSupportedLangs().join("|")})`, "en").option("--type <type>", "Force site type (content|tool|game), skip auto-detection").option("--detect-only", "Only detect site type/topic, skip full check").option("--page <url>", "Analyze a single page value (AI four-dimension scoring)").action(async (url, opts) => {
|
|
368
|
+
program.name("adsense-check").description("Check if a website meets Google AdSense review requirements").version("1.0.0").argument("<url>", "Website URL to check").option("-j, --json", "Output JSON to stdout").option("-n, --max-crawl <number>", "Total page crawl limit (Phase 1 + 2)", "50").option("-m, --page-limit <number>", "Max structural pages to crawl (Phase 1)", "50").option("-c, --content-limit <number>", "Max content pages to crawl (Phase 2)", "20").option("--sample-min <number>", "Min content pages to sample", "20").option("--sample-ratio <ratio>", "Content page sampling ratio (0-1)", "0.2").option("--ai", "Enable AI content quality analysis", false).option("-t, --timeout <ms>", "Page load timeout", "30000").option("--api-key <key>", "AI API key").option("-o, --output <dir>", "Report output directory", "tmp").option("--no-save", "Skip auto-saving report").option("-l, --lang <lang>", `Output language (${getSupportedLangs().join("|")})`, "en").option("--type <type>", "Force site type (content|tool|game|video|reference), skip auto-detection").option("--detect-only", "Only detect site type/topic, skip full check").option("--page <url>", "Analyze a single page value (AI four-dimension scoring)").action(async (url, opts) => {
|
|
347
369
|
try {
|
|
348
370
|
new URL(url);
|
|
349
371
|
} catch {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';
|
|
2
2
|
type Lang = string;
|
|
3
|
-
type SiteType = 'content' | 'tool' | 'game' | 'unsupported';
|
|
4
|
-
type PageType = 'homepage' | 'content' | 'game_detail' | 'required' | 'listing' | 'utility' | 'unknown';
|
|
3
|
+
type SiteType = 'content' | 'tool' | 'game' | 'video' | 'reference' | 'unsupported';
|
|
4
|
+
type PageType = 'homepage' | 'content' | 'game_detail' | 'video_detail' | 'reference_detail' | 'reference_listing' | 'required' | 'listing' | 'utility' | 'unknown';
|
|
5
5
|
interface CheckItem {
|
|
6
6
|
name: string;
|
|
7
7
|
status: CheckStatus;
|
|
@@ -27,6 +27,10 @@ interface PageDetail {
|
|
|
27
27
|
relevance?: 'relevant' | 'tangential' | 'off-topic';
|
|
28
28
|
ai?: {
|
|
29
29
|
status: CheckStatus;
|
|
30
|
+
valueScore?: number;
|
|
31
|
+
originalityScore?: number;
|
|
32
|
+
relevanceScore?: number;
|
|
33
|
+
complianceScore?: number;
|
|
30
34
|
assessment: string;
|
|
31
35
|
suggestions: string[];
|
|
32
36
|
};
|
package/dist/index.js
CHANGED