Files
memind/scripts/wechat-mp-menu.mjs
T
john 9b4a25799f Add smart ACK provider for WeChat MP replies
Replace fixed ackText with a rule-based AckProvider that picks
response templates by message type and intent (translate, summary,
rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync,
zero I/O, auto-falls back to config.ackText on any error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 15:19:03 +08:00

131 lines
3.5 KiB
JavaScript
Executable File
Raw 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.
#!/usr/bin/env node
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { loadH5Environment } from './load-env.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
loadH5Environment(__dirname);
const DEFAULT_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/stable_token';
const DEFAULT_MENU_CREATE_URL = 'https://api.weixin.qq.com/cgi-bin/menu/create';
const DEFAULT_MENU_GET_URL = 'https://api.weixin.qq.com/cgi-bin/menu/get';
const MENU = {
button: [
{
type: 'view',
name: 'MeMind',
url: 'https://m.tkmind.cn',
},
{
type: 'view',
name: 'M空间',
url: 'https://m.tkmind.cn/space',
},
{
type: 'view',
name: 'M广场',
url: 'https://plaza.tkmind.cn',
},
],
};
function endpointFromTokenUrl(pathname, fallback) {
const tokenUrl = process.env.H5_WECHAT_MP_TOKEN_URL?.trim() || DEFAULT_TOKEN_URL;
try {
const url = new URL(tokenUrl);
url.pathname = pathname;
url.search = '';
return url.toString();
} catch {
return fallback;
}
}
async function readJson(response) {
const text = await response.text();
if (!response.ok) {
throw new Error(text || `HTTP ${response.status}`);
}
return text ? JSON.parse(text) : null;
}
async function getAccessToken() {
const appid =
process.env.H5_WECHAT_MP_APP_ID?.trim() ?? process.env.H5_WECHAT_APP_ID?.trim() ?? '';
const secret =
process.env.H5_WECHAT_MP_APP_SECRET?.trim() ?? process.env.H5_WECHAT_APP_SECRET?.trim() ?? '';
if (!appid || !secret) {
throw new Error(
'缺少 H5_WECHAT_MP_APP_ID / H5_WECHAT_MP_APP_SECRET(或 H5_WECHAT_APP_ID / H5_WECHAT_APP_SECRET',
);
}
const tokenUrl = process.env.H5_WECHAT_MP_TOKEN_URL?.trim() || DEFAULT_TOKEN_URL;
const payload = await readJson(
await fetch(tokenUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credential',
appid,
secret,
force_refresh: false,
}),
}),
);
if (!payload?.access_token) {
throw new Error(payload?.errmsg || `获取 access_token 失败 (${payload?.errcode ?? 'unknown'})`);
}
return payload.access_token;
}
async function createMenu(accessToken) {
const baseUrl =
process.env.H5_WECHAT_MP_MENU_CREATE_URL?.trim() ||
endpointFromTokenUrl('/cgi-bin/menu/create', DEFAULT_MENU_CREATE_URL);
const url = `${baseUrl}?access_token=${encodeURIComponent(accessToken)}`;
const payload = await readJson(
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(MENU),
}),
);
if (Number(payload?.errcode ?? 0) !== 0) {
throw new Error(payload?.errmsg || `创建菜单失败 (${payload?.errcode})`);
}
return payload;
}
async function getMenu(accessToken) {
const baseUrl =
process.env.H5_WECHAT_MP_MENU_GET_URL?.trim() ||
endpointFromTokenUrl('/cgi-bin/menu/get', DEFAULT_MENU_GET_URL);
const url = `${baseUrl}?access_token=${encodeURIComponent(accessToken)}`;
return readJson(await fetch(url, { method: 'GET' }));
}
const dryRun = process.argv.includes('--dry-run');
if (dryRun) {
console.log(JSON.stringify(MENU, null, 2));
process.exit(0);
}
const accessToken = await getAccessToken();
const created = await createMenu(accessToken);
const current = await getMenu(accessToken).catch((err) => ({ warning: err.message }));
console.log(
JSON.stringify(
{
ok: true,
created,
menu: MENU,
current,
},
null,
2,
),
);