Files
BYW-VoiceHub/server/utils/wechatSubscribeFields.ts

100 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 微信**订阅消息**字段的取值规则(纯函数,不依赖 Nitro / 数据库,可直接用 node 跑验证)。
*
* 为什么单独放一个文件:微信的字段校验是**整条消息级别**的 —— 只要有一个字段超长、
* 格式不对或为空,整条消息发不出去,微信只回一个 `47003 argument invalid! data.xxx.value invalid`
* (而且报错里点名的字段不一定是真正有问题的那一个)。所以这些裁剪规则必须能单独测。
*
* 各类型限制(公众平台模板字段名后缀就是类型):
* thing 20 字
* phrase 5 个以内纯汉字(**只截断不补齐**:补齐会塞进非汉字字符,更容易 47003)
* time 必须是 'YYYY-MM-DD HH:mm'
* date 必须是 'YYYY-MM-DD'
* character_string 32 字(只能数字、字母)
* number/amount 32 字(只能数字)
*/
/** 各类型字段的长度上限 */
export const FIELD_MAX_LEN: Record<string, number> = {
thing: 20,
phrase: 5,
time: 24,
date: 10,
character_string: 32,
number: 32,
amount: 32,
digit: 32,
letter: 32,
symbol: 5
}
/** 字段名 → 类型(thing1/character_string3 → thing/character_string) */
export function fieldType(key: string): string {
return String(key || '').replace(/[0-9]+$/, '')
}
/**
* 按字段类型裁剪成微信能接受的值。
* ⚠️ 返回空字符串仍然会被微信拒,调用方要保证变量有值(见 wechatSubscribeService 的占位逻辑)。
*/
export function clampField(key: string, value: unknown): string {
const raw = value === null || value === undefined ? '' : String(value)
// 微信不认换行,先压成一行(站内通知的 message 是多行拼的)
const text = raw.replace(/\s+/g, ' ').trim()
const type = fieldType(key)
if (type === 'thing') {
// thing:20 字以内;超了截 19 字 + 省略号(省略号也算 1 个字,正好 20)
return Array.from(text).length > 20 ? Array.from(text).slice(0, 19).join('') + '…' : text
}
if (type === 'phrase') {
/*
* phrase = 5 个以内纯汉字。**只截断、不补齐**。
*
* 早先的版本为了绕开「社区反馈恰好 5 个汉字会报 47003」,把短词用全角空格补到 5 个字 ——
* 结果补进去的 U+3000 不是汉字,反而更容易被判「不是纯汉字」。现在按官方文档字面执行:
* 汉字数 ≤ 5 就原样发(「已排期」3 个字完全合规),超了才截。
*/
return Array.from(text).slice(0, 5).join('')
}
const max = FIELD_MAX_LEN[type] || 32
return text.slice(0, max)
}
/** 北京时间(UTC+8)的 'YYYY-MM-DD' */
export function beijingDateString(date: Date): string {
const t = new Date(date.getTime() + 8 * 60 * 60 * 1000)
const p = (n: number) => (n < 10 ? '0' + n : String(n))
return t.getUTCFullYear() + '-' + p(t.getUTCMonth() + 1) + '-' + p(t.getUTCDate())
}
/**
* 把播放日期 + 排期时段揉成微信 time 型字段要求的 'YYYY-MM-DD HH:mm'。
* 支持 '12:30'、'12:30-13:00'、'12:30~13:00'、'12:30'(全角冒号),取**开始时间**。
* 解析不出来时退化成当天 00:00 —— time 型字段不能为空,否则整条消息 47003。
*/
export function buildWechatTimeValue(date: Date, timeRange?: string | null): string {
const day = beijingDateString(date)
const m = String(timeRange || '').match(/(\d{1,2})\s*[::]\s*(\d{2})/)
if (!m) return day + ' 00:00'
const hh = String(Math.min(23, parseInt(m[1], 10))).padStart(2, '0')
return day + ' ' + hh + ':' + m[2]
}
/** 'YYYY-MM-DD HH:mm'(微信 time 型字段要求的格式,北京时间) */
export function beijingNow(): string {
const now = new Date(Date.now() + 8 * 60 * 60 * 1000)
const p = (n: number) => (n < 10 ? '0' + n : String(n))
return (
now.getUTCFullYear() +
'-' +
p(now.getUTCMonth() + 1) +
'-' +
p(now.getUTCDate()) +
' ' +
p(now.getUTCHours()) +
':' +
p(now.getUTCMinutes())
)
}