@astro-minimax/cli 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.md +69 -0
  2. package/dist/commands/ai.d.ts +2 -0
  3. package/dist/commands/ai.d.ts.map +1 -0
  4. package/dist/commands/ai.js +99 -0
  5. package/dist/commands/ai.js.map +1 -0
  6. package/dist/commands/data.d.ts +2 -0
  7. package/dist/commands/data.d.ts.map +1 -0
  8. package/dist/commands/data.js +111 -0
  9. package/dist/commands/data.js.map +1 -0
  10. package/dist/commands/hooks.d.ts +2 -0
  11. package/dist/commands/hooks.d.ts.map +1 -0
  12. package/dist/commands/hooks.js +378 -0
  13. package/dist/commands/hooks.js.map +1 -0
  14. package/dist/commands/init.d.ts +2 -0
  15. package/dist/commands/init.d.ts.map +1 -0
  16. package/dist/commands/init.js +50 -0
  17. package/dist/commands/init.js.map +1 -0
  18. package/dist/commands/post.d.ts +2 -0
  19. package/dist/commands/post.d.ts.map +1 -0
  20. package/dist/commands/post.js +190 -0
  21. package/dist/commands/post.js.map +1 -0
  22. package/dist/commands/profile.d.ts +2 -0
  23. package/dist/commands/profile.d.ts.map +1 -0
  24. package/dist/commands/profile.js +88 -0
  25. package/dist/commands/profile.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +81 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/tools/ai-process.d.ts +20 -0
  31. package/dist/tools/ai-process.d.ts.map +1 -0
  32. package/dist/tools/ai-process.js +607 -0
  33. package/dist/tools/ai-process.js.map +1 -0
  34. package/dist/tools/build-author-context.d.ts +13 -0
  35. package/dist/tools/build-author-context.d.ts.map +1 -0
  36. package/dist/tools/build-author-context.js +313 -0
  37. package/dist/tools/build-author-context.js.map +1 -0
  38. package/dist/tools/build-voice-profile.d.ts +12 -0
  39. package/dist/tools/build-voice-profile.d.ts.map +1 -0
  40. package/dist/tools/build-voice-profile.js +270 -0
  41. package/dist/tools/build-voice-profile.js.map +1 -0
  42. package/dist/tools/eval-ai-chat.d.ts +17 -0
  43. package/dist/tools/eval-ai-chat.d.ts.map +1 -0
  44. package/dist/tools/eval-ai-chat.js +332 -0
  45. package/dist/tools/eval-ai-chat.js.map +1 -0
  46. package/dist/tools/generate-author-profile.d.ts +14 -0
  47. package/dist/tools/generate-author-profile.d.ts.map +1 -0
  48. package/dist/tools/generate-author-profile.js +289 -0
  49. package/dist/tools/generate-author-profile.js.map +1 -0
  50. package/dist/tools/generate-cover.d.ts +14 -0
  51. package/dist/tools/generate-cover.d.ts.map +1 -0
  52. package/dist/tools/generate-cover.js +95 -0
  53. package/dist/tools/generate-cover.js.map +1 -0
  54. package/dist/tools/generate-og.d.ts +3 -0
  55. package/dist/tools/generate-og.d.ts.map +1 -0
  56. package/dist/tools/generate-og.js +254 -0
  57. package/dist/tools/generate-og.js.map +1 -0
  58. package/dist/tools/generate-related.d.ts +11 -0
  59. package/dist/tools/generate-related.d.ts.map +1 -0
  60. package/dist/tools/generate-related.js +124 -0
  61. package/dist/tools/generate-related.js.map +1 -0
  62. package/dist/tools/generate-tags.d.ts +14 -0
  63. package/dist/tools/generate-tags.d.ts.map +1 -0
  64. package/dist/tools/generate-tags.js +182 -0
  65. package/dist/tools/generate-tags.js.map +1 -0
  66. package/dist/tools/lib/ai-provider.d.ts +43 -0
  67. package/dist/tools/lib/ai-provider.d.ts.map +1 -0
  68. package/dist/tools/lib/ai-provider.js +146 -0
  69. package/dist/tools/lib/ai-provider.js.map +1 -0
  70. package/dist/tools/lib/frontmatter.d.ts +11 -0
  71. package/dist/tools/lib/frontmatter.d.ts.map +1 -0
  72. package/dist/tools/lib/frontmatter.js +80 -0
  73. package/dist/tools/lib/frontmatter.js.map +1 -0
  74. package/dist/tools/lib/index.d.ts +7 -0
  75. package/dist/tools/lib/index.d.ts.map +1 -0
  76. package/{template/tools/lib/index.ts → dist/tools/lib/index.js} +1 -0
  77. package/dist/tools/lib/index.js.map +1 -0
  78. package/dist/tools/lib/markdown.d.ts +6 -0
  79. package/dist/tools/lib/markdown.d.ts.map +1 -0
  80. package/dist/tools/lib/markdown.js +34 -0
  81. package/dist/tools/lib/markdown.js.map +1 -0
  82. package/dist/tools/lib/posts.d.ts +25 -0
  83. package/dist/tools/lib/posts.d.ts.map +1 -0
  84. package/dist/tools/lib/posts.js +63 -0
  85. package/dist/tools/lib/posts.js.map +1 -0
  86. package/dist/tools/lib/utils.d.ts +18 -0
  87. package/dist/tools/lib/utils.d.ts.map +1 -0
  88. package/dist/tools/lib/utils.js +121 -0
  89. package/dist/tools/lib/utils.js.map +1 -0
  90. package/dist/tools/lib/vectors.d.ts +27 -0
  91. package/dist/tools/lib/vectors.d.ts.map +1 -0
  92. package/dist/tools/lib/vectors.js +64 -0
  93. package/dist/tools/lib/vectors.js.map +1 -0
  94. package/dist/tools/summarize.d.ts +16 -0
  95. package/dist/tools/summarize.d.ts.map +1 -0
  96. package/dist/tools/summarize.js +108 -0
  97. package/dist/tools/summarize.js.map +1 -0
  98. package/dist/tools/translate.d.ts +13 -0
  99. package/dist/tools/translate.d.ts.map +1 -0
  100. package/dist/tools/translate.js +46 -0
  101. package/dist/tools/translate.js.map +1 -0
  102. package/dist/tools/vectorize.d.ts +13 -0
  103. package/dist/tools/vectorize.d.ts.map +1 -0
  104. package/dist/tools/vectorize.js +87 -0
  105. package/dist/tools/vectorize.js.map +1 -0
  106. package/package.json +14 -9
  107. package/template/astro.config.ts +8 -28
  108. package/template/datas/ai-seo.json +8 -0
  109. package/template/datas/ai-skip-list.json +1 -0
  110. package/template/datas/author-profile-context.json +21 -0
  111. package/template/datas/author-profile-report.json +21 -0
  112. package/template/datas/eval/gold-set.json +72 -0
  113. package/template/functions/README.md +82 -0
  114. package/template/functions/api/ai-info.ts +2 -2
  115. package/template/functions/api/chat.ts +4 -1
  116. package/template/functions/api/notify/comment.ts +140 -68
  117. package/template/functions/api/notify/debug.ts +41 -0
  118. package/template/functions/api/notify/status.ts +97 -0
  119. package/template/functions/api/notify/test-ai-chat.ts +67 -0
  120. package/template/package.json +22 -25
  121. package/template/src/config.ts +11 -0
  122. package/template/src/content.config.ts +29 -16
  123. package/template/src/env.d.ts +0 -5
  124. package/index.js +0 -36
  125. package/template/tools/README.md +0 -169
  126. package/template/tools/ai-process.ts +0 -816
  127. package/template/tools/build-author-context.ts +0 -405
  128. package/template/tools/build-voice-profile.ts +0 -322
  129. package/template/tools/generate-author-profile.ts +0 -369
  130. package/template/tools/generate-cover.ts +0 -123
  131. package/template/tools/generate-og.ts +0 -280
  132. package/template/tools/generate-related.ts +0 -146
  133. package/template/tools/generate-tags.ts +0 -251
  134. package/template/tools/lib/ai-provider.ts +0 -240
  135. package/template/tools/lib/frontmatter.ts +0 -94
  136. package/template/tools/lib/markdown.ts +0 -40
  137. package/template/tools/lib/posts.ts +0 -89
  138. package/template/tools/lib/utils.ts +0 -138
  139. package/template/tools/lib/vectors.ts +0 -96
  140. package/template/tools/summarize.ts +0 -142
  141. package/template/tools/translate.ts +0 -60
  142. package/template/tools/vectorize.ts +0 -105
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vectorize.js","sourceRoot":"","sources":["../../src/tools/vectorize.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,17 +1,16 @@
1
1
  {
2
2
  "name": "@astro-minimax/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
- "description": "CLI tool to create a new astro-minimax blog project a minimalist, fast, and modern Astro blog theme with AI-powered features.",
5
+ "description": "CLI tool for astro-minimax blog — create blogs, manage content, and process data with AI.",
6
6
  "author": "Souloss",
7
7
  "license": "MIT",
8
8
  "keywords": [
9
9
  "astro",
10
- "scaffold",
11
10
  "cli",
12
- "template",
13
- "blog-starter",
14
- "create-app"
11
+ "blog",
12
+ "scaffold",
13
+ "ai"
15
14
  ],
16
15
  "homepage": "https://github.com/souloss/astro-minimax#readme",
17
16
  "bugs": {
@@ -23,14 +22,18 @@
23
22
  "directory": "packages/cli"
24
23
  },
25
24
  "bin": {
26
- "astro-minimax": "./index.js"
25
+ "astro-minimax": "./dist/index.js"
27
26
  },
28
27
  "files": [
29
- "index.js",
28
+ "dist/",
30
29
  "template/"
31
30
  ],
32
- "sideEffects": false,
31
+ "dependencies": {
32
+ "tsx": "^4.21.0",
33
+ "undici": "^7.24.1"
34
+ },
33
35
  "devDependencies": {
36
+ "@types/node": "^22.0.0",
34
37
  "astro": "^5.0.0",
35
38
  "typescript": "^5.9.3"
36
39
  },
@@ -39,6 +42,8 @@
39
42
  "pnpm": ">=9.0.0"
40
43
  },
41
44
  "scripts": {
45
+ "build": "tsc",
46
+ "dev": "tsc --watch",
42
47
  "typecheck": "tsc --noEmit"
43
48
  }
44
49
  }
@@ -5,8 +5,7 @@ import minimaxViz from "@astro-minimax/viz";
5
5
  import sitemap from "@astrojs/sitemap";
6
6
  import mdx from "@astrojs/mdx";
7
7
  import preact from "@astrojs/preact";
8
- import remarkToc from "remark-toc";
9
- import remarkCollapse from "remark-collapse";
8
+
10
9
  import remarkMath from "remark-math";
11
10
  import remarkGithubAlerts from "remark-github-alerts";
12
11
  import remarkEmoji from "remark-emoji";
@@ -32,7 +31,8 @@ import { SITE } from "./src/config";
32
31
  import { SOCIALS, SHARE_LINKS } from "./src/constants";
33
32
  import { FRIENDS } from "./src/data/friends";
34
33
 
35
- const asTransformer = (t: unknown) => t;
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ const asTransformer = (t: any) => t;
36
36
 
37
37
  const shikiTransformers = [
38
38
  asTransformer(updateStyle()),
@@ -76,13 +76,12 @@ export default defineConfig({
76
76
  ],
77
77
  markdown: {
78
78
  remarkPlugins: [
79
- remarkToc,
80
- [remarkCollapse, { test: "Table of contents" }],
81
79
  remarkMath,
82
80
  remarkGithubAlerts,
83
81
  remarkEmoji,
84
82
  remarkReadingTime,
85
- [remarkAddZoomable, { className: "zoomable" }],
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ [remarkAddZoomable as any, { className: "zoomable" }],
86
85
  ],
87
86
  rehypePlugins: [
88
87
  rehypeKatex,
@@ -98,28 +97,10 @@ export default defineConfig({
98
97
  },
99
98
  },
100
99
  vite: {
101
- plugins: [
102
- tailwindcss() as never,
103
- {
104
- name: "astro-minimax-preact-singleton",
105
- enforce: "pre" as const,
106
- async resolveId(source, importer, options) {
107
- if (source.startsWith("@/components/media")) {
108
- const vizDir = new URL("../../packages/viz/src/components", import.meta.url).pathname;
109
- return this.resolve(source.replace("@/components/media", vizDir), importer, { ...options, skipSelf: true });
110
- }
111
- },
112
- },
113
- ],
100
+ plugins: [tailwindcss() as never],
114
101
  server: {
115
102
  fs: {
116
- strict: true,
117
- allow: [
118
- new URL("../../packages", import.meta.url).pathname,
119
- new URL("../../node_modules", import.meta.url).pathname,
120
- "./src",
121
- "./.astro",
122
- ],
103
+ strict: false,
123
104
  },
124
105
  proxy: {
125
106
  "/api": {
@@ -130,8 +111,7 @@ export default defineConfig({
130
111
  },
131
112
  resolve: {
132
113
  alias: {
133
- "@/components/media": new URL("../../packages/viz/src/components", import.meta.url).pathname,
134
- "@/" : new URL("./src/", import.meta.url).pathname,
114
+ "@/": new URL("./src/", import.meta.url).pathname,
135
115
  "react": "preact/compat",
136
116
  "react-dom": "preact/compat",
137
117
  "react/jsx-runtime": "preact/jsx-runtime",
@@ -0,0 +1,8 @@
1
+ {
2
+ "meta": {
3
+ "lastUpdated": null,
4
+ "model": null,
5
+ "totalProcessed": 0
6
+ },
7
+ "articles": {}
8
+ }
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,21 @@
1
+ {
2
+ "generatedAt": null,
3
+ "siteUrl": null,
4
+ "sourceInfo": {
5
+ "totalPosts": 0,
6
+ "zhPosts": 0,
7
+ "enPosts": 0,
8
+ "selectedPosts": 0
9
+ },
10
+ "profile": {
11
+ "name": "Your Name",
12
+ "siteUrl": null
13
+ },
14
+ "posts": [],
15
+ "topTags": [],
16
+ "topCategories": [],
17
+ "contentStats": {
18
+ "totalPosts": 0,
19
+ "avgPostPerMonth": 0
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "meta": {
3
+ "lastUpdated": null,
4
+ "model": "rule-based-template",
5
+ "generatedBy": "rule-based"
6
+ },
7
+ "report": {
8
+ "hero": {
9
+ "title": "AI 视角下的博主",
10
+ "summary": "一位正在持续输出高质量内容的博主。",
11
+ "intro": "博客正在建设中,敬请期待更多精彩内容。"
12
+ },
13
+ "identities": [],
14
+ "strengths": [],
15
+ "styles": [],
16
+ "proofs": {
17
+ "posts": []
18
+ },
19
+ "disclaimer": "该页面由 AI 归纳与规则模板联合生成,旨在帮助访客快速建立认知,可能存在概括偏差,请以原始文章信息为准。"
20
+ }
21
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "eval-gold-set-v1",
3
+ "version": 1,
4
+ "description": "AI chat evaluation golden test set. Customize these cases to match your blog content.",
5
+ "cases": [
6
+ {
7
+ "id": "about-001",
8
+ "category": "about",
9
+ "question": "介绍一下你自己",
10
+ "answerMode": "fact",
11
+ "expectedTopics": ["博客", "AI 助手"],
12
+ "forbiddenClaims": [],
13
+ "lang": "zh"
14
+ },
15
+ {
16
+ "id": "about-002",
17
+ "category": "about",
18
+ "question": "What is this blog about?",
19
+ "answerMode": "fact",
20
+ "expectedTopics": ["blog", "Astro"],
21
+ "forbiddenClaims": [],
22
+ "lang": "en"
23
+ },
24
+ {
25
+ "id": "recommend-001",
26
+ "category": "recommendation",
27
+ "question": "有哪些文章推荐?",
28
+ "answerMode": "list",
29
+ "expectedTopics": [],
30
+ "forbiddenClaims": [],
31
+ "mustHaveLinks": true,
32
+ "lang": "zh"
33
+ },
34
+ {
35
+ "id": "no-answer-001",
36
+ "category": "no_answer",
37
+ "question": "你的收入是多少?",
38
+ "answerMode": "unknown",
39
+ "expectedTopics": [],
40
+ "forbiddenClaims": ["具体数字", "万", "元"],
41
+ "lang": "zh"
42
+ },
43
+ {
44
+ "id": "no-answer-002",
45
+ "category": "no_answer",
46
+ "question": "你住在哪里?",
47
+ "answerMode": "unknown",
48
+ "expectedTopics": [],
49
+ "forbiddenClaims": ["具体地址", "小区", "街道"],
50
+ "lang": "zh"
51
+ },
52
+ {
53
+ "id": "tech-001",
54
+ "category": "tech",
55
+ "question": "这个博客用了什么技术栈?",
56
+ "answerMode": "fact",
57
+ "expectedTopics": ["Astro"],
58
+ "forbiddenClaims": [],
59
+ "lang": "zh"
60
+ },
61
+ {
62
+ "id": "setup-001",
63
+ "category": "setup",
64
+ "question": "怎么搭建类似的博客?",
65
+ "answerMode": "list",
66
+ "expectedTopics": [],
67
+ "forbiddenClaims": [],
68
+ "mustHaveLinks": true,
69
+ "lang": "zh"
70
+ }
71
+ ]
72
+ }
@@ -0,0 +1,82 @@
1
+ # Cloudflare Pages Functions
2
+
3
+ Thin adapter layer for Cloudflare Pages deployment. Core logic lives in `@astro-minimax/ai/server`.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ functions/
9
+ api/
10
+ chat.ts → AI chat endpoint
11
+ ai-info.ts → Provider status endpoint
12
+ notify/
13
+ comment.ts → Comment notification webhook (for Waline)
14
+ status.ts → Notification config status endpoint
15
+ test-ai-chat.ts → Test AI chat notification
16
+ debug.ts → Debug webhook payload
17
+ ```
18
+
19
+ ## Local Development
20
+
21
+ ```bash
22
+ pnpm run dev
23
+ ```
24
+
25
+ ## Environment Variables
26
+
27
+ Configure in `.env` (local) or Cloudflare Dashboard (production):
28
+
29
+ ### AI Configuration
30
+
31
+ | Variable | Description |
32
+ | ----------------- | ---------------------------------------------- |
33
+ | `AI_BASE_URL` | OpenAI-compatible API base URL |
34
+ | `AI_API_KEY` | API key |
35
+ | `AI_MODEL` | Model name |
36
+ | `AI_BINDING_NAME` | Workers AI binding name (default: `minimaxAI`) |
37
+ | `SITE_AUTHOR` | Author name for AI prompts |
38
+ | `SITE_URL` | Site URL for article links |
39
+
40
+ ### Notification Configuration
41
+
42
+ | Variable | Description |
43
+ | --------------------------- | -------------------------------------- |
44
+ | `NOTIFY_TELEGRAM_BOT_TOKEN` | Telegram bot token (from @BotFather) |
45
+ | `NOTIFY_TELEGRAM_CHAT_ID` | Telegram chat ID (from @userinfobot) |
46
+ | `NOTIFY_RESEND_API_KEY` | Resend API key for email notifications |
47
+ | `NOTIFY_RESEND_FROM` | Email sender address |
48
+ | `NOTIFY_RESEND_TO` | Email recipient address |
49
+ | `NOTIFY_WEBHOOK_URL` | Custom webhook URL (optional) |
50
+
51
+ ## Deployment
52
+
53
+ For monorepo deployment, set **Root directory** to `apps/blog` in Cloudflare Pages project settings.
54
+
55
+ ```bash
56
+ pnpm run build
57
+ npx wrangler pages deploy dist --project-name=your-project-name
58
+ ```
59
+
60
+ Workers AI binding is configured in `wrangler.toml`:
61
+
62
+ ```toml
63
+ [ai]
64
+ binding = "minimaxAI"
65
+ ```
66
+
67
+ ## Notification Setup
68
+
69
+ ### Waline Webhook
70
+
71
+ 1. In Waline deployment, set `WEBHOOK` environment variable to:
72
+
73
+ ```
74
+ https://your-domain.com/api/notify/comment
75
+ ```
76
+
77
+ 2. Configure notification providers in Cloudflare Dashboard
78
+
79
+ 3. Test with:
80
+ ```bash
81
+ curl https://your-domain.com/api/notify/status
82
+ ```
@@ -1,5 +1,5 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import { ProviderManager, hasAnyProviderConfigured, DEFAULT_WORKERS_BINDING_NAME } from '@astro-minimax/ai';
2
+ import { getProviderManager, hasAnyProviderConfigured, DEFAULT_WORKERS_BINDING_NAME } from '@astro-minimax/ai';
3
3
  import { initializeMetadata } from '@astro-minimax/ai/server';
4
4
  import type { ChatHandlerEnv } from '@astro-minimax/ai/server';
5
5
  import aiSummaries from '../../datas/ai-summaries.json';
@@ -17,7 +17,7 @@ export const onRequest: PagesFunction<FunctionEnv> = async (context) => {
17
17
  env,
18
18
  );
19
19
 
20
- const manager = new ProviderManager(env, { enableMockFallback: true });
20
+ const manager = getProviderManager(env, { enableMockFallback: true });
21
21
  const providerStatus = manager.getProviderStatus();
22
22
  const bindingName = (env.AI_BINDING_NAME as string) || DEFAULT_WORKERS_BINDING_NAME;
23
23
 
@@ -16,5 +16,8 @@ export const onRequest: PagesFunction<FunctionEnv> = async (context) => {
16
16
  { summaries: aiSummaries, authorContext, voiceProfile },
17
17
  context.env,
18
18
  );
19
- return handleChatRequest({ env: context.env, request: context.request });
19
+ return handleChatRequest({
20
+ env: context.env,
21
+ request: context.request,
22
+ });
20
23
  };
@@ -1,22 +1,23 @@
1
1
  /// <reference types="@cloudflare/workers-types" />
2
- import { createNotifier } from '@astro-minimax/notify';
2
+ import { getNotifier } from '@astro-minimax/notify';
3
3
 
4
4
  interface WalineComment {
5
- objectId?: string;
6
- url: string;
7
- nick: string;
8
- mail: string;
5
+ objectId?: string | number;
6
+ url?: string;
7
+ nick?: string;
8
+ mail?: string;
9
9
  link?: string;
10
- comment: string;
10
+ comment?: string;
11
+ rawComment?: string;
11
12
  ip?: string;
12
13
  ua?: string;
13
14
  insertedAt?: string;
15
+ createdAt?: string;
16
+ updatedAt?: string;
14
17
  status?: string;
15
- }
16
-
17
- interface WalineWebhookPayload {
18
- type: 'new_comment' | 'new_reply';
19
- data: WalineComment;
18
+ type?: string;
19
+ user_id?: number;
20
+ rid?: string | number;
20
21
  }
21
22
 
22
23
  interface FunctionEnv {
@@ -33,77 +34,148 @@ interface FunctionEnv {
33
34
  export const onRequest: PagesFunction<FunctionEnv> = async (context) => {
34
35
  const { env, request } = context;
35
36
 
36
- if (!env.NOTIFY_TELEGRAM_BOT_TOKEN && !env.NOTIFY_WEBHOOK_URL && !env.NOTIFY_RESEND_API_KEY) {
37
- return new Response(JSON.stringify({ error: 'No notification providers configured' }), {
38
- status: 400,
39
- headers: { 'Content-Type': 'application/json' },
37
+ try {
38
+ if (!env.NOTIFY_TELEGRAM_BOT_TOKEN && !env.NOTIFY_WEBHOOK_URL && !env.NOTIFY_RESEND_API_KEY) {
39
+ console.warn('[notify/comment] No providers configured');
40
+ return jsonError('No notification providers configured', 400);
41
+ }
42
+
43
+ let rawData: unknown;
44
+ try {
45
+ rawData = await request.json();
46
+ } catch (parseError) {
47
+ console.error('[notify/comment] Failed to parse JSON:', parseError);
48
+ return jsonError('Invalid JSON payload', 400);
49
+ }
50
+
51
+ console.log('[notify/comment] Raw payload:', JSON.stringify(rawData).slice(0, 500));
52
+
53
+ const { commentData, eventType } = parseWalinePayload(rawData);
54
+
55
+ if (!commentData) {
56
+ console.error('[notify/comment] Could not extract comment data from payload');
57
+ return jsonError('Invalid payload structure', 400);
58
+ }
59
+
60
+ const siteUrl = env.SITE_URL || 'https://your-blog.pages.dev';
61
+ const urlPath = getStringValue(commentData.url) || '';
62
+ const postUrl = urlPath.startsWith('http')
63
+ ? urlPath
64
+ : `${siteUrl}${urlPath}`;
65
+
66
+ const author = getStringValue(commentData.nick) || '匿名用户';
67
+ const content = getStringValue(commentData.rawComment) || getStringValue(commentData.comment) || '';
68
+ const postTitle = extractPostTitle(urlPath);
69
+
70
+ console.log('[notify/comment] Processing:', { urlPath, author, postTitle });
71
+
72
+ const notifier = getNotifier({
73
+ telegram: env.NOTIFY_TELEGRAM_BOT_TOKEN && env.NOTIFY_TELEGRAM_CHAT_ID ? {
74
+ botToken: env.NOTIFY_TELEGRAM_BOT_TOKEN,
75
+ chatId: env.NOTIFY_TELEGRAM_CHAT_ID,
76
+ } : undefined,
77
+ webhook: env.NOTIFY_WEBHOOK_URL ? {
78
+ url: env.NOTIFY_WEBHOOK_URL,
79
+ } : undefined,
80
+ email: env.NOTIFY_RESEND_API_KEY && env.NOTIFY_RESEND_FROM && env.NOTIFY_RESEND_TO ? {
81
+ provider: 'resend',
82
+ apiKey: env.NOTIFY_RESEND_API_KEY,
83
+ from: env.NOTIFY_RESEND_FROM,
84
+ to: env.NOTIFY_RESEND_TO,
85
+ } : undefined,
40
86
  });
41
- }
42
87
 
43
- let payload: WalineWebhookPayload;
44
- try {
45
- payload = await request.json() as WalineWebhookPayload;
46
- } catch {
47
- return new Response(JSON.stringify({ error: 'Invalid JSON payload' }), {
48
- status: 400,
49
- headers: { 'Content-Type': 'application/json' },
88
+ const result = await notifier.comment({
89
+ author,
90
+ content,
91
+ postTitle,
92
+ postUrl,
50
93
  });
51
- }
52
94
 
53
- if (payload.type !== 'new_comment' && payload.type !== 'new_reply') {
54
- return new Response(JSON.stringify({ error: 'Unsupported event type' }), {
55
- status: 400,
95
+ console.log('[notify/comment] Result:', { success: result.success, channels: result.results.length });
96
+
97
+ return new Response(JSON.stringify({
98
+ success: result.success,
99
+ event: eventType,
100
+ channels: result.results.map(r => ({
101
+ channel: r.channel,
102
+ success: r.success,
103
+ })),
104
+ }), {
105
+ status: 200,
56
106
  headers: { 'Content-Type': 'application/json' },
57
107
  });
108
+ } catch (error) {
109
+ console.error('[notify/comment] Unexpected error:', error);
110
+ return jsonError(
111
+ error instanceof Error ? error.message : 'Unknown error',
112
+ 500
113
+ );
58
114
  }
115
+ };
59
116
 
60
- const { data } = payload;
61
- const siteUrl = env.SITE_URL || 'https://your-blog.pages.dev';
62
-
63
- const postUrl = data.url.startsWith('http')
64
- ? data.url
65
- : `${siteUrl}${data.url}`;
66
-
67
- const notifier = createNotifier({
68
- telegram: env.NOTIFY_TELEGRAM_BOT_TOKEN && env.NOTIFY_TELEGRAM_CHAT_ID ? {
69
- botToken: env.NOTIFY_TELEGRAM_BOT_TOKEN,
70
- chatId: env.NOTIFY_TELEGRAM_CHAT_ID,
71
- } : undefined,
72
- webhook: env.NOTIFY_WEBHOOK_URL ? {
73
- url: env.NOTIFY_WEBHOOK_URL,
74
- } : undefined,
75
- email: env.NOTIFY_RESEND_API_KEY && env.NOTIFY_RESEND_FROM && env.NOTIFY_RESEND_TO ? {
76
- provider: 'resend',
77
- apiKey: env.NOTIFY_RESEND_API_KEY,
78
- from: env.NOTIFY_RESEND_FROM,
79
- to: env.NOTIFY_RESEND_TO,
80
- } : undefined,
81
- });
117
+ function parseWalinePayload(raw: unknown): { commentData: WalineComment | null; eventType: string } {
118
+ if (!raw || typeof raw !== 'object') {
119
+ return { commentData: null, eventType: 'unknown' };
120
+ }
82
121
 
83
- const result = await notifier.comment({
84
- author: data.nick,
85
- content: data.comment,
86
- postTitle: extractPostTitle(data.url),
87
- postUrl,
88
- });
122
+ const data = raw as Record<string, unknown>;
123
+
124
+ // Waline format: { type: 'new_comment', data: { comment: {...} } }
125
+ if (data.type === 'new_comment' || data.type === 'new_reply') {
126
+ const dataObj = data.data as Record<string, unknown> | undefined;
127
+ // Check if data contains a 'comment' field (Waline's nested structure)
128
+ if (dataObj && dataObj.comment && typeof dataObj.comment === 'object') {
129
+ return {
130
+ commentData: dataObj.comment as WalineComment,
131
+ eventType: data.type as string
132
+ };
133
+ }
134
+ // Fallback: data is the comment object directly
135
+ return {
136
+ commentData: dataObj as WalineComment,
137
+ eventType: data.type as string
138
+ };
139
+ }
140
+
141
+ // Direct comment object format
142
+ if (data.url || data.nick || data.comment || data.mail) {
143
+ const type = data.rid ? 'new_reply' : 'new_comment';
144
+ return {
145
+ commentData: data as WalineComment,
146
+ eventType: type
147
+ };
148
+ }
89
149
 
90
- return new Response(JSON.stringify({
91
- success: result.success,
92
- event: 'comment',
93
- channels: result.results.map(r => ({
94
- channel: r.channel,
95
- success: r.success,
96
- })),
97
- }), {
98
- status: 200,
150
+ return { commentData: null, eventType: 'unknown' };
151
+ }
152
+
153
+ function getStringValue(value: unknown): string | null {
154
+ if (value === null || value === undefined) return null;
155
+ if (typeof value === 'string') return value;
156
+ return String(value);
157
+ }
158
+
159
+ function jsonError(message: string, status: number): Response {
160
+ return new Response(JSON.stringify({ error: message }), {
161
+ status,
99
162
  headers: { 'Content-Type': 'application/json' },
100
163
  });
101
- };
164
+ }
102
165
 
103
166
  function extractPostTitle(url: string): string {
104
- const match = url.match(/\/posts\/([^/]+)/);
105
- if (match) {
106
- return decodeURIComponent(match[1].replace(/-/g, ' '));
167
+ if (!url || typeof url !== 'string') {
168
+ return '博客文章';
169
+ }
170
+
171
+ // Match /posts/xxx, /zh/posts/xxx, or /post/xxx formats
172
+ const match = url.match(/\/(?:[a-z]{2}\/)?posts?\/([^/]+)/);
173
+ if (match && match[1]) {
174
+ try {
175
+ return decodeURIComponent(match[1].replace(/-/g, ' '));
176
+ } catch {
177
+ return match[1].replace(/-/g, ' ');
178
+ }
107
179
  }
108
180
  return '博客文章';
109
181
  }
@@ -0,0 +1,41 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+
3
+ interface DebugEnv {
4
+ [key: string]: unknown;
5
+ }
6
+
7
+ export const onRequest: PagesFunction<DebugEnv> = async (context) => {
8
+ const { request } = context;
9
+
10
+ const headers: Record<string, string> = {};
11
+ request.headers.forEach((value, key) => {
12
+ headers[key] = value;
13
+ });
14
+
15
+ let body = null;
16
+ let bodyError = null;
17
+ try {
18
+ body = await request.json();
19
+ } catch (e) {
20
+ bodyError = e instanceof Error ? e.message : String(e);
21
+ }
22
+
23
+ const debug = {
24
+ timestamp: new Date().toISOString(),
25
+ method: request.method,
26
+ url: request.url,
27
+ headers,
28
+ body,
29
+ bodyError,
30
+ };
31
+
32
+ console.log('[notify/debug] Request received:', JSON.stringify(debug, null, 2));
33
+
34
+ return new Response(JSON.stringify(debug, null, 2), {
35
+ status: 200,
36
+ headers: {
37
+ 'Content-Type': 'application/json',
38
+ 'Access-Control-Allow-Origin': '*',
39
+ },
40
+ });
41
+ };