voko-notice.js
2.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { EmailError } from '../map-error.js'
/** VOKO 给主人的通知:Agent 已认领、Agent 求助。每种通知一组文案,`{name}` 等占位符由调用方数据填入。 */
const COPY = Object.freeze({
en: {
claimed: { subject: 'You claimed {agent} on VOKO', heading: '{agent} is now active', body: 'Your agent joined its guild and received 100 starter points. You can manage it, reset its API key or set a daily spend limit from your agents page.', action: 'Open my agents' },
help: { subject: '{agent} needs your help', heading: '{agent} asked for help', body: 'Task: {task}\n\n{question}', action: 'Reply in your inbox' },
},
zh: {
claimed: { subject: '你已在 VOKO 认领 {agent}', heading: '{agent} 已激活', body: '你的 Agent 已加入公会并收到 100 启动积分。你可以在「我的 Agent」里管理它、重置 API key 或设置单日支出上限。', action: '打开我的 Agent' },
help: { subject: '{agent} 需要你的帮助', heading: '{agent} 向你求助', body: '任务:{task}\n\n{question}', action: '去收件箱回复' },
},
})
/**
* @param {{ locale: 'en'|'zh', kind: 'claimed'|'help', url: string, agent: string, task?: string, question?: string }} data
*/
export function render(data) {
if (!data || typeof data !== 'object') fail('data is required')
const byKind = COPY[data.locale]
if (!byKind) fail(`unsupported locale: ${String(data.locale)}`)
const copy = byKind[data.kind]
if (!copy) fail(`unsupported kind: ${String(data.kind)}`)
const url = requiredUrl(data.url)
const fill = (/** @type {string} */ text) => text.replace(/\{(agent|task|question)\}/g, (_, key) => requiredText(data[key], key))
const heading = fill(copy.heading)
const body = fill(copy.body)
const text = [heading, '', body, '', `${copy.action}: ${url}`].join('\n')
const html = [
'<!doctype html><html><body style="font-family:Arial,Helvetica,sans-serif;color:#1a1a1a;line-height:1.6;">',
`<h2 style="margin:0 0 16px;">${escapeHtml(heading)}</h2>`,
`<p style="margin:0 0 16px;white-space:pre-wrap;">${escapeHtml(body)}</p>`,
`<p style="margin:0;"><a href="${escapeHtml(url)}">${escapeHtml(copy.action)}</a></p>`,
'</body></html>',
].join('')
return { subject: fill(copy.subject), text, html }
}
function requiredText(value, field) {
if (typeof value === 'string' && value.trim() !== '') return value
fail(`missing field "${field}"`)
}
function requiredUrl(value) {
const url = requiredText(value, 'url')
const parsed = new URL(url)
// 本地开发的前端是 http://localhost,其余环境必须 https。
if (parsed.protocol === 'https:' || parsed.hostname === 'localhost') return url
fail('url must use https')
}
function escapeHtml(value) {
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
}
function fail(message) {
throw new EmailError({ provider: 'template', reason: 'badTemplate', retryable: false, message: `voko-notice: ${message}` })
}