feat(wechat): 关注公众号后自动回复欢迎语(被动回复,可后台配置)
用户关注服务号(扫登录码后关注 或 直接搜索关注)时,在回调 HTTP 响应里 回一条文本消息(微信「被动回复」)。被动回复不需要 access_token、不受 IP 白名单 限制,永远能发。欢迎语在管理端「登录设置」可配,留空用默认。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -232,6 +232,7 @@ export interface WechatMPConfig {
|
||||
appid: string;
|
||||
token: string; // 消息推送签名校验用(与公众平台服务器配置一致)
|
||||
app_secret: string; // 单管理员后台:明文回显
|
||||
welcome: string; // 关注后自动回复的欢迎语(空=用默认)
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
@@ -255,11 +256,11 @@ export async function getWechatMP(): Promise<WechatMPConfig> {
|
||||
const res = guard(await fetch(`${ADMIN}/wechat-mp`, { headers: authHeaders() }));
|
||||
const d = (await res.json().catch(() => ({}))) as Partial<WechatMPConfig> & { error?: string };
|
||||
if (!res.ok) throw new Error(d.error ?? `wechat-mp failed: ${res.status}`);
|
||||
return { appid: d.appid ?? "", token: d.token ?? "", app_secret: d.app_secret ?? "", enabled: !!d.enabled };
|
||||
return { appid: d.appid ?? "", token: d.token ?? "", app_secret: d.app_secret ?? "", welcome: d.welcome ?? "", enabled: !!d.enabled };
|
||||
}
|
||||
|
||||
// app_secret 传空串 = 沿用已保存的。
|
||||
export async function saveWechatMP(body: { appid: string; app_secret: string; token: string }): Promise<{ enabled: boolean }> {
|
||||
export async function saveWechatMP(body: { appid: string; app_secret: string; token: string; welcome: string }): Promise<{ enabled: boolean }> {
|
||||
const res = guard(await fetch(`${ADMIN}/wechat-mp`, { method: "PUT", headers: authHeaders(true), body: JSON.stringify(body) }));
|
||||
const d = (await res.json().catch(() => ({}))) as { enabled?: boolean; error?: string };
|
||||
if (!res.ok) throw new Error(d.error ?? `save failed: ${res.status}`);
|
||||
|
||||
@@ -8,6 +8,7 @@ export function LoginConfigPage() {
|
||||
const [appid, setAppid] = useState("");
|
||||
const [token, setToken] = useState("");
|
||||
const [secret, setSecret] = useState(""); // 留空=沿用已存
|
||||
const [welcome, setWelcome] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [err, setErr] = useState("");
|
||||
const [ok, setOk] = useState(false);
|
||||
@@ -19,6 +20,7 @@ export function LoginConfigPage() {
|
||||
setAppid(c.appid);
|
||||
setToken(c.token);
|
||||
setSecret(c.app_secret);
|
||||
setWelcome(c.welcome);
|
||||
})
|
||||
.catch((e) => setErr((e as Error).message));
|
||||
}, []);
|
||||
@@ -29,8 +31,8 @@ export function LoginConfigPage() {
|
||||
setErr("");
|
||||
setOk(false);
|
||||
try {
|
||||
const r = await saveWechatMP({ appid: appid.trim(), app_secret: secret, token: token.trim() });
|
||||
setCfg((c) => (c ? { ...c, appid: appid.trim(), token: token.trim(), app_secret: secret, enabled: r.enabled } : c));
|
||||
const r = await saveWechatMP({ appid: appid.trim(), app_secret: secret, token: token.trim(), welcome: welcome.trim() });
|
||||
setCfg((c) => (c ? { ...c, appid: appid.trim(), token: token.trim(), app_secret: secret, welcome: welcome.trim(), enabled: r.enabled } : c));
|
||||
setOk(true);
|
||||
} catch (e) {
|
||||
setErr((e as Error).message);
|
||||
@@ -77,6 +79,12 @@ export function LoginConfigPage() {
|
||||
<input value={token} onChange={(e) => setToken(e.target.value)} placeholder="自定义一串字母数字"
|
||||
className="mt-1 block w-full rounded-lg border border-gray-200 px-2.5 py-1.5 font-mono text-sm text-gray-800 focus:border-violet-400 focus:outline-none" />
|
||||
</label>
|
||||
<label className="text-xs text-gray-500 md:col-span-2">
|
||||
关注欢迎语(用户关注后自动回复。留空则用默认,建议写上下载链接)
|
||||
<textarea value={welcome} onChange={(e) => setWelcome(e.target.value)} rows={3}
|
||||
placeholder={"欢迎关注!\n下载客户端登录即可开始体验。\n下载地址:https://…"}
|
||||
className="mt-1 block w-full resize-y rounded-lg border border-gray-200 px-2.5 py-1.5 text-sm text-gray-800 focus:border-violet-400 focus:outline-none" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{err && <p className="mt-2 text-xs text-rose-500">{err}</p>}
|
||||
|
||||
Reference in New Issue
Block a user