88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
import { maskContactPhone } from './phone';
|
||
import { shanghaiYmd, startOfShanghaiDay } from './shanghai-date';
|
||
import { wecomReportPeriod, type WecomReportKind, type WecomReportPeriod } from './wecom-report';
|
||
|
||
export const WECOM_PLUGIN_PAGE_SIZE_DEFAULT = 5;
|
||
export const WECOM_PLUGIN_PAGE_SIZE_MAX = 10;
|
||
|
||
export const WECOM_PLUGIN_METRICS_KINDS = ['today', 'daily', 'weekly', 'monthly'] as const;
|
||
export type WecomPluginMetricsKind = (typeof WECOM_PLUGIN_METRICS_KINDS)[number];
|
||
|
||
export type WecomPluginMetricsPeriod = Omit<WecomReportPeriod, 'kind'> & {
|
||
kind: WecomPluginMetricsKind;
|
||
};
|
||
|
||
export function isWecomPluginMetricsKind(v: string): v is WecomPluginMetricsKind {
|
||
return (WECOM_PLUGIN_METRICS_KINDS as readonly string[]).includes(v);
|
||
}
|
||
|
||
export function parseWecomPluginMetricsKind(raw?: string | null): WecomPluginMetricsKind | null {
|
||
const v = String(raw ?? '').trim().toLowerCase();
|
||
if (!v) return 'today';
|
||
return isWecomPluginMetricsKind(v) ? v : null;
|
||
}
|
||
|
||
export function clampWecomPluginPageSize(raw?: string | number | null): number {
|
||
const n = typeof raw === 'number' ? raw : Number(raw);
|
||
if (!Number.isFinite(n) || n <= 0) return WECOM_PLUGIN_PAGE_SIZE_DEFAULT;
|
||
return Math.min(WECOM_PLUGIN_PAGE_SIZE_MAX, Math.max(1, Math.floor(n)));
|
||
}
|
||
|
||
export function clampWecomPluginPage(raw?: string | number | null): number {
|
||
const n = typeof raw === 'number' ? raw : Number(raw);
|
||
if (!Number.isFinite(n) || n <= 0) return 1;
|
||
return Math.min(100, Math.floor(n));
|
||
}
|
||
|
||
/** 长度必须相同后再逐字符 XOR,避免短密码走快速失败路径时的明显差异(仍非密码学级) */
|
||
export function verifyWecomPluginApiKey(provided: string, expected?: string | null): boolean {
|
||
const exp = String(expected ?? '');
|
||
const got = String(provided ?? '');
|
||
if (!exp || !got || exp.length !== got.length) return false;
|
||
let diff = 0;
|
||
for (let i = 0; i < exp.length; i++) {
|
||
diff |= exp.charCodeAt(i) ^ got.charCodeAt(i);
|
||
}
|
||
return diff === 0;
|
||
}
|
||
|
||
export function isWecomPluginEnabled(env: {
|
||
WECOM_PLUGIN_ENABLED?: string;
|
||
WECOM_PLUGIN_API_KEY?: string;
|
||
}): boolean {
|
||
return env.WECOM_PLUGIN_ENABLED === 'true' && Boolean(String(env.WECOM_PLUGIN_API_KEY ?? '').trim());
|
||
}
|
||
|
||
export function wecomPluginMetricsPeriod(
|
||
kind: WecomPluginMetricsKind,
|
||
now = new Date(),
|
||
): WecomPluginMetricsPeriod {
|
||
if (kind === 'today') {
|
||
const start = startOfShanghaiDay(now);
|
||
const ymd = shanghaiYmd(start);
|
||
return {
|
||
kind,
|
||
start,
|
||
endExclusive: now,
|
||
periodKey: ymd,
|
||
title: `今日(${ymd})`,
|
||
rangeLabel: ymd,
|
||
incrementLabel: '今日新增',
|
||
};
|
||
}
|
||
const period = wecomReportPeriod(kind as WecomReportKind, now);
|
||
return { ...period, kind };
|
||
}
|
||
|
||
export function toWecomPluginUserView(user: {
|
||
userNo: string;
|
||
nickname?: string | null;
|
||
phone?: string | null;
|
||
}): { userNo: string; nickname: string; phone: string } {
|
||
return {
|
||
userNo: user.userNo,
|
||
nickname: user.nickname?.trim() || '—',
|
||
phone: maskContactPhone(user.phone),
|
||
};
|
||
}
|