@astro-minimax/notify 0.1.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 +252 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/notify.d.ts +3 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +130 -0
- package/dist/providers/email.d.ts +6 -0
- package/dist/providers/email.d.ts.map +1 -0
- package/dist/providers/email.js +84 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +3 -0
- package/dist/providers/telegram.d.ts +6 -0
- package/dist/providers/telegram.d.ts.map +1 -0
- package/dist/providers/telegram.js +73 -0
- package/dist/providers/webhook.d.ts +6 -0
- package/dist/providers/webhook.d.ts.map +1 -0
- package/dist/providers/webhook.js +53 -0
- package/dist/templates/ai-chat.d.ts +5 -0
- package/dist/templates/ai-chat.d.ts.map +1 -0
- package/dist/templates/ai-chat.js +212 -0
- package/dist/templates/comment.d.ts +5 -0
- package/dist/templates/comment.d.ts.map +1 -0
- package/dist/templates/comment.js +83 -0
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +14 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# @astro-minimax/notify
|
|
2
|
+
|
|
3
|
+
多渠道通知包,为 astro-minimax 博客系统提供灵活的通知功能。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **多渠道支持**:Telegram、Email (Resend)、Webhook
|
|
8
|
+
- **多事件类型**:评论通知、AI 对话监控
|
|
9
|
+
- **丰富信息展示**:Token 用量、阶段耗时、引用文章等
|
|
10
|
+
- **隐私保护**:Session ID 自动匿名化
|
|
11
|
+
- **代理支持**:支持 HTTP/HTTPS 代理
|
|
12
|
+
- **容错设计**:失败不影响主业务流程
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @astro-minimax/notify
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 环境变量配置
|
|
21
|
+
|
|
22
|
+
### Telegram Provider
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
NOTIFY_TELEGRAM_BOT_TOKEN=your-bot-token
|
|
26
|
+
NOTIFY_TELEGRAM_CHAT_ID=your-chat-id
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
获取方式:
|
|
30
|
+
|
|
31
|
+
1. 在 Telegram 搜索 `@BotFather`,发送 `/newbot` 创建 Bot,获取 Token
|
|
32
|
+
2. 在 Telegram 搜索 `@userinfobot`,发送 `/start` 获取你的 Chat ID
|
|
33
|
+
|
|
34
|
+
### Email Provider (Resend)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
NOTIFY_RESEND_API_KEY=re_xxx
|
|
38
|
+
NOTIFY_RESEND_FROM=noreply@yourdomain.com
|
|
39
|
+
NOTIFY_RESEND_TO=you@example.com
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
获取方式:
|
|
43
|
+
|
|
44
|
+
1. 注册 [Resend](https://resend.com) 账号
|
|
45
|
+
2. 在 Dashboard 创建 API Key
|
|
46
|
+
3. 验证发件域名
|
|
47
|
+
|
|
48
|
+
### Webhook Provider
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
NOTIFY_WEBHOOK_URL=https://your-webhook.com/notify
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 使用方式
|
|
55
|
+
|
|
56
|
+
### 基础用法
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { createNotifier } from '@astro-minimax/notify';
|
|
60
|
+
|
|
61
|
+
const notifier = createNotifier({
|
|
62
|
+
telegram: {
|
|
63
|
+
botToken: process.env.NOTIFY_TELEGRAM_BOT_TOKEN,
|
|
64
|
+
chatId: process.env.NOTIFY_TELEGRAM_CHAT_ID,
|
|
65
|
+
},
|
|
66
|
+
email: {
|
|
67
|
+
provider: 'resend',
|
|
68
|
+
apiKey: process.env.NOTIFY_RESEND_API_KEY,
|
|
69
|
+
from: process.env.NOTIFY_RESEND_FROM,
|
|
70
|
+
to: process.env.NOTIFY_RESEND_TO,
|
|
71
|
+
},
|
|
72
|
+
webhook: {
|
|
73
|
+
url: process.env.NOTIFY_WEBHOOK_URL,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 发送评论通知
|
|
78
|
+
await notifier.comment({
|
|
79
|
+
author: '张三',
|
|
80
|
+
content: '文章写得真好!',
|
|
81
|
+
postTitle: '如何使用 Astro',
|
|
82
|
+
postUrl: 'https://example.com/posts/how-to-use-astro',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 发送 AI 对话通知
|
|
86
|
+
await notifier.aiChat({
|
|
87
|
+
sessionId: 'abc123',
|
|
88
|
+
roundNumber: 1,
|
|
89
|
+
userMessage: '你好',
|
|
90
|
+
aiResponse: '你好!有什么可以帮助你的?',
|
|
91
|
+
model: {
|
|
92
|
+
name: 'gpt-4',
|
|
93
|
+
provider: 'openai',
|
|
94
|
+
},
|
|
95
|
+
usage: {
|
|
96
|
+
total: 100,
|
|
97
|
+
input: 50,
|
|
98
|
+
output: 50,
|
|
99
|
+
},
|
|
100
|
+
timing: {
|
|
101
|
+
total: 1500,
|
|
102
|
+
generation: 1400,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 自定义模板
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const notifier = createNotifier({
|
|
111
|
+
telegram: { botToken, chatId },
|
|
112
|
+
templates: {
|
|
113
|
+
comment: {
|
|
114
|
+
telegram: (event) => ({
|
|
115
|
+
text: `📬 新评论\n\n${event.author}: ${event.content}`,
|
|
116
|
+
parse_mode: 'HTML',
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API
|
|
124
|
+
|
|
125
|
+
### `createNotifier(config: NotifyConfig): Notifier`
|
|
126
|
+
|
|
127
|
+
创建通知器实例。
|
|
128
|
+
|
|
129
|
+
#### NotifyConfig
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
interface NotifyConfig {
|
|
133
|
+
telegram?: {
|
|
134
|
+
botToken: string;
|
|
135
|
+
chatId: string;
|
|
136
|
+
};
|
|
137
|
+
webhook?: {
|
|
138
|
+
url: string;
|
|
139
|
+
method?: 'POST';
|
|
140
|
+
headers?: Record<string, string>;
|
|
141
|
+
};
|
|
142
|
+
email?: {
|
|
143
|
+
provider: 'resend';
|
|
144
|
+
apiKey: string;
|
|
145
|
+
from: string;
|
|
146
|
+
to: string;
|
|
147
|
+
};
|
|
148
|
+
templates?: Partial<EventTemplates>;
|
|
149
|
+
logger?: Logger;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Notifier
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
interface Notifier {
|
|
157
|
+
comment(event: Omit<CommentEvent, 'type'>): Promise<NotifyResult>;
|
|
158
|
+
aiChat(event: Omit<AiChatEvent, 'type'>): Promise<NotifyResult>;
|
|
159
|
+
send(event: NotifyEvent): Promise<NotifyResult>;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 事件类型
|
|
164
|
+
|
|
165
|
+
#### CommentEvent
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
interface CommentEvent {
|
|
169
|
+
type: 'comment';
|
|
170
|
+
author: string;
|
|
171
|
+
content: string;
|
|
172
|
+
postTitle: string;
|
|
173
|
+
postUrl: string;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### AiChatEvent
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
interface AiChatEvent {
|
|
181
|
+
type: 'ai-chat';
|
|
182
|
+
sessionId: string;
|
|
183
|
+
roundNumber: number;
|
|
184
|
+
userMessage: string;
|
|
185
|
+
aiResponse?: string;
|
|
186
|
+
referencedArticles?: Array<{ title: string; url?: string }>;
|
|
187
|
+
model?: {
|
|
188
|
+
name: string;
|
|
189
|
+
provider?: string;
|
|
190
|
+
apiHost?: string;
|
|
191
|
+
};
|
|
192
|
+
usage?: {
|
|
193
|
+
total: number;
|
|
194
|
+
input: number;
|
|
195
|
+
output: number;
|
|
196
|
+
};
|
|
197
|
+
timing?: {
|
|
198
|
+
total: number;
|
|
199
|
+
keywordExtraction?: number;
|
|
200
|
+
search?: number;
|
|
201
|
+
generation?: number;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## 通知效果
|
|
207
|
+
|
|
208
|
+
### 评论通知
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
💬 新评论
|
|
212
|
+
|
|
213
|
+
📖 文章:如何使用 Astro
|
|
214
|
+
👤 评论者:张三
|
|
215
|
+
|
|
216
|
+
「文章写得真好!」
|
|
217
|
+
|
|
218
|
+
🔗 查看评论
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### AI 对话通知
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
🗣 博客 AI 对话
|
|
225
|
+
|
|
226
|
+
👤 a1b2***f6 · 🕐 03-17 12:30 · 第 3 轮
|
|
227
|
+
|
|
228
|
+
❓ 读者:
|
|
229
|
+
「你好,测试 AI 对话通知功能」
|
|
230
|
+
|
|
231
|
+
💬 AI:
|
|
232
|
+
你好!AI 对话通知功能测试成功...
|
|
233
|
+
|
|
234
|
+
📎 引用文章:
|
|
235
|
+
· 京都马拉松
|
|
236
|
+
· 东京旅行
|
|
237
|
+
|
|
238
|
+
⚙️ 模型配置:
|
|
239
|
+
· API Host: api.openai.com
|
|
240
|
+
· 主对话模型: gpt-4
|
|
241
|
+
|
|
242
|
+
🧮 Token 用量:
|
|
243
|
+
· 本次请求合计: 总 1,089 / 入 500 / 出 589
|
|
244
|
+
|
|
245
|
+
⏱️ 阶段耗时:
|
|
246
|
+
· 总耗时: 15.50s
|
|
247
|
+
· 文本生成: 15.49s
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createNotifier } from './notify.js';
|
|
2
|
+
export type { NotifyConfig, NotifyEvent, CommentEvent, AiChatEvent, ArticleRef, ModelInfo, TokenUsage, PhaseTiming, NotifyResult, SendResult, Notifier, EventTemplates, Logger, TelegramConfig, WebhookConfig, EmailConfig, TelegramTemplate, WebhookPayload, EmailTemplate, Channel, EventType, } from './types.js';
|
|
3
|
+
export { defaultTemplates } from './templates/index.js';
|
|
4
|
+
export { createTelegramProvider, createWebhookProvider, createEmailProvider, } from './providers/index.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,cAAc,EACd,MAAM,EACN,cAAc,EACd,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,OAAO,EACP,SAAS,GACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
package/dist/notify.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAMZ,QAAQ,EAIT,MAAM,YAAY,CAAC;AAkDpB,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CA4H7D"}
|
package/dist/notify.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createTelegramProvider, createWebhookProvider, createEmailProvider, } from './providers/index.js';
|
|
2
|
+
import { defaultTemplates } from './templates/index.js';
|
|
3
|
+
class DefaultLogger {
|
|
4
|
+
info(message, data) {
|
|
5
|
+
console.log(`[notify] ${message}`, data ?? '');
|
|
6
|
+
}
|
|
7
|
+
error(message, error, data) {
|
|
8
|
+
console.error(`[notify] ${message}`, error?.message ?? '', data ?? '');
|
|
9
|
+
}
|
|
10
|
+
warn(message, data) {
|
|
11
|
+
console.warn(`[notify] ${message}`, data ?? '');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function mergeTemplates(custom) {
|
|
15
|
+
if (!custom)
|
|
16
|
+
return defaultTemplates;
|
|
17
|
+
return {
|
|
18
|
+
comment: {
|
|
19
|
+
telegram: custom.comment?.telegram ?? defaultTemplates.comment.telegram,
|
|
20
|
+
webhook: custom.comment?.webhook ?? defaultTemplates.comment.webhook,
|
|
21
|
+
email: custom.comment?.email ?? defaultTemplates.comment.email,
|
|
22
|
+
},
|
|
23
|
+
'ai-chat': {
|
|
24
|
+
telegram: custom['ai-chat']?.telegram ?? defaultTemplates['ai-chat'].telegram,
|
|
25
|
+
webhook: custom['ai-chat']?.webhook ?? defaultTemplates['ai-chat'].webhook,
|
|
26
|
+
email: custom['ai-chat']?.email ?? defaultTemplates['ai-chat'].email,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function createNotifier(config) {
|
|
31
|
+
const logger = config.logger ?? new DefaultLogger();
|
|
32
|
+
const templates = mergeTemplates(config.templates);
|
|
33
|
+
const providers = {};
|
|
34
|
+
if (config.telegram) {
|
|
35
|
+
providers.telegram = createTelegramProvider(config.telegram, logger);
|
|
36
|
+
}
|
|
37
|
+
if (config.webhook) {
|
|
38
|
+
providers.webhook = createWebhookProvider(config.webhook, logger);
|
|
39
|
+
}
|
|
40
|
+
if (config.email) {
|
|
41
|
+
providers.email = createEmailProvider(config.email, logger);
|
|
42
|
+
}
|
|
43
|
+
const hasProviders = Object.keys(providers).length > 0;
|
|
44
|
+
if (!hasProviders) {
|
|
45
|
+
logger.warn('No notification providers configured');
|
|
46
|
+
}
|
|
47
|
+
async function sendToAll(event) {
|
|
48
|
+
if (!hasProviders) {
|
|
49
|
+
return {
|
|
50
|
+
event: event.type,
|
|
51
|
+
results: [],
|
|
52
|
+
success: false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const results = [];
|
|
56
|
+
const eventType = event.type;
|
|
57
|
+
const eventTemplates = templates[eventType];
|
|
58
|
+
const sendPromises = [];
|
|
59
|
+
if (providers.telegram) {
|
|
60
|
+
sendPromises.push(providers.telegram.send(eventTemplates.telegram(event))
|
|
61
|
+
.then(result => {
|
|
62
|
+
logger.info('Telegram notification result', { success: result.success, duration: result.duration });
|
|
63
|
+
return { channel: 'telegram', result };
|
|
64
|
+
})
|
|
65
|
+
.catch(error => ({
|
|
66
|
+
channel: 'telegram',
|
|
67
|
+
result: { channel: 'telegram', success: false, error: error?.message ?? 'Unknown error' },
|
|
68
|
+
})));
|
|
69
|
+
}
|
|
70
|
+
if (providers.webhook) {
|
|
71
|
+
sendPromises.push(providers.webhook.send(eventTemplates.webhook(event))
|
|
72
|
+
.then(result => {
|
|
73
|
+
logger.info('Webhook notification result', { success: result.success, duration: result.duration });
|
|
74
|
+
return { channel: 'webhook', result };
|
|
75
|
+
})
|
|
76
|
+
.catch(error => ({
|
|
77
|
+
channel: 'webhook',
|
|
78
|
+
result: { channel: 'webhook', success: false, error: error?.message ?? 'Unknown error' },
|
|
79
|
+
})));
|
|
80
|
+
}
|
|
81
|
+
if (providers.email) {
|
|
82
|
+
sendPromises.push(providers.email.send(eventTemplates.email(event))
|
|
83
|
+
.then(result => {
|
|
84
|
+
logger.info('Email notification result', { success: result.success, duration: result.duration });
|
|
85
|
+
return { channel: 'email', result };
|
|
86
|
+
})
|
|
87
|
+
.catch(error => ({
|
|
88
|
+
channel: 'email',
|
|
89
|
+
result: { channel: 'email', success: false, error: error?.message ?? 'Unknown error' },
|
|
90
|
+
})));
|
|
91
|
+
}
|
|
92
|
+
const settledResults = await Promise.allSettled(sendPromises);
|
|
93
|
+
for (const settled of settledResults) {
|
|
94
|
+
if (settled.status === 'fulfilled') {
|
|
95
|
+
results.push(settled.value.result);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const success = results.some(r => r.success);
|
|
99
|
+
return {
|
|
100
|
+
event: eventType,
|
|
101
|
+
results,
|
|
102
|
+
success,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
async comment(event) {
|
|
107
|
+
const fullEvent = {
|
|
108
|
+
...event,
|
|
109
|
+
type: 'comment',
|
|
110
|
+
timestamp: new Date(),
|
|
111
|
+
};
|
|
112
|
+
return sendToAll(fullEvent);
|
|
113
|
+
},
|
|
114
|
+
async aiChat(event) {
|
|
115
|
+
const fullEvent = {
|
|
116
|
+
...event,
|
|
117
|
+
type: 'ai-chat',
|
|
118
|
+
timestamp: new Date(),
|
|
119
|
+
};
|
|
120
|
+
return sendToAll(fullEvent);
|
|
121
|
+
},
|
|
122
|
+
async send(event) {
|
|
123
|
+
const fullEvent = {
|
|
124
|
+
...event,
|
|
125
|
+
timestamp: event.timestamp ?? new Date(),
|
|
126
|
+
};
|
|
127
|
+
return sendToAll(fullEvent);
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EmailConfig, EmailTemplate, SendResult, Logger } from '../types.js';
|
|
2
|
+
export interface EmailProvider {
|
|
3
|
+
send(template: EmailTemplate): Promise<SendResult>;
|
|
4
|
+
}
|
|
5
|
+
export declare function createEmailProvider(config: EmailConfig, logger?: Logger): EmailProvider;
|
|
6
|
+
//# sourceMappingURL=email.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/providers/email.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAElF,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD;AAoBD,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,MAAM,CAAC,EAAE,MAAM,GACd,aAAa,CA8Ef"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
let proxyConfigured = false;
|
|
2
|
+
async function configureProxy() {
|
|
3
|
+
if (proxyConfigured)
|
|
4
|
+
return;
|
|
5
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
6
|
+
if (proxyUrl) {
|
|
7
|
+
try {
|
|
8
|
+
const { setGlobalDispatcher, ProxyAgent } = await import('undici');
|
|
9
|
+
setGlobalDispatcher(new ProxyAgent(proxyUrl));
|
|
10
|
+
proxyConfigured = true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// ProxyAgent not available, continue without proxy
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createEmailProvider(config, logger) {
|
|
18
|
+
const { provider, apiKey, from, to } = config;
|
|
19
|
+
return {
|
|
20
|
+
async send(template) {
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
if (provider !== 'resend') {
|
|
23
|
+
return {
|
|
24
|
+
channel: 'email',
|
|
25
|
+
success: false,
|
|
26
|
+
error: `Unsupported email provider: ${provider}`,
|
|
27
|
+
duration: 0,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
await configureProxy();
|
|
32
|
+
const response = await fetch('https://api.resend.com/emails', {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
from,
|
|
40
|
+
to,
|
|
41
|
+
subject: template.subject,
|
|
42
|
+
html: template.html,
|
|
43
|
+
text: template.text,
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
const duration = Date.now() - start;
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorData = await response.json();
|
|
49
|
+
const errorMsg = `Resend API error: ${response.status} - ${errorData.message || 'Unknown error'}`;
|
|
50
|
+
logger?.error('Email send failed', new Error(errorMsg), {
|
|
51
|
+
to,
|
|
52
|
+
status: response.status,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
channel: 'email',
|
|
56
|
+
success: false,
|
|
57
|
+
error: errorMsg,
|
|
58
|
+
duration,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
logger?.info('Email notification sent', { to, duration });
|
|
62
|
+
return {
|
|
63
|
+
channel: 'email',
|
|
64
|
+
success: true,
|
|
65
|
+
duration,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const duration = Date.now() - start;
|
|
70
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
71
|
+
logger?.error('Email send failed', error instanceof Error ? error : undefined, {
|
|
72
|
+
to,
|
|
73
|
+
error: errorMsg,
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
channel: 'email',
|
|
77
|
+
success: false,
|
|
78
|
+
error: errorMsg,
|
|
79
|
+
duration,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TelegramConfig, TelegramTemplate, SendResult, Logger } from '../types.js';
|
|
2
|
+
export interface TelegramProvider {
|
|
3
|
+
send(template: TelegramTemplate): Promise<SendResult>;
|
|
4
|
+
}
|
|
5
|
+
export declare function createTelegramProvider(config: TelegramConfig, logger?: Logger): TelegramProvider;
|
|
6
|
+
//# sourceMappingURL=telegram.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telegram.d.ts","sourceRoot":"","sources":["../../src/providers/telegram.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAExF,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACvD;AAoBD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,cAAc,EACtB,MAAM,CAAC,EAAE,MAAM,GACd,gBAAgB,CAmElB"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
let proxyConfigured = false;
|
|
2
|
+
async function configureProxy() {
|
|
3
|
+
if (proxyConfigured)
|
|
4
|
+
return;
|
|
5
|
+
const proxyUrl = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
|
|
6
|
+
if (proxyUrl) {
|
|
7
|
+
try {
|
|
8
|
+
const { setGlobalDispatcher, ProxyAgent } = await import('undici');
|
|
9
|
+
setGlobalDispatcher(new ProxyAgent(proxyUrl));
|
|
10
|
+
proxyConfigured = true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// ProxyAgent not available, continue without proxy
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function createTelegramProvider(config, logger) {
|
|
18
|
+
const { botToken, chatId } = config;
|
|
19
|
+
return {
|
|
20
|
+
async send(template) {
|
|
21
|
+
const start = Date.now();
|
|
22
|
+
try {
|
|
23
|
+
await configureProxy();
|
|
24
|
+
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
|
25
|
+
const response = await fetch(url, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json' },
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
chat_id: chatId,
|
|
30
|
+
text: template.text,
|
|
31
|
+
parse_mode: template.parse_mode ?? 'HTML',
|
|
32
|
+
disable_web_page_preview: false,
|
|
33
|
+
}),
|
|
34
|
+
});
|
|
35
|
+
const duration = Date.now() - start;
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const errorText = await response.text();
|
|
38
|
+
const errorMsg = `Telegram API error: ${response.status} - ${errorText}`;
|
|
39
|
+
logger?.error('Telegram send failed', new Error(errorMsg), {
|
|
40
|
+
chatId,
|
|
41
|
+
status: response.status,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
channel: 'telegram',
|
|
45
|
+
success: false,
|
|
46
|
+
error: errorMsg,
|
|
47
|
+
duration,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
logger?.info('Telegram notification sent', { chatId, duration });
|
|
51
|
+
return {
|
|
52
|
+
channel: 'telegram',
|
|
53
|
+
success: true,
|
|
54
|
+
duration,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const duration = Date.now() - start;
|
|
59
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
60
|
+
logger?.error('Telegram send failed', error instanceof Error ? error : undefined, {
|
|
61
|
+
chatId,
|
|
62
|
+
error: errorMsg,
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
channel: 'telegram',
|
|
66
|
+
success: false,
|
|
67
|
+
error: errorMsg,
|
|
68
|
+
duration,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WebhookConfig, WebhookPayload, SendResult, Logger } from '../types.js';
|
|
2
|
+
export interface WebhookProvider {
|
|
3
|
+
send(payload: WebhookPayload): Promise<SendResult>;
|
|
4
|
+
}
|
|
5
|
+
export declare function createWebhookProvider(config: WebhookConfig, logger?: Logger): WebhookProvider;
|
|
6
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../src/providers/webhook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErF,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACpD;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EACrB,MAAM,CAAC,EAAE,MAAM,GACd,eAAe,CA6DjB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function createWebhookProvider(config, logger) {
|
|
2
|
+
const { url, method = 'POST', headers = {} } = config;
|
|
3
|
+
return {
|
|
4
|
+
async send(payload) {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(url, {
|
|
8
|
+
method,
|
|
9
|
+
headers: {
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
...headers,
|
|
12
|
+
},
|
|
13
|
+
body: JSON.stringify(payload),
|
|
14
|
+
});
|
|
15
|
+
const duration = Date.now() - start;
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
const errorText = await response.text();
|
|
18
|
+
const errorMsg = `Webhook error: ${response.status} - ${errorText}`;
|
|
19
|
+
logger?.error('Webhook send failed', new Error(errorMsg), {
|
|
20
|
+
url,
|
|
21
|
+
status: response.status,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
channel: 'webhook',
|
|
25
|
+
success: false,
|
|
26
|
+
error: errorMsg,
|
|
27
|
+
duration,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
logger?.info('Webhook notification sent', { url, duration });
|
|
31
|
+
return {
|
|
32
|
+
channel: 'webhook',
|
|
33
|
+
success: true,
|
|
34
|
+
duration,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
const duration = Date.now() - start;
|
|
39
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
40
|
+
logger?.error('Webhook send failed', error instanceof Error ? error : undefined, {
|
|
41
|
+
url,
|
|
42
|
+
error: errorMsg,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
channel: 'webhook',
|
|
46
|
+
success: false,
|
|
47
|
+
error: errorMsg,
|
|
48
|
+
duration,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AiChatEvent, TelegramTemplate, WebhookPayload, EmailTemplate } from '../types.js';
|
|
2
|
+
export declare function telegramTemplate(event: AiChatEvent): TelegramTemplate;
|
|
3
|
+
export declare function webhookPayload(event: AiChatEvent): WebhookPayload;
|
|
4
|
+
export declare function emailTemplate(event: AiChatEvent): EmailTemplate;
|
|
5
|
+
//# sourceMappingURL=ai-chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-chat.d.ts","sourceRoot":"","sources":["../../src/templates/ai-chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAYhG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,gBAAgB,CA4ErE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc,CAiBjE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,aAAa,CA8G/D"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
function anonymizeSessionId(sessionId) {
|
|
2
|
+
if (!sessionId || sessionId.length <= 6)
|
|
3
|
+
return sessionId;
|
|
4
|
+
return `${sessionId.slice(0, 4)}***${sessionId.slice(-2)}`;
|
|
5
|
+
}
|
|
6
|
+
function formatTime(ms) {
|
|
7
|
+
if (ms < 1000)
|
|
8
|
+
return `${ms}ms`;
|
|
9
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
10
|
+
}
|
|
11
|
+
export function telegramTemplate(event) {
|
|
12
|
+
const userMsg = event.userMessage.length > 150
|
|
13
|
+
? event.userMessage.slice(0, 150) + '...'
|
|
14
|
+
: event.userMessage;
|
|
15
|
+
const sessionIdDisplay = anonymizeSessionId(event.sessionId);
|
|
16
|
+
const timestamp = event.timestamp ? formatDate(event.timestamp) : formatDate(new Date());
|
|
17
|
+
const lines = [
|
|
18
|
+
`🗣 <b>博客 AI 对话</b>`,
|
|
19
|
+
``,
|
|
20
|
+
`👤 ${sessionIdDisplay} · 🕐 ${timestamp} · 第 ${event.roundNumber} 轮`,
|
|
21
|
+
``,
|
|
22
|
+
`❓ <b>读者:</b>`,
|
|
23
|
+
`<i>「${escapeHtml(userMsg)}」</i>`,
|
|
24
|
+
];
|
|
25
|
+
if (event.aiResponse) {
|
|
26
|
+
const aiMsg = event.aiResponse.length > 200
|
|
27
|
+
? event.aiResponse.slice(0, 200) + '...'
|
|
28
|
+
: event.aiResponse;
|
|
29
|
+
lines.push(``, `💬 <b>AI:</b>`, escapeHtml(aiMsg));
|
|
30
|
+
}
|
|
31
|
+
if (event.referencedArticles?.length) {
|
|
32
|
+
lines.push(``, `📎 <b>引用文章:</b>`);
|
|
33
|
+
event.referencedArticles.slice(0, 5).forEach(article => {
|
|
34
|
+
if (article.url) {
|
|
35
|
+
lines.push(` · <a href="${article.url}">${escapeHtml(article.title)}</a>`);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
lines.push(` · ${escapeHtml(article.title)}`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (event.model) {
|
|
43
|
+
lines.push(``, `⚙️ <b>模型配置:</b>`);
|
|
44
|
+
if (event.model.apiHost) {
|
|
45
|
+
lines.push(` · API Host: ${escapeHtml(event.model.apiHost)}`);
|
|
46
|
+
}
|
|
47
|
+
lines.push(` · 主对话模型: ${escapeHtml(event.model.name)}`);
|
|
48
|
+
if (event.model.provider) {
|
|
49
|
+
lines.push(` · Provider: ${escapeHtml(event.model.provider)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (event.usage) {
|
|
53
|
+
lines.push(``, `🧮 <b>Token 用量:</b>`);
|
|
54
|
+
lines.push(` · 本次请求合计: 总 ${event.usage.total} / 入 ${event.usage.input} / 出 ${event.usage.output}`);
|
|
55
|
+
}
|
|
56
|
+
if (event.timing) {
|
|
57
|
+
lines.push(``, `⏱️ <b>阶段耗时:</b>`);
|
|
58
|
+
lines.push(` · 总耗时: ${formatTime(event.timing.total)}`);
|
|
59
|
+
if (event.timing.keywordExtraction) {
|
|
60
|
+
lines.push(` · 关键词提取: ${formatTime(event.timing.keywordExtraction)}`);
|
|
61
|
+
}
|
|
62
|
+
if (event.timing.search) {
|
|
63
|
+
lines.push(` · 检索执行: ${formatTime(event.timing.search)}`);
|
|
64
|
+
}
|
|
65
|
+
if (event.timing.evidenceAnalysis) {
|
|
66
|
+
lines.push(` · 证据分析: ${formatTime(event.timing.evidenceAnalysis)}`);
|
|
67
|
+
}
|
|
68
|
+
if (event.timing.generation) {
|
|
69
|
+
lines.push(` · 文本生成: ${formatTime(event.timing.generation)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (event.siteUrl) {
|
|
73
|
+
lines.push(``, `🔗 <a href="${event.siteUrl}">访问网站</a>`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
text: lines.join('\n'),
|
|
77
|
+
parse_mode: 'HTML',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function webhookPayload(event) {
|
|
81
|
+
return {
|
|
82
|
+
event: 'ai-chat',
|
|
83
|
+
timestamp: event.timestamp?.toISOString() ?? new Date().toISOString(),
|
|
84
|
+
data: {
|
|
85
|
+
sessionId: event.sessionId,
|
|
86
|
+
sessionIdAnonymized: anonymizeSessionId(event.sessionId),
|
|
87
|
+
roundNumber: event.roundNumber,
|
|
88
|
+
userMessage: event.userMessage,
|
|
89
|
+
aiResponse: event.aiResponse,
|
|
90
|
+
referencedArticles: event.referencedArticles,
|
|
91
|
+
model: event.model,
|
|
92
|
+
usage: event.usage,
|
|
93
|
+
timing: event.timing,
|
|
94
|
+
siteUrl: event.siteUrl,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export function emailTemplate(event) {
|
|
99
|
+
const userMsg = event.userMessage.length > 300
|
|
100
|
+
? event.userMessage.slice(0, 300) + '...'
|
|
101
|
+
: event.userMessage;
|
|
102
|
+
const sessionIdDisplay = anonymizeSessionId(event.sessionId);
|
|
103
|
+
const timestamp = event.timestamp ? formatDate(event.timestamp) : formatDate(new Date());
|
|
104
|
+
const articlesHtml = event.referencedArticles?.length ? `
|
|
105
|
+
<div class="section">
|
|
106
|
+
<div class="section-title">📎 引用文章</div>
|
|
107
|
+
<div class="content">
|
|
108
|
+
${event.referencedArticles.slice(0, 5).map(a => a.url ? `<a href="${a.url}">${escapeHtml(a.title)}</a>` : escapeHtml(a.title)).join('<br>')}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
` : '';
|
|
112
|
+
const modelHtml = event.model ? `
|
|
113
|
+
<div class="section">
|
|
114
|
+
<div class="section-title">⚙️ 模型配置</div>
|
|
115
|
+
<div class="content meta-grid">
|
|
116
|
+
${event.model.apiHost ? `<span>API Host: ${escapeHtml(event.model.apiHost)}</span>` : ''}
|
|
117
|
+
<span>模型: ${escapeHtml(event.model.name)}</span>
|
|
118
|
+
${event.model.provider ? `<span>Provider: ${escapeHtml(event.model.provider)}</span>` : ''}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
` : '';
|
|
122
|
+
const usageHtml = event.usage ? `
|
|
123
|
+
<div class="section">
|
|
124
|
+
<div class="section-title">🧮 Token 用量</div>
|
|
125
|
+
<div class="content meta-grid">
|
|
126
|
+
<span>总计: ${event.usage.total}</span>
|
|
127
|
+
<span>输入: ${event.usage.input}</span>
|
|
128
|
+
<span>输出: ${event.usage.output}</span>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
` : '';
|
|
132
|
+
const timingHtml = event.timing ? `
|
|
133
|
+
<div class="section">
|
|
134
|
+
<div class="section-title">⏱️ 阶段耗时</div>
|
|
135
|
+
<div class="content meta-grid">
|
|
136
|
+
<span>总耗时: ${formatTime(event.timing.total)}</span>
|
|
137
|
+
${event.timing.keywordExtraction ? `<span>关键词提取: ${formatTime(event.timing.keywordExtraction)}</span>` : ''}
|
|
138
|
+
${event.timing.search ? `<span>检索: ${formatTime(event.timing.search)}</span>` : ''}
|
|
139
|
+
${event.timing.generation ? `<span>生成: ${formatTime(event.timing.generation)}</span>` : ''}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
` : '';
|
|
143
|
+
return {
|
|
144
|
+
subject: `🗣 博客 AI 对话 - 第 ${event.roundNumber} 轮`,
|
|
145
|
+
html: `
|
|
146
|
+
<!DOCTYPE html>
|
|
147
|
+
<html>
|
|
148
|
+
<head>
|
|
149
|
+
<meta charset="utf-8">
|
|
150
|
+
<style>
|
|
151
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
152
|
+
.header { border-bottom: 1px solid #eee; padding-bottom: 16px; margin-bottom: 16px; }
|
|
153
|
+
.title { font-size: 18px; font-weight: 600; margin: 0 0 8px 0; }
|
|
154
|
+
.meta-header { font-size: 13px; color: #666; }
|
|
155
|
+
.section { margin: 16px 0; }
|
|
156
|
+
.section-title { font-weight: 600; color: #555; font-size: 14px; margin-bottom: 8px; }
|
|
157
|
+
.content { background: #f9f9f9; padding: 16px; border-radius: 8px; }
|
|
158
|
+
.user-msg { border-left: 3px solid #007bff; padding-left: 12px; }
|
|
159
|
+
.ai-msg { border-left: 3px solid #28a745; padding-left: 12px; }
|
|
160
|
+
.meta-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; font-size: 13px; }
|
|
161
|
+
.meta-grid span { padding: 4px 8px; background: #eee; border-radius: 4px; }
|
|
162
|
+
.link { display: inline-block; background: #007bff; color: #fff; padding: 10px 20px; text-decoration: none; border-radius: 6px; margin-top: 16px; }
|
|
163
|
+
</style>
|
|
164
|
+
</head>
|
|
165
|
+
<body>
|
|
166
|
+
<div class="header">
|
|
167
|
+
<h1 class="title">🗣 博客 AI 对话</h1>
|
|
168
|
+
<p class="meta-header">👤 ${sessionIdDisplay} · 🕐 ${timestamp} · 第 ${event.roundNumber} 轮</p>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="section">
|
|
171
|
+
<div class="section-title">❓ 读者提问</div>
|
|
172
|
+
<div class="content user-msg">${escapeHtml(userMsg)}</div>
|
|
173
|
+
</div>
|
|
174
|
+
${event.aiResponse ? `
|
|
175
|
+
<div class="section">
|
|
176
|
+
<div class="section-title">💬 AI 回复</div>
|
|
177
|
+
<div class="content ai-msg">${escapeHtml(event.aiResponse.slice(0, 400))}${event.aiResponse.length > 400 ? '...' : ''}</div>
|
|
178
|
+
</div>
|
|
179
|
+
` : ''}
|
|
180
|
+
${articlesHtml}
|
|
181
|
+
${modelHtml}
|
|
182
|
+
${usageHtml}
|
|
183
|
+
${timingHtml}
|
|
184
|
+
${event.siteUrl ? `<a href="${event.siteUrl}" class="link">访问网站</a>` : ''}
|
|
185
|
+
</body>
|
|
186
|
+
</html>`,
|
|
187
|
+
text: `博客 AI 对话
|
|
188
|
+
|
|
189
|
+
👤 ${sessionIdDisplay} · 🕐 ${timestamp} · 第 ${event.roundNumber} 轮
|
|
190
|
+
|
|
191
|
+
❓ 读者提问:
|
|
192
|
+
${userMsg}
|
|
193
|
+
|
|
194
|
+
${event.aiResponse ? `💬 AI 回复:\n${event.aiResponse.slice(0, 400)}\n` : ''}
|
|
195
|
+
${event.referencedArticles?.length ? `📎 引用文章:\n${event.referencedArticles.map(a => ` · ${a.title}`).join('\n')}\n` : ''}
|
|
196
|
+
${event.model ? `⚙️ 模型: ${event.model.name}${event.model.provider ? ` @ ${event.model.provider}` : ''}\n` : ''}
|
|
197
|
+
${event.usage ? `🧮 Token: 总 ${event.usage.total} / 入 ${event.usage.input} / 出 ${event.usage.output}\n` : ''}
|
|
198
|
+
${event.timing ? `⏱️ 耗时: ${formatTime(event.timing.total)}\n` : ''}`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function escapeHtml(text) {
|
|
202
|
+
return text
|
|
203
|
+
.replace(/&/g, '&')
|
|
204
|
+
.replace(/</g, '<')
|
|
205
|
+
.replace(/>/g, '>')
|
|
206
|
+
.replace(/"/g, '"')
|
|
207
|
+
.replace(/'/g, ''');
|
|
208
|
+
}
|
|
209
|
+
function formatDate(date) {
|
|
210
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
211
|
+
return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
212
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CommentEvent, TelegramTemplate, WebhookPayload, EmailTemplate } from '../types.js';
|
|
2
|
+
export declare function telegramTemplate(event: CommentEvent): TelegramTemplate;
|
|
3
|
+
export declare function webhookPayload(event: CommentEvent): WebhookPayload;
|
|
4
|
+
export declare function emailTemplate(event: CommentEvent): EmailTemplate;
|
|
5
|
+
//# sourceMappingURL=comment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/templates/comment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,gBAAgB,CAgBtE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,cAAc,CAWlE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,CA+ChE"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export function telegramTemplate(event) {
|
|
2
|
+
const content = event.content.length > 200
|
|
3
|
+
? event.content.slice(0, 200) + '...'
|
|
4
|
+
: event.content;
|
|
5
|
+
return {
|
|
6
|
+
text: `💬 <b>新评论</b>
|
|
7
|
+
|
|
8
|
+
📖 文章:${escapeHtml(event.postTitle)}
|
|
9
|
+
👤 评论者:${escapeHtml(event.author)}
|
|
10
|
+
|
|
11
|
+
<i>「${escapeHtml(content)}」</i>
|
|
12
|
+
|
|
13
|
+
🔗 <a href="${event.postUrl}">查看评论</a>`,
|
|
14
|
+
parse_mode: 'HTML',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export function webhookPayload(event) {
|
|
18
|
+
return {
|
|
19
|
+
event: 'comment',
|
|
20
|
+
timestamp: new Date().toISOString(),
|
|
21
|
+
data: {
|
|
22
|
+
author: event.author,
|
|
23
|
+
content: event.content,
|
|
24
|
+
postTitle: event.postTitle,
|
|
25
|
+
postUrl: event.postUrl,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function emailTemplate(event) {
|
|
30
|
+
const content = event.content.length > 500
|
|
31
|
+
? event.content.slice(0, 500) + '...'
|
|
32
|
+
: event.content;
|
|
33
|
+
return {
|
|
34
|
+
subject: `💬 新评论 - ${event.postTitle}`,
|
|
35
|
+
html: `
|
|
36
|
+
<!DOCTYPE html>
|
|
37
|
+
<html>
|
|
38
|
+
<head>
|
|
39
|
+
<meta charset="utf-8">
|
|
40
|
+
<style>
|
|
41
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
42
|
+
.header { border-bottom: 1px solid #eee; padding-bottom: 16px; margin-bottom: 16px; }
|
|
43
|
+
.title { font-size: 18px; font-weight: 600; margin: 0 0 8px 0; }
|
|
44
|
+
.meta { font-size: 14px; color: #666; }
|
|
45
|
+
.content { background: #f9f9f9; padding: 16px; border-radius: 8px; margin: 16px 0; }
|
|
46
|
+
.quote { font-style: italic; color: #555; }
|
|
47
|
+
.link { display: inline-block; background: #007bff; color: #fff; padding: 10px 20px; text-decoration: none; border-radius: 6px; margin-top: 16px; }
|
|
48
|
+
.footer { margin-top: 24px; font-size: 12px; color: #999; }
|
|
49
|
+
</style>
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<div class="header">
|
|
53
|
+
<h1 class="title">💬 新评论</h1>
|
|
54
|
+
<p class="meta">文章:<a href="${event.postUrl}">${escapeHtml(event.postTitle)}</a></p>
|
|
55
|
+
</div>
|
|
56
|
+
<p><strong>评论者:</strong>${escapeHtml(event.author)}</p>
|
|
57
|
+
<div class="content">
|
|
58
|
+
<p class="quote">"${escapeHtml(content)}"</p>
|
|
59
|
+
</div>
|
|
60
|
+
<a href="${event.postUrl}" class="link">查看评论</a>
|
|
61
|
+
<div class="footer">
|
|
62
|
+
<p>此邮件由您的博客通知系统发送</p>
|
|
63
|
+
</div>
|
|
64
|
+
</body>
|
|
65
|
+
</html>`,
|
|
66
|
+
text: `新评论
|
|
67
|
+
|
|
68
|
+
文章:${event.postTitle}
|
|
69
|
+
评论者:${event.author}
|
|
70
|
+
|
|
71
|
+
"${content}"
|
|
72
|
+
|
|
73
|
+
查看评论:${event.postUrl}`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function escapeHtml(text) {
|
|
77
|
+
return text
|
|
78
|
+
.replace(/&/g, '&')
|
|
79
|
+
.replace(/</g, '<')
|
|
80
|
+
.replace(/>/g, '>')
|
|
81
|
+
.replace(/"/g, '"')
|
|
82
|
+
.replace(/'/g, ''');
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,eAAO,MAAM,gBAAgB,EAAE,cAW9B,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as commentTemplates from './comment.js';
|
|
2
|
+
import * as aiChatTemplates from './ai-chat.js';
|
|
3
|
+
export const defaultTemplates = {
|
|
4
|
+
comment: {
|
|
5
|
+
telegram: commentTemplates.telegramTemplate,
|
|
6
|
+
webhook: commentTemplates.webhookPayload,
|
|
7
|
+
email: commentTemplates.emailTemplate,
|
|
8
|
+
},
|
|
9
|
+
'ai-chat': {
|
|
10
|
+
telegram: aiChatTemplates.telegramTemplate,
|
|
11
|
+
webhook: aiChatTemplates.webhookPayload,
|
|
12
|
+
email: aiChatTemplates.emailTemplate,
|
|
13
|
+
},
|
|
14
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export interface TelegramConfig {
|
|
2
|
+
botToken: string;
|
|
3
|
+
chatId: string;
|
|
4
|
+
}
|
|
5
|
+
export interface WebhookConfig {
|
|
6
|
+
url: string;
|
|
7
|
+
method?: 'POST';
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export interface EmailConfig {
|
|
11
|
+
provider: 'resend';
|
|
12
|
+
apiKey: string;
|
|
13
|
+
from: string;
|
|
14
|
+
to: string;
|
|
15
|
+
}
|
|
16
|
+
export interface NotifyConfig {
|
|
17
|
+
telegram?: TelegramConfig;
|
|
18
|
+
webhook?: WebhookConfig;
|
|
19
|
+
email?: EmailConfig;
|
|
20
|
+
templates?: Partial<EventTemplates>;
|
|
21
|
+
logger?: Logger;
|
|
22
|
+
}
|
|
23
|
+
export type EventType = 'comment' | 'ai-chat';
|
|
24
|
+
export interface BaseEvent {
|
|
25
|
+
type: EventType;
|
|
26
|
+
timestamp?: Date;
|
|
27
|
+
}
|
|
28
|
+
export interface CommentEvent extends BaseEvent {
|
|
29
|
+
type: 'comment';
|
|
30
|
+
author: string;
|
|
31
|
+
content: string;
|
|
32
|
+
postTitle: string;
|
|
33
|
+
postUrl: string;
|
|
34
|
+
}
|
|
35
|
+
export interface ArticleRef {
|
|
36
|
+
title: string;
|
|
37
|
+
url?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ModelInfo {
|
|
40
|
+
name: string;
|
|
41
|
+
provider?: string;
|
|
42
|
+
apiHost?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface TokenUsage {
|
|
45
|
+
total: number;
|
|
46
|
+
input: number;
|
|
47
|
+
output: number;
|
|
48
|
+
}
|
|
49
|
+
export interface PhaseTiming {
|
|
50
|
+
total: number;
|
|
51
|
+
keywordExtraction?: number;
|
|
52
|
+
search?: number;
|
|
53
|
+
evidenceAnalysis?: number;
|
|
54
|
+
generation?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface AiChatEvent extends BaseEvent {
|
|
57
|
+
type: 'ai-chat';
|
|
58
|
+
sessionId: string;
|
|
59
|
+
roundNumber: number;
|
|
60
|
+
userMessage: string;
|
|
61
|
+
aiResponse?: string;
|
|
62
|
+
referencedArticles?: ArticleRef[];
|
|
63
|
+
model?: ModelInfo;
|
|
64
|
+
usage?: TokenUsage;
|
|
65
|
+
timing?: PhaseTiming;
|
|
66
|
+
siteUrl?: string;
|
|
67
|
+
}
|
|
68
|
+
export type NotifyEvent = CommentEvent | AiChatEvent;
|
|
69
|
+
export interface TelegramTemplate {
|
|
70
|
+
text: string;
|
|
71
|
+
parse_mode?: 'HTML' | 'MarkdownV2';
|
|
72
|
+
}
|
|
73
|
+
export interface WebhookPayload {
|
|
74
|
+
event: EventType;
|
|
75
|
+
timestamp: string;
|
|
76
|
+
data: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
export interface EmailTemplate {
|
|
79
|
+
subject: string;
|
|
80
|
+
html: string;
|
|
81
|
+
text?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface EventTemplates {
|
|
84
|
+
comment: {
|
|
85
|
+
telegram: (event: CommentEvent) => TelegramTemplate;
|
|
86
|
+
webhook: (event: CommentEvent) => WebhookPayload;
|
|
87
|
+
email: (event: CommentEvent) => EmailTemplate;
|
|
88
|
+
};
|
|
89
|
+
'ai-chat': {
|
|
90
|
+
telegram: (event: AiChatEvent) => TelegramTemplate;
|
|
91
|
+
webhook: (event: AiChatEvent) => WebhookPayload;
|
|
92
|
+
email: (event: AiChatEvent) => EmailTemplate;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export type Channel = 'telegram' | 'webhook' | 'email';
|
|
96
|
+
export interface SendResult {
|
|
97
|
+
channel: Channel;
|
|
98
|
+
success: boolean;
|
|
99
|
+
error?: string;
|
|
100
|
+
duration?: number;
|
|
101
|
+
}
|
|
102
|
+
export interface NotifyResult {
|
|
103
|
+
event: EventType;
|
|
104
|
+
results: SendResult[];
|
|
105
|
+
success: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface Logger {
|
|
108
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
109
|
+
error(message: string, error?: Error, data?: Record<string, unknown>): void;
|
|
110
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
111
|
+
}
|
|
112
|
+
export interface Notifier {
|
|
113
|
+
comment(event: Omit<CommentEvent, 'type'>): Promise<NotifyResult>;
|
|
114
|
+
aiChat(event: Omit<AiChatEvent, 'type'>): Promise<NotifyResult>;
|
|
115
|
+
send(event: NotifyEvent): Promise<NotifyResult>;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@astro-minimax/notify",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Multi-channel notification package for astro-minimax blogs",
|
|
6
|
+
"author": "Souloss",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/"
|
|
19
|
+
],
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"build:watch": "tsc -p tsconfig.build.json --watch",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"prepublishOnly": "pnpm run clean && pnpm run build",
|
|
26
|
+
"publish": "npm publish --access public"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"grammy": "^1.41.1",
|
|
30
|
+
"undici": "^6.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"resend": "^3.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"resend": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@cloudflare/workers-types": "^4.20260313.1",
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"typescript": "^5.9.3"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=22.12.0"
|
|
47
|
+
}
|
|
48
|
+
}
|